Skip to main content

bwx/
actions.rs

1use crate::prelude::*;
2
3pub async fn register(
4    email: &str,
5    apikey: crate::locked::ApiKey,
6) -> Result<()> {
7    let (client, config) = api_client_async().await?;
8
9    client
10        .register(email, &crate::config::device_id(&config).await?, &apikey)
11        .await?;
12
13    Ok(())
14}
15
16pub async fn login(
17    email: &str,
18    password: crate::locked::Password,
19    two_factor_token: Option<&str>,
20    two_factor_provider: Option<crate::api::TwoFactorProviderType>,
21) -> Result<(
22    String,
23    String,
24    crate::api::KdfType,
25    u32,
26    Option<u32>,
27    Option<u32>,
28    String,
29    crate::identity::Identity,
30)> {
31    let (client, config) = api_client_async().await?;
32    let (kdf, iterations, memory, parallelism) =
33        client.prelogin(email).await?;
34
35    let identity = crate::identity::Identity::new(
36        email,
37        &password,
38        kdf,
39        iterations,
40        memory,
41        parallelism,
42    )?;
43    let (access_token, refresh_token, protected_key) = client
44        .login(
45            email,
46            config.sso_id.as_deref(),
47            &crate::config::device_id(&config).await?,
48            &identity.master_password_hash,
49            two_factor_token,
50            two_factor_provider,
51        )
52        .await?;
53
54    Ok((
55        access_token,
56        refresh_token,
57        kdf,
58        iterations,
59        memory,
60        parallelism,
61        protected_key,
62        identity,
63    ))
64}
65
66pub async fn send_two_factor_email(
67    email: &str,
68    sso_email_2fa_session_token: &str,
69) -> Result<()> {
70    let (client, config) = api_client_async().await?;
71    client
72        .send_email_login(
73            email,
74            &crate::config::device_id(&config).await?,
75            sso_email_2fa_session_token,
76        )
77        .await
78}
79
80pub fn unlock<S: std::hash::BuildHasher>(
81    email: &str,
82    password: &crate::locked::Password,
83    kdf: crate::api::KdfType,
84    iterations: u32,
85    memory: Option<u32>,
86    parallelism: Option<u32>,
87    protected_key: &str,
88    protected_private_key: &str,
89    protected_org_keys: &std::collections::HashMap<String, String, S>,
90) -> Result<(
91    crate::locked::Keys,
92    std::collections::HashMap<String, crate::locked::Keys>,
93)> {
94    let identity = crate::identity::Identity::new(
95        email,
96        password,
97        kdf,
98        iterations,
99        memory,
100        parallelism,
101    )?;
102    unlock_with_identity(
103        &identity,
104        protected_key,
105        protected_private_key,
106        protected_org_keys,
107    )
108}
109
110/// Like `unlock`, but reuses an `Identity` already derived elsewhere
111/// (e.g. by `login`). Lets callers skip a second KDF run when they're
112/// going to unlock the vault immediately after authenticating.
113pub fn unlock_with_identity<S: std::hash::BuildHasher>(
114    identity: &crate::identity::Identity,
115    protected_key: &str,
116    protected_private_key: &str,
117    protected_org_keys: &std::collections::HashMap<String, String, S>,
118) -> Result<(
119    crate::locked::Keys,
120    std::collections::HashMap<String, crate::locked::Keys>,
121)> {
122    let protected_key =
123        crate::cipherstring::CipherString::new(protected_key)?;
124    let key = match protected_key.decrypt_locked_symmetric(&identity.keys) {
125        Ok(master_keys) => crate::locked::Keys::new(master_keys),
126        Err(Error::InvalidMac) => {
127            return Err(Error::IncorrectPassword {
128                message: "Password is incorrect. Try again.".to_string(),
129            })
130        }
131        Err(e) => return Err(e),
132    };
133
134    let protected_private_key =
135        crate::cipherstring::CipherString::new(protected_private_key)?;
136    let private_key =
137        match protected_private_key.decrypt_locked_symmetric(&key) {
138            Ok(private_key) => crate::locked::PrivateKey::new(private_key),
139            Err(e) => return Err(e),
140        };
141
142    let mut org_keys = std::collections::HashMap::new();
143    for (org_id, protected_org_key) in protected_org_keys {
144        let protected_org_key =
145            crate::cipherstring::CipherString::new(protected_org_key)?;
146        let org_key =
147            match protected_org_key.decrypt_locked_asymmetric(&private_key) {
148                Ok(org_key) => crate::locked::Keys::new(org_key),
149                Err(e) => return Err(e),
150            };
151        org_keys.insert(org_id.clone(), org_key);
152    }
153
154    Ok((key, org_keys))
155}
156
157pub async fn sync(
158    access_token: &str,
159    refresh_token: &str,
160) -> Result<(
161    Option<String>,
162    (
163        String,
164        String,
165        std::collections::HashMap<String, String>,
166        Vec<crate::db::Entry>,
167    ),
168)> {
169    with_exchange_refresh_token_async(
170        access_token,
171        refresh_token,
172        |access_token| {
173            let access_token = access_token.to_string();
174            Box::pin(async move { sync_once(&access_token).await })
175        },
176    )
177    .await
178}
179
180async fn sync_once(
181    access_token: &str,
182) -> Result<(
183    String,
184    String,
185    std::collections::HashMap<String, String>,
186    Vec<crate::db::Entry>,
187)> {
188    let (client, _) = api_client_async().await?;
189    client.sync(access_token).await
190}
191
192pub fn add(
193    access_token: &str,
194    refresh_token: &str,
195    name: &str,
196    data: &crate::db::EntryData,
197    notes: Option<&str>,
198    folder_id: Option<&str>,
199) -> Result<(Option<String>, ())> {
200    with_exchange_refresh_token(access_token, refresh_token, |access_token| {
201        add_once(access_token, name, data, notes, folder_id)
202    })
203}
204
205fn add_once(
206    access_token: &str,
207    name: &str,
208    data: &crate::db::EntryData,
209    notes: Option<&str>,
210    folder_id: Option<&str>,
211) -> Result<()> {
212    let (client, _) = api_client()?;
213    client.add(access_token, name, data, notes, folder_id)?;
214    Ok(())
215}
216
217pub fn edit(
218    access_token: &str,
219    refresh_token: &str,
220    id: &str,
221    org_id: Option<&str>,
222    name: &str,
223    data: &crate::db::EntryData,
224    fields: &[crate::db::Field],
225    notes: Option<&str>,
226    folder_uuid: Option<&str>,
227    history: &[crate::db::HistoryEntry],
228) -> Result<(Option<String>, ())> {
229    with_exchange_refresh_token(access_token, refresh_token, |access_token| {
230        edit_once(
231            access_token,
232            id,
233            org_id,
234            name,
235            data,
236            fields,
237            notes,
238            folder_uuid,
239            history,
240        )
241    })
242}
243
244fn edit_once(
245    access_token: &str,
246    id: &str,
247    org_id: Option<&str>,
248    name: &str,
249    data: &crate::db::EntryData,
250    fields: &[crate::db::Field],
251    notes: Option<&str>,
252    folder_uuid: Option<&str>,
253    history: &[crate::db::HistoryEntry],
254) -> Result<()> {
255    let (client, _) = api_client()?;
256    client.edit(
257        access_token,
258        id,
259        org_id,
260        name,
261        data,
262        fields,
263        notes,
264        folder_uuid,
265        history,
266    )?;
267    Ok(())
268}
269
270pub fn remove(
271    access_token: &str,
272    refresh_token: &str,
273    id: &str,
274) -> Result<(Option<String>, ())> {
275    with_exchange_refresh_token(access_token, refresh_token, |access_token| {
276        remove_once(access_token, id)
277    })
278}
279
280fn remove_once(access_token: &str, id: &str) -> Result<()> {
281    let (client, _) = api_client()?;
282    client.remove(access_token, id)?;
283    Ok(())
284}
285
286pub fn list_folders(
287    access_token: &str,
288    refresh_token: &str,
289) -> Result<(Option<String>, Vec<(String, String)>)> {
290    with_exchange_refresh_token(access_token, refresh_token, |access_token| {
291        list_folders_once(access_token)
292    })
293}
294
295fn list_folders_once(access_token: &str) -> Result<Vec<(String, String)>> {
296    let (client, _) = api_client()?;
297    client.folders(access_token)
298}
299
300pub fn create_folder(
301    access_token: &str,
302    refresh_token: &str,
303    name: &str,
304) -> Result<(Option<String>, String)> {
305    with_exchange_refresh_token(access_token, refresh_token, |access_token| {
306        create_folder_once(access_token, name)
307    })
308}
309
310fn create_folder_once(access_token: &str, name: &str) -> Result<String> {
311    let (client, _) = api_client()?;
312    client.create_folder(access_token, name)
313}
314
315fn with_exchange_refresh_token<F, T>(
316    access_token: &str,
317    refresh_token: &str,
318    f: F,
319) -> Result<(Option<String>, T)>
320where
321    F: Fn(&str) -> Result<T>,
322{
323    match f(access_token) {
324        Ok(t) => Ok((None, t)),
325        Err(Error::RequestUnauthorized) => {
326            let access_token = exchange_refresh_token(refresh_token)?;
327            let t = f(&access_token)?;
328            Ok((Some(access_token), t))
329        }
330        Err(e) => Err(e),
331    }
332}
333
334async fn with_exchange_refresh_token_async<F, T>(
335    access_token: &str,
336    refresh_token: &str,
337    f: F,
338) -> Result<(Option<String>, T)>
339where
340    F: Fn(
341            &str,
342        ) -> std::pin::Pin<
343            Box<dyn std::future::Future<Output = Result<T>> + Send>,
344        > + Send
345        + Sync,
346    T: Send,
347{
348    match f(access_token).await {
349        Ok(t) => Ok((None, t)),
350        Err(Error::RequestUnauthorized) => {
351            let access_token =
352                exchange_refresh_token_async(refresh_token).await?;
353            let t = f(&access_token).await?;
354            Ok((Some(access_token), t))
355        }
356        Err(e) => Err(e),
357    }
358}
359
360fn exchange_refresh_token(refresh_token: &str) -> Result<String> {
361    let (client, _) = api_client()?;
362    client.exchange_refresh_token(refresh_token)
363}
364
365async fn exchange_refresh_token_async(refresh_token: &str) -> Result<String> {
366    let (client, _) = api_client()?;
367    client.exchange_refresh_token_async(refresh_token).await
368}
369
370fn api_client() -> Result<(crate::api::Client, crate::config::Config)> {
371    let config = crate::config::Config::load()?;
372    let client = crate::api::Client::new(
373        &config.base_url(),
374        &config.identity_url(),
375        &config.ui_url(),
376        config.client_cert_path(),
377    );
378    Ok((client, config))
379}
380
381async fn api_client_async(
382) -> Result<(crate::api::Client, crate::config::Config)> {
383    let config = crate::config::Config::load_async().await?;
384    let client = crate::api::Client::new(
385        &config.base_url(),
386        &config.identity_url(),
387        &config.ui_url(),
388        config.client_cert_path(),
389    );
390    Ok((client, config))
391}