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
110pub 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}