mod ops;
use rustolio_rpc::prelude::*;
use rustolio_utils::{
bytes::{encoding::encode_to_bytes, hex},
crypto,
prelude::*,
};
use rustolio_web::prelude::*;
use crate::Value;
const DB_USERNAME: &str = "DB_USERNAME";
const DB_PRIVATE_KEY: &str = "DB_PRIVATE_KEY";
const DB_ENCRYPTION_KEY: &str = "DB_ENCRYPTION_KEY";
const DB_DECAPSULATION_KEY: &str = "DB_DECAPSULATION_KEY";
global! {
static CLIENT: GlobalSignal<Option<Client>> = GlobalSignal::new(None);
Effect::new(|| {
tracing::info!("initializing DB-Client...");
Client::init().unwrap();
});
}
#[derive(Clone)]
pub struct Client {
username: String,
private_key: crypto::signature::PrivateKey,
encryption_cipher: crypto::encryption::Cipher,
decapsulation_key: crypto::encapsulation::DecapsulationKey,
}
impl Client {
fn init() -> rustolio_utils::Result<()> {
let storage = storage()?;
let Some(username) = storage
.get_item(DB_USERNAME)
.context("Failed to get item from LocalStorage")?
else {
return Ok(());
};
let Some(private_key) = storage
.get_item(DB_PRIVATE_KEY)
.context("Failed to get item from LocalStorage")?
else {
return Ok(());
};
let Some(encryption_key) = storage
.get_item(DB_ENCRYPTION_KEY)
.context("Failed to get item from LocalStorage")?
else {
return Ok(());
};
let Some(decapsulation_key) = storage
.get_item(DB_DECAPSULATION_KEY)
.context("Failed to get item from LocalStorage")?
else {
return Ok(());
};
let private_key = hex::decode(&private_key).context("Failed to decode private key")?;
let encryption_key =
hex::decode(&encryption_key).context("Failed to decode encryption key")?;
let decapsulation_key =
hex::decode(&decapsulation_key).context("Failed to decode decapsulation key")?;
let private_key = crypto::signature::PrivateKey::from_bytes(private_key)
.context("Failed to construct key")?;
let encryption_key = crypto::encryption::Key::from_bytes(&encryption_key)
.context("Failed to construct key")?;
let decapsulation_key =
crypto::encapsulation::DecapsulationKey::from_bytes(decapsulation_key)
.context("Failed to construct key")?;
CLIENT.update(|c| {
*c = Some(Client {
username,
private_key,
encryption_cipher: crypto::encryption::Cipher::new(encryption_key),
decapsulation_key,
});
});
Ok(())
}
pub async fn create(username: &str, password: &str) -> rustolio_utils::Result<()> {
let private_key =
crypto::signature::PrivateKey::generate().context("Failed to create private key")?;
let encryption_key =
crypto::encryption::Key::generate().context("Failed to create encryption key")?;
let decapsulation_key = crypto::encapsulation::DecapsulationKey::generate()
.context("Failed to create decapsulation key")?;
let (encrypted_private_key, encrypted_encryption_key, encrypted_decapsulation_key) = {
let password_hash = crypto::hash::Hasher::once_raw(password.as_bytes());
let password_key = crypto::encryption::Key::from_bytes(password_hash.as_ref())
.context("Failed to create encryption key from password")?;
let cipher = crypto::encryption::Cipher::new(password_key);
(
cipher
.encrypt(private_key.to_bytes())
.context("Failed to encrypt private key")?,
cipher
.encrypt(encryption_key.to_bytes())
.context("Failed to encrypt encryption key")?,
cipher
.encrypt(decapsulation_key.to_bytes())
.context("Failed to encrypt decapsulation key")?,
)
};
{
let storage = storage()?;
let private_key = hex::encode(private_key.to_bytes());
let encryption_key = hex::encode(encryption_key.to_bytes());
let decapsulation_key = hex::encode(decapsulation_key.to_bytes());
storage.set_item(DB_USERNAME, username)?;
storage.set_item(DB_PRIVATE_KEY, &private_key)?;
storage.set_item(DB_ENCRYPTION_KEY, &encryption_key)?;
storage.set_item(DB_DECAPSULATION_KEY, &decapsulation_key)?;
}
let encapsulation_key = decapsulation_key.encapsulation_key().to_bytes();
let client = Client {
username: username.to_string(),
private_key,
encryption_cipher: crypto::encryption::Cipher::new(&encryption_key),
decapsulation_key,
};
let encrypted_client = EncryptedClient {
encrypted_decapsulation_key,
encrypted_encryption_key,
encrypted_private_key,
};
client
.set(&["username", username], &encrypted_client)
.await
.context("Failed to create new user")?;
client
.set(&["shared", username], &encapsulation_key)
.await
.context("Failed to create shared user")?;
CLIENT.update(|c| {
*c = Some(client);
});
Ok(())
}
pub async fn login(username: &str, password: &str) -> rustolio_utils::Result<()> {
let EncryptedClient {
encrypted_private_key,
encrypted_encryption_key,
encrypted_decapsulation_key,
} = ops::get(
encode_to_bytes(&["username", username]).context("Failed to encode username")?,
)
.await
.context("Failed to load user")?
.context("User does not exists")?
.into_value()?;
let (private_key, encryption_key, decapsulation_key) = {
let password_hash = crypto::hash::Hasher::once_raw(password.as_bytes());
let password_key = crypto::encryption::Key::from_bytes(password_hash.as_ref())
.context("Failed to create encryption key from password")?;
let cipher = crypto::encryption::Cipher::new(password_key);
(
cipher
.decrypt(&encrypted_private_key)
.context("Failed to decrypt private key")?,
cipher
.decrypt(&encrypted_encryption_key)
.context("Failed to decrypt encryption key")?,
cipher
.decrypt(&encrypted_decapsulation_key)
.context("Failed to decrypt decapsulation key")?,
)
};
let private_key = crypto::signature::PrivateKey::from_bytes(private_key)
.context("Failed to construct key")?;
let encryption_key = crypto::encryption::Key::from_bytes(&encryption_key)
.context("Failed to construct key")?;
let decapsulation_key =
crypto::encapsulation::DecapsulationKey::from_bytes(decapsulation_key)
.context("Failed to construct key")?;
{
let storage = storage()?;
let private_key = hex::encode(private_key.to_bytes());
let encryption_key = hex::encode(encryption_key.to_bytes());
let decapsulation_key = hex::encode(decapsulation_key.to_bytes());
storage.set_item(DB_USERNAME, username)?;
storage.set_item(DB_PRIVATE_KEY, &private_key)?;
storage.set_item(DB_ENCRYPTION_KEY, &encryption_key)?;
storage.set_item(DB_DECAPSULATION_KEY, &decapsulation_key)?;
}
let client = Client {
username: username.to_string(),
private_key,
encryption_cipher: crypto::encryption::Cipher::new(encryption_key),
decapsulation_key,
};
CLIENT.update(|c| {
*c = Some(client);
});
Ok(())
}
pub fn logout() -> rustolio_utils::Result<()> {
let storage = storage()?;
storage.delete(DB_USERNAME)?;
storage.delete(DB_PRIVATE_KEY)?;
storage.delete(DB_ENCRYPTION_KEY)?;
storage.delete(DB_DECAPSULATION_KEY)?;
CLIENT.update(|c| {
*c = None;
});
Ok(())
}
pub fn local() -> Option<Self> {
CLIENT.value()
}
pub async fn get(&self, key: &impl Encode) -> Result<Option<Value>, ServerFnError<String>> {
let key = encode_to_bytes(key).unwrap();
ops::get(key).await
}
pub async fn set(
&self,
key: &impl Encode,
value: &impl Encode,
) -> Result<(), ServerFnError<String>> {
let key = encode_to_bytes(key).unwrap();
let value = Value::from_value(value).unwrap();
ops::set((key, value)).await
}
pub async fn secure_get(
&self,
key: &impl Encode,
) -> Result<Option<Value>, ServerFnError<String>> {
let key = encode_to_bytes(key).unwrap();
let signed_key = self.private_key.sign(key);
ops::secure_get(signed_key).await
}
pub async fn secure_set(
&self,
key: &impl Encode,
value: &impl Encode,
) -> Result<(), ServerFnError<String>> {
let key = encode_to_bytes(key).unwrap();
let value = Value::from_value(value).unwrap();
let signed_msg = self.private_key.sign((key, value));
ops::secure_set(signed_msg).await
}
}
fn storage() -> rustolio_utils::Result<web_sys::Storage> {
web_sys::window()
.context("No window available")?
.local_storage()?
.context("No storage available")
}
#[derive(Debug, Clone, Encode, Decode)]
struct EncryptedClient {
encrypted_private_key: crypto::encryption::Encypted,
encrypted_encryption_key: crypto::encryption::Encypted,
encrypted_decapsulation_key: crypto::encryption::Encypted,
}