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