use thiserror::Error;
use crate::api::data::{Error as DataError, OwnedData};
use crate::api::nonce::{request_nonce, NonceError};
use crate::api::request::{ensure_success, ResponseError};
use crate::api::url::UrlBuilder;
use crate::client::Client;
use crate::crypto::key_set::KeySet;
use crate::file::remote_file::RemoteFile;
pub struct Password<'a> {
file: &'a RemoteFile,
password: &'a str,
nonce: Vec<u8>,
}
impl<'a> Password<'a> {
pub fn new(file: &'a RemoteFile, password: &'a str, nonce: Option<Vec<u8>>) -> Self {
Self {
file,
password,
nonce: nonce.unwrap_or_default(),
}
}
pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
let mut key = KeySet::from(self.file, None);
if self.nonce.is_empty() {
self.nonce = self.fetch_auth_nonce(client)?;
}
key.derive_auth_password(self.password, &UrlBuilder::download(self.file, true));
let data = OwnedData::from(PasswordData::from(&key), &self.file)
.map_err(|err| -> PrepareError { err.into() })?;
self.change_password(client, &data)
}
fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
}
fn change_password(
&self,
client: &Client,
data: &OwnedData<PasswordData>,
) -> Result<(), Error> {
let url = UrlBuilder::api_password(self.file);
let response = client
.post(url)
.json(&data)
.send()
.map_err(|_| ChangeError::Request)?;
ensure_success(&response).map_err(|err| err.into())
}
}
#[derive(Debug, Serialize)]
struct PasswordData {
auth: String,
}
impl PasswordData {
pub fn from(key: &KeySet) -> PasswordData {
PasswordData {
auth: key.auth_key_encoded().unwrap(),
}
}
}
#[derive(Error, Debug)]
pub enum Error {
#[error("failed to prepare setting the password")]
Prepare(#[from] PrepareError),
#[error("the file has expired or did never exist")]
Expired,
#[error("failed to send the password change request")]
Change(#[from] ChangeError),
}
impl From<NonceError> for Error {
fn from(err: NonceError) -> Error {
match err {
NonceError::Expired => Error::Expired,
err => Error::Prepare(PrepareError::Auth(err)),
}
}
}
impl From<ResponseError> for Error {
fn from(err: ResponseError) -> Error {
match err {
ResponseError::Expired => Error::Expired,
err => Error::Change(ChangeError::Response(err)),
}
}
}
#[derive(Error, Debug)]
pub enum PrepareError {
#[error("failed to authenticate")]
Auth(#[from] NonceError),
#[error("")]
Data(#[from] DataError),
}
#[derive(Error, Debug)]
pub enum ChangeError {
#[error("failed to send password change request")]
Request,
#[error("bad response from server while changing password")]
Response(#[from] ResponseError),
}