1use std::collections::HashMap;
2
3use log::info;
4use serde_json;
5
6use crate::common::Credentials;
7use crate::crypto::{derive_crypto_key, hash_username};
8use crate::utils::generate_password_base64;
9use crate::{ItemMetaData, ObjectKey, SecureStorage, SecureStringError, SecureStringResult};
10
11use super::local_storage_user::LocalStorageUser;
12
13const PASSWORD_FIELD: &str = "__PASSWD__";
14
15#[derive(Clone, PartialEq, Debug)]
16pub struct LocalStorage {
17 credentials: Credentials,
18 storage: Option<SecureStorage>,
19}
20
21impl LocalStorage {
22 pub async fn new(credentials: Credentials) -> SecureStringResult<Self> {
23 let password = credentials.password();
24
25 let storage = if password.is_empty() {
26 None
27 } else {
28 let user = LocalStorageUser::create_or_validate(&credentials).await?;
29 Some(user.secure_storage().clone())
30 };
31 Ok(Self {
32 credentials,
33 storage,
34 })
35 }
36
37 pub fn get_username(&self) -> SecureStringResult<String> {
38 Ok(self.credentials.username().to_string())
39 }
40
41 pub async fn user_exists(environment: &str, username: &str) -> bool {
42 let hashed_username = hash_username(username);
43 let object_key = ObjectKey::new(environment, &hashed_username, "self").unwrap();
44 SecureStorage::exists(object_key).await
45 }
46
47 pub async fn soft_reset(&self) -> SecureStringResult<()> {
48 self.hard_reset().await?;
51 Ok(())
52 }
53
54 pub async fn hard_reset(&self) -> SecureStringResult<()> {
55 let environment = self.credentials.environment();
56 let username = self.credentials.username();
57 let hashed_username = hash_username(&username);
58 let object_key = ObjectKey::new(&environment, &hashed_username, "self")?;
59
60 let secure_storage = SecureStorage::for_deletion(object_key);
61 secure_storage.delete().await?;
62
63 Ok(())
64 }
65
66 pub async fn validate_password(&self) -> SecureStringResult<bool> {
67 if self.storage.is_none() {
69 return Err(SecureStringError::StorageNotInitialized);
70 }
71
72 match self.storage.as_ref().unwrap().load().await {
74 Ok(_) => Ok(true),
76 Err(SecureStringError::DecryptError(_)) => Ok(false),
78 Err(err) => Err(err),
80 }
81 }
82
83 pub async fn change_password(&mut self, new_password: &str) -> SecureStringResult<()> {
84 let items = self.get_items().await?;
86 let new_credentials = Credentials::new(
87 Some(&self.credentials.environment()),
88 &self.credentials.username(),
89 new_password,
90 );
91
92 self.soft_reset().await?;
94
95 let user = LocalStorageUser::create_or_validate(&new_credentials).await?;
97 self.storage = Some(user.secure_storage().clone());
98
99 self.put_items(&items).await?;
101 Ok(())
102 }
103
104 pub async fn initiate_with_password(
105 environment: Option<&str>,
106 username: &str,
107 password: &str,
108 ) -> SecureStringResult<Self> {
109 let credentials = Credentials::new(environment, username, password);
110 Self::new(credentials).await
111 }
112
113 pub async fn initiate_with_no_password(
114 environment: Option<&str>,
115 username: &str,
116 ) -> SecureStringResult<Self> {
117 let password = "";
118 let credentials = Credentials::new(environment, username, &password);
119 Self::new(credentials).await
120 }
121
122 pub async fn list_items(&self) -> SecureStringResult<Vec<ItemMetaData>> {
123 let items = self.get_items().await?;
124
125 let items_as_meta = items
126 .into_iter()
127 .map(|(id, tags)| {
128 let mut new_tags = tags.clone();
129 new_tags.retain(|k, _| !k.starts_with("__") && !k.ends_with("__"));
130 ItemMetaData::new_with_tags(&id, new_tags)
131 })
132 .collect();
133
134 Ok(items_as_meta)
135 }
136
137 pub async fn add_item(&mut self, item_meta: ItemMetaData) -> SecureStringResult<()> {
138 let mut items = self.get_items().await?;
139
140 let item_id = item_meta.id();
141
142 if items.contains_key(&item_id) {
143 return Err(SecureStringError::FormAlreadyExists);
144 }
145
146 let item_meta_stored = self.populate_meta_stored(&item_meta, None);
147
148 items.insert(item_id, item_meta_stored);
149
150 self.put_items(&items).await
151 }
152
153 pub async fn delete_item(&mut self, item_id: &str) -> SecureStringResult<()> {
154 let environment = self.credentials.environment();
156 let object_key = ObjectKey::new(&environment, "", &item_id)?;
157 let secure_storage = SecureStorage::for_deletion(object_key);
158 secure_storage.delete().await?;
159
160 let mut items = self.get_items().await?;
162 if items.remove(item_id).is_none() {
163 return Err(SecureStringError::PasswordNotFound(format!(
164 "Configuration for {} not found",
165 item_id
166 )));
167 }
168 self.put_items(&items).await
169 }
170
171 pub async fn save_content(
172 &mut self,
173 item_meta: ItemMetaData,
174 content: &[u8],
175 ) -> SecureStringResult<()> {
176 let item_id = item_meta.id();
177 info!("Saving configuration for {}", item_id);
178
179 let password = generate_password_base64()?;
180 let derived_key = derive_crypto_key(&password, &item_id).await?;
181
182 let environment = self.credentials.environment();
183 let object_key = ObjectKey::new(&environment, "", &item_id)?;
184 let secure_storage_form = SecureStorage::new(object_key, derived_key);
185 secure_storage_form.save(content).await?;
186
187 let mut items = self.get_items().await?;
188 let item_meta_stored = self.populate_meta_stored(&item_meta, Some(password));
189
190 items.insert(item_id, item_meta_stored);
191
192 self.put_items(&items).await
193 }
194
195 pub async fn load_content(&self, item_id: &str) -> Result<Option<Vec<u8>>, SecureStringError> {
196 let items = self.get_items().await?;
197
198 if let Some(meta) = items.get(item_id) {
199 if let Some(password) = meta.get(PASSWORD_FIELD) {
200 let environment = self.credentials.environment();
201 let object_key = ObjectKey::new(&environment, "", &item_id)?;
202 let derived_key = derive_crypto_key(&password, &item_id).await?;
203 let secure_storage_form = SecureStorage::new(object_key, derived_key);
204
205 secure_storage_form.load().await.map(Some)
206 } else {
207 Ok(None)
209 }
210 } else {
211 Err(SecureStringError::NoLocalStorageData)
213 }
214 }
215
216 async fn get_items(&self) -> SecureStringResult<HashMap<String, HashMap<String, String>>> {
217 if self.storage.is_none() {
218 return Err(SecureStringError::StorageNotInitialized);
219 }
220
221 match self.storage.as_ref().unwrap().load().await {
222 Ok(passwords_json) => serde_json::from_slice(&passwords_json).map_err(|err| {
223 SecureStringError::SerdeError(format!("Failed to parse forms_db: {:?}", err))
224 }),
225 Err(SecureStringError::NoLocalStorageData) => Ok(HashMap::new()),
226 Err(err) => Err(err),
227 }
228 }
229
230 async fn put_items(
231 &mut self,
232 items: &HashMap<String, HashMap<String, String>>,
233 ) -> SecureStringResult<()> {
234 if self.storage.is_none() {
235 return Err(SecureStringError::StorageNotInitialized);
236 }
237 let items_vec = serde_json::to_vec(items).map_err(|err| {
238 SecureStringError::SerdeError(format!("Failed to serialize forms_db: {:?}", err))
239 })?;
240 self.storage.as_ref().unwrap().save(&items_vec).await
241 }
242
243 fn populate_meta_stored(
244 &self,
245 item_meta: &ItemMetaData,
246 password: Option<String>,
247 ) -> HashMap<String, String> {
248 let mut item_meta_stored = HashMap::new();
249
250 if let Some(pwd) = password {
251 item_meta_stored.insert(PASSWORD_FIELD.to_string(), pwd);
252 }
253
254 if let Some(tags) = item_meta.tags() {
255 item_meta_stored.extend(tags);
256 }
257
258 item_meta_stored
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use std::collections::HashMap;
265
266 use wasm_bindgen_test::*;
267
268 use super::*;
269
270 wasm_bindgen_test_configure!(run_in_browser);
271
272 #[wasm_bindgen_test]
273 async fn test_list_items() {
274 let credentials = Credentials::new(None, "test_user_list_items", "password_for_list_items");
275
276 let local_storage = LocalStorage::new(credentials.clone()).await;
277 assert!(local_storage.is_ok());
278 let local_storage = local_storage.unwrap();
279
280 let result = local_storage.list_items().await;
281 assert!(
282 result.is_ok(),
283 "Failed to list documents: {:?}",
284 result.err().unwrap()
285 );
286
287 let document_map = result.unwrap();
288 assert_eq!(document_map.len(), 0);
289 local_storage.hard_reset().await.unwrap();
290 }
291
292 #[wasm_bindgen_test]
293 async fn test_save_and_load_content() {
294 let credentials = Credentials::new(None, "test_user_save_load", "password_for_save_load");
295
296 let local_storage = LocalStorage::new(credentials.clone()).await;
297 assert!(local_storage.is_ok());
298 let mut local_storage = local_storage.unwrap();
299
300 let mut config = HashMap::new();
301 config.insert("__NAME__".to_string(), "test_config".to_string());
302
303 let form_id = format!("{}-{}", credentials.username(), credentials.password());
305 let meta_data = ItemMetaData::new(&form_id);
306 let config_bytes = serde_json::to_vec(&config).unwrap();
307
308 let save_result = local_storage
309 .save_content(meta_data.clone(), &config_bytes)
310 .await;
311 assert!(
312 save_result.is_ok(),
313 "Failed to save document: {:?}",
314 save_result.err().unwrap()
315 );
316
317 let load_result = local_storage.load_content(&form_id).await;
318 assert!(
319 load_result.is_ok(),
320 "Failed to load document: {:?}",
321 load_result.err().unwrap()
322 );
323
324 let loaded_config_bytes_option = load_result.unwrap();
325 assert!(
326 loaded_config_bytes_option.is_some(),
327 "Loaded document did not exist"
328 );
329
330 let loaded_config_bytes = loaded_config_bytes_option.unwrap();
331 let loaded_config: HashMap<String, String> =
332 serde_json::from_slice(&loaded_config_bytes).unwrap();
333 assert_eq!(
334 loaded_config, config,
335 "Loaded document did not match saved document"
336 );
337 local_storage.hard_reset().await.unwrap();
338 }
339
340 #[wasm_bindgen_test]
341 async fn test_add_and_delete_items() {
342 let credentials = Credentials::new(
343 None,
344 "test_user_add_and_delete_items",
345 "password_for_add_and_delete",
346 );
347
348 let local_storage = LocalStorage::new(credentials.clone()).await;
349 assert!(local_storage.is_ok());
350 let mut local_storage = local_storage.unwrap();
351
352 let form_id = format!("{}-{}", credentials.username(), credentials.password());
354 let meta_data = ItemMetaData::new(&form_id);
355
356 let add_result = local_storage.add_item(meta_data.clone()).await;
358 assert!(
359 add_result.is_ok(),
360 "Failed to add document: {:?}",
361 add_result.err().unwrap()
362 );
363
364 let load_result = local_storage.load_content(&form_id).await;
365 assert!(load_result.is_ok(), "Load failed after add");
366
367 let loaded_data = load_result.unwrap();
368 assert!(loaded_data.is_none(), "Data present after add");
369
370 let delete_result = local_storage.delete_item(&form_id).await;
372 assert!(
373 delete_result.is_ok(),
374 "Failed to delete document: {:?}",
375 delete_result.err().unwrap()
376 );
377
378 let load_result_after_delete = local_storage.load_content(&form_id).await;
379 assert!(
380 matches!(
381 load_result_after_delete,
382 Err(SecureStringError::NoLocalStorageData)
383 ),
384 "Successfully loaded document after delete"
385 );
386 local_storage.hard_reset().await.unwrap();
387 }
388
389 #[wasm_bindgen_test]
390 async fn test_delete_non_existent_item() {
391 let credentials = Credentials::new(
392 None,
393 "test_user_delete_non_existent_item",
394 "password_for_delete_non_existent",
395 );
396
397 let local_storage = LocalStorage::new(credentials.clone()).await;
398 assert!(local_storage.is_ok());
399 let mut local_storage = local_storage.unwrap();
400
401 let form_id = format!("{}-{}", credentials.username(), credentials.password());
403 let delete_result = local_storage.delete_item(&form_id).await;
404 assert!(
405 delete_result.is_err(),
406 "Successfully deleted a non-existent document"
407 );
408
409 local_storage.hard_reset().await.unwrap();
410 }
411
412 #[wasm_bindgen_test]
413 async fn test_load_non_existent_item() {
414 let credentials = Credentials::new(
415 None,
416 "test_user_load_non_existent_item",
417 "password_for_load_non_existent",
418 );
419
420 let local_storage = LocalStorage::new(credentials.clone()).await;
421 assert!(local_storage.is_ok());
422 let local_storage = local_storage.unwrap();
423
424 let form_id = format!("{}-{}", credentials.username(), credentials.password());
426 let load_result = local_storage.load_content(&form_id).await;
427 assert!(
428 load_result.is_err(),
429 "Successfully loaded a non-existent document"
430 );
431 local_storage.hard_reset().await.unwrap();
432 }
433
434 #[wasm_bindgen_test]
435 async fn test_populate_meta_stored() {
436 let credentials = Credentials::new(
437 None,
438 "test_user_populate_meta_stored",
439 "password_for_populate_meta_stored",
440 );
441
442 let local_storage = LocalStorage::new(credentials.clone()).await;
443 assert!(local_storage.is_ok());
444 let local_storage = local_storage.unwrap();
445
446 let mut tags = HashMap::new();
447 tags.insert("tag1".to_string(), "value1".to_string());
448 tags.insert("tag2".to_string(), "value2".to_string());
449
450 let item_meta = ItemMetaData::new_with_tags("test_populate_meta_stored", tags);
451
452 let item_meta_stored =
453 local_storage.populate_meta_stored(&item_meta, Some("password".to_string()));
454
455 assert_eq!(
456 item_meta_stored.get(PASSWORD_FIELD),
457 Some(&"password".to_string())
458 );
459 assert_eq!(item_meta_stored.get("tag1"), Some(&"value1".to_string()));
460 assert_eq!(item_meta_stored.get("tag2"), Some(&"value2".to_string()));
461 local_storage.hard_reset().await.unwrap();
462 }
463
464 #[wasm_bindgen_test]
465 async fn test_validate_password() {
466 let correct_password = "correct_password";
467 let incorrect_password = "incorrect_password";
468
469 let local_storage =
471 LocalStorage::initiate_with_password(None, "test_user", correct_password).await;
472 assert!(
473 local_storage.is_ok(),
474 "Failed to initialize with correct password"
475 );
476
477 let result =
479 LocalStorage::initiate_with_password(None, "test_user", incorrect_password).await;
480 assert!(
481 matches!(result, Err(SecureStringError::DecryptError(_))),
482 "Unexpected error type returned"
483 );
484
485 local_storage.unwrap().hard_reset().await.unwrap();
487 }
488
489 #[wasm_bindgen_test]
490 async fn test_change_password() {
491 let old_password = "old_password";
492 let new_password = "new_password";
493 let username = "test_user_change_password_local_storage";
494
495 let item_id = "test_item_id";
496 let item_meta = ItemMetaData::new(item_id);
497 let test_content = b"test_content";
498
499 let local_storage =
501 LocalStorage::initiate_with_password(None, username, old_password).await;
502 assert!(
503 local_storage.is_ok(),
504 "Failed to initialize with old password"
505 );
506 let mut local_storage = local_storage.unwrap();
507
508 let save_result = local_storage
510 .save_content(item_meta.clone(), test_content)
511 .await;
512 assert!(
513 save_result.is_ok(),
514 "Failed to save content before changing password"
515 );
516
517 let change_password_result = local_storage.change_password(new_password).await;
519 assert!(change_password_result.is_ok(), "Failed to change password");
520
521 let result = LocalStorage::initiate_with_password(None, username, old_password).await;
523 assert!(
524 matches!(result, Err(SecureStringError::DecryptError(_))),
525 "Unexpectedly succeeded in loading with old password"
526 );
527
528 let result = LocalStorage::initiate_with_password(None, username, new_password).await;
530 assert!(result.is_ok(), "Failed to load with new password");
531 let new_local_storage = result.unwrap();
532
533 let load_result = new_local_storage.load_content(item_id).await;
535 assert!(
536 load_result.is_ok() && load_result.unwrap() == Some(test_content.to_vec()),
537 "Failed to load content or content was incorrect after changing password"
538 );
539
540 local_storage.hard_reset().await.unwrap();
542 }
543}