use base64::prelude::{Engine as _, BASE64_STANDARD_NO_PAD};
use std::{cell::RefCell, future::Future, rc::Rc};
use web_sys::{wasm_bindgen::JsValue, CryptoKeyPair, Storage};
pub const KEY_STORAGE_KEY: &str = "identity";
pub const KEY_STORAGE_DELEGATION: &str = "delegation";
pub(crate) const KEY_VECTOR: &str = "iv";
const LOCAL_STORAGE_PREFIX: &str = "ic-";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StoredKey {
String(String),
CryptoKeyPair(CryptoKeyPair),
}
impl StoredKey {
pub fn decode(&self) -> Result<Vec<u8>, String> {
match self {
StoredKey::String(s) => BASE64_STANDARD_NO_PAD.decode(s).map_err(|e| e.to_string()),
StoredKey::CryptoKeyPair(_) => Err("CryptoKeyPair cannot be decoded".to_string()),
}
}
pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
BASE64_STANDARD_NO_PAD.encode(data.as_ref())
}
}
impl From<String> for StoredKey {
fn from(value: String) -> Self {
StoredKey::String(value)
}
}
pub trait AuthClientStorage {
fn get<T: AsRef<str>>(&mut self, key: T) -> impl Future<Output = Option<StoredKey>>;
fn set<S: AsRef<str>, T: AsRef<str>>(&mut self, key: S, value: T) -> impl Future<Output = ()>;
fn remove<T: AsRef<str>>(&mut self, key: T) -> impl Future<Output = ()>;
}
#[derive(Debug, Default, Clone)]
pub struct LocalStorage {
local_storage: Option<Storage>,
}
impl LocalStorage {
pub fn new(local_storage: Option<Storage>) -> Self {
LocalStorage { local_storage }
}
fn get_local_storage(&self) -> Result<Storage, JsValue> {
if let Some(local_storage) = self.local_storage.clone() {
return Ok(local_storage);
}
if let Some(window) = web_sys::window() {
let local_storage = window.local_storage()?;
local_storage.ok_or("Could not find local storage.".into())
} else {
Err("No window found".into())
}
}
}
impl AuthClientStorage for LocalStorage {
async fn get<T: AsRef<str>>(&mut self, key: T) -> Option<StoredKey> {
let local_storage = self.get_local_storage().unwrap();
let key = format!("{}{}", LOCAL_STORAGE_PREFIX, key.as_ref());
let value = local_storage.get_item(&key).unwrap();
value.map(StoredKey::String)
}
async fn set<S: AsRef<str>, T: AsRef<str>>(&mut self, key: S, value: T) {
let local_storage = self.get_local_storage().unwrap();
let key = format!("{}{}", LOCAL_STORAGE_PREFIX, key.as_ref());
local_storage.set_item(&key, value.as_ref()).unwrap();
}
async fn remove<T: AsRef<str>>(&mut self, key: T) {
let local_storage = self.get_local_storage().unwrap();
let key = format!("{}{}", LOCAL_STORAGE_PREFIX, key.as_ref());
local_storage.remove_item(&key).unwrap();
}
}
#[derive(Debug, Clone)]
pub enum AuthClientStorageType {
LocalStorage(Rc<RefCell<LocalStorage>>),
}
impl Default for AuthClientStorageType {
fn default() -> Self {
AuthClientStorageType::LocalStorage(Rc::new(RefCell::new(LocalStorage::new(None))))
}
}
impl AuthClientStorage for AuthClientStorageType {
async fn get<T: AsRef<str>>(&mut self, key: T) -> Option<StoredKey> {
match self {
AuthClientStorageType::LocalStorage(storage) => {
let mut storage = storage.borrow_mut().clone();
storage.get(key).await
}
}
}
async fn set<S: AsRef<str>, T: AsRef<str>>(&mut self, key: S, value: T) {
match self {
AuthClientStorageType::LocalStorage(storage) => {
let mut storage = storage.borrow_mut().clone();
storage.set(key, value).await
}
}
}
async fn remove<T: AsRef<str>>(&mut self, key: T) {
match self {
AuthClientStorageType::LocalStorage(storage) => {
let mut storage = storage.borrow_mut().clone();
storage.remove(key).await
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
async fn test_local_storage() {
let mut storage = LocalStorage::default();
storage.set("test", "value").await;
let value = storage.get("test").await.unwrap();
assert_eq!(value, StoredKey::String("value".to_string()));
storage.remove("test").await;
let value = storage.get("test").await;
assert_eq!(value, None);
}
#[wasm_bindgen_test]
async fn test_auth_client_storage_type() {
let mut storage = AuthClientStorageType::LocalStorage(Rc::new(RefCell::new(LocalStorage::default())));
storage.set("test", "value").await;
let value = storage.get("test").await.unwrap();
assert_eq!(value, StoredKey::String("value".to_string()));
storage.remove("test").await;
let value = storage.get("test").await;
assert_eq!(value, None);
}
}