ffsend_api/action/
password.rs

1use thiserror::Error;
2
3use crate::api::data::{Error as DataError, OwnedData};
4use crate::api::nonce::{request_nonce, NonceError};
5use crate::api::request::{ensure_success, ResponseError};
6use crate::api::url::UrlBuilder;
7use crate::client::Client;
8use crate::crypto::key_set::KeySet;
9use crate::file::remote_file::RemoteFile;
10
11/// An action to change a password of an uploaded Send file.
12///
13/// This API specification for this action is compatible with both Firefox Send v2 and v3.
14pub struct Password<'a> {
15    /// The remote file to change the password for.
16    file: &'a RemoteFile,
17
18    /// The new password to use for the file.
19    password: &'a str,
20
21    /// The authentication nonce.
22    /// May be an empty vector if the nonce is unknown.
23    nonce: Vec<u8>,
24}
25
26impl<'a> Password<'a> {
27    /// Construct a new password action for the given remote file.
28    pub fn new(file: &'a RemoteFile, password: &'a str, nonce: Option<Vec<u8>>) -> Self {
29        Self {
30            file,
31            password,
32            nonce: nonce.unwrap_or_default(),
33        }
34    }
35
36    /// Invoke the password action.
37    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
38        // Create a key set for the file
39        let mut key = KeySet::from(self.file, None);
40
41        // Fetch the authentication nonce if not set yet
42        if self.nonce.is_empty() {
43            self.nonce = self.fetch_auth_nonce(client)?;
44        }
45
46        // Derive a new authentication key
47        key.derive_auth_password(self.password, &UrlBuilder::download(self.file, true));
48
49        // Build the password data, wrap it as owned
50        let data = OwnedData::from(PasswordData::from(&key), &self.file)
51            .map_err(|err| -> PrepareError { err.into() })?;
52
53        // Send the request to change the password
54        self.change_password(client, &data)
55    }
56
57    /// Fetch the authentication nonce for the file from the Send server.
58    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
59        request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
60    }
61
62    /// Send the request for changing the file password.
63    fn change_password(
64        &self,
65        client: &Client,
66        data: &OwnedData<PasswordData>,
67    ) -> Result<(), Error> {
68        // Get the password URL, and send the change
69        let url = UrlBuilder::api_password(self.file);
70        let response = client
71            .post(url)
72            .json(&data)
73            .send()
74            .map_err(|_| ChangeError::Request)?;
75
76        // Ensure the response is successful
77        ensure_success(&response).map_err(|err| err.into())
78    }
79}
80
81/// The data object to send to the password endpoint,
82/// which sets the file password.
83#[derive(Debug, Serialize)]
84struct PasswordData {
85    /// The authentication key
86    auth: String,
87}
88
89impl PasswordData {
90    /// Create the password data object from the given key set.
91    pub fn from(key: &KeySet) -> PasswordData {
92        PasswordData {
93            auth: key.auth_key_encoded().unwrap(),
94        }
95    }
96}
97
98#[derive(Error, Debug)]
99pub enum Error {
100    /// An error occurred while preparing the action.
101    #[error("failed to prepare setting the password")]
102    Prepare(#[from] PrepareError),
103
104    /// The given Send file has expired, or did never exist in the first place.
105    /// Therefore the file could not be downloaded.
106    #[error("the file has expired or did never exist")]
107    Expired,
108
109    /// An error has occurred while sending the password change request to
110    /// the server.
111    #[error("failed to send the password change request")]
112    Change(#[from] ChangeError),
113}
114
115impl From<NonceError> for Error {
116    fn from(err: NonceError) -> Error {
117        match err {
118            NonceError::Expired => Error::Expired,
119            err => Error::Prepare(PrepareError::Auth(err)),
120        }
121    }
122}
123
124impl From<ResponseError> for Error {
125    fn from(err: ResponseError) -> Error {
126        match err {
127            ResponseError::Expired => Error::Expired,
128            err => Error::Change(ChangeError::Response(err)),
129        }
130    }
131}
132
133#[derive(Error, Debug)]
134pub enum PrepareError {
135    /// Failed authenticating, needed to set a new password.
136    #[error("failed to authenticate")]
137    Auth(#[from] NonceError),
138
139    /// Some error occurred while building the data that will be sent.
140    /// The owner token might possibly be missing, the wrapped error will
141    /// describe this further.
142    #[error("")]
143    Data(#[from] DataError),
144}
145
146#[derive(Error, Debug)]
147pub enum ChangeError {
148    /// Sending the request to change the password failed.
149    #[error("failed to send password change request")]
150    Request,
151
152    /// The server responded with an error while changing the file password.
153    #[error("bad response from server while changing password")]
154    Response(#[from] ResponseError),
155}