ic_auth_client/storage/
async_storage.rs

1//! Storage implementations for authentication client data.
2//!
3//! This module provides browser-based storage for secure credential management.
4
5use super::{StorageError, StoredKey};
6use futures::future::BoxFuture;
7use web_sys::Storage;
8
9const LOCAL_STORAGE_PREFIX: &str = "ic-";
10
11/// Implementation of [`AuthClientStorage`].
12#[derive(Debug, Default, Clone, Copy)]
13pub struct LocalStorage;
14
15impl LocalStorage {
16    /// Creates a new instance of [`LocalStorage`].
17    pub fn new() -> Self {
18        Self
19    }
20
21    fn get_local_storage(&self) -> Option<Storage> {
22        match gloo_utils::window().local_storage() {
23            Ok(storage) => storage,
24            Err(_e) => {
25                #[cfg(feature = "tracing")]
26                error!("Could not find local storage: {_e:?}");
27                None
28            }
29        }
30    }
31}
32
33impl AuthClientStorage for LocalStorage {
34    fn get<'a>(
35        &'a mut self,
36        key: &'a str,
37    ) -> BoxFuture<'a, Result<Option<StoredKey>, StorageError>> {
38        Box::pin(async move {
39            let local_storage = self
40                .get_local_storage()
41                .ok_or_else(|| StorageError::WebSys("LocalStorage not available".to_string()))?;
42            let key = format!("{}{}", LOCAL_STORAGE_PREFIX, key);
43            let value = local_storage.get_item(&key)?;
44            Ok(value.map(StoredKey::String))
45        })
46    }
47
48    fn set<'a>(
49        &'a mut self,
50        key: &'a str,
51        value: StoredKey,
52    ) -> BoxFuture<'a, Result<(), StorageError>> {
53        Box::pin(async move {
54            let local_storage = self
55                .get_local_storage()
56                .ok_or_else(|| StorageError::WebSys("LocalStorage not available".to_string()))?;
57            let key = format!("{}{}", LOCAL_STORAGE_PREFIX, key);
58            let value = value.encode();
59            local_storage.set_item(&key, value.as_ref())?;
60            Ok(())
61        })
62    }
63
64    fn remove<'a>(&'a mut self, key: &'a str) -> BoxFuture<'a, Result<(), StorageError>> {
65        Box::pin(async move {
66            let local_storage = self
67                .get_local_storage()
68                .ok_or_else(|| StorageError::WebSys("LocalStorage not available".to_string()))?;
69            let key = format!("{}{}", LOCAL_STORAGE_PREFIX, key);
70            local_storage.remove_item(&key)?;
71            Ok(())
72        })
73    }
74}
75
76impl From<LocalStorage> for Box<dyn AuthClientStorage> {
77    fn from(storage: LocalStorage) -> Self {
78        Box::new(storage)
79    }
80}
81
82/// Trait for persisting user authentication data.
83pub trait AuthClientStorage: Send {
84    /// Retrieves a stored value by key from the storage backend.
85    ///
86    /// # Parameters
87    /// - `key`: The key to retrieve the value for. The key will be prefixed with the storage prefix.
88    ///
89    /// # Returns
90    /// Returns `Ok(Some(StoredKey))` if the key exists in storage, `Ok(None)` if not, or
91    /// `Err(StorageError)` if there was an error accessing the storage.
92    fn get<'a>(
93        &'a mut self,
94        key: &'a str,
95    ) -> BoxFuture<'a, Result<Option<StoredKey>, StorageError>>;
96
97    /// Stores a value with the given key in the storage backend.
98    ///
99    /// # Parameters
100    /// - `key`: The key to store the value under. The key will be prefixed with the storage prefix.
101    /// - `value`: The value to store, which will be encoded before storage.
102    ///
103    /// # Returns
104    /// Returns `Ok(())` if the value was successfully stored, or `Err(StorageError)` if there was an
105    /// error accessing the storage or storing the value.
106    fn set<'a>(
107        &'a mut self,
108        key: &'a str,
109        value: StoredKey,
110    ) -> BoxFuture<'a, Result<(), StorageError>>;
111
112    /// Removes a stored value by key from the storage backend.
113    ///
114    /// # Parameters
115    /// - `key`: The key to remove from storage. The key will be prefixed with the storage prefix.
116    ///
117    /// # Returns
118    /// Returns `Ok(())` if the key was successfully removed or didn't exist, or `Err(StorageError)`
119    /// if there was an error accessing the storage.
120    fn remove<'a>(&'a mut self, key: &'a str) -> BoxFuture<'a, Result<(), StorageError>>;
121}
122
123#[allow(dead_code)]
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use wasm_bindgen_test::*;
128
129    #[wasm_bindgen_test]
130    async fn test_local_storage() {
131        let mut storage = LocalStorage;
132        let value = StoredKey::String("value".to_string());
133        storage.set("test", value).await.unwrap();
134        let value = storage.get("test").await.unwrap();
135        assert_eq!(value, Some(StoredKey::String("value".to_string())));
136        storage.remove("test").await.unwrap();
137        let value = storage.get("test").await.unwrap();
138        assert_eq!(value, None);
139    }
140}