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}