rustolio_db/client/
mod.rs1mod ops;
12use rustolio_rpc::prelude::*;
15use rustolio_utils::{
16 bytes::{encoding::encode_to_bytes, hex},
17 crypto,
18 prelude::*,
19};
20use rustolio_web::prelude::*;
21
22use crate::Value;
23
24const DB_USERNAME: &str = "DB_USERNAME";
27const DB_PRIVATE_KEY: &str = "DB_PRIVATE_KEY";
28const DB_ENCRYPTION_KEY: &str = "DB_ENCRYPTION_KEY";
29const DB_DECAPSULATION_KEY: &str = "DB_DECAPSULATION_KEY";
30
31global! {
32 static CLIENT: GlobalSignal<Option<Client>> = GlobalSignal::new(None);
33 Effect::new(|| {
34 tracing::info!("initializing DB-Client...");
35 Client::init().unwrap();
36 });
37}
38
39#[derive(Clone)]
40pub struct Client {
41 username: String,
42 private_key: crypto::signature::PrivateKey,
43 encryption_cipher: crypto::encryption::Cipher,
44 decapsulation_key: crypto::encapsulation::DecapsulationKey,
45}
46
47impl Client {
48 fn init() -> rustolio_utils::Result<()> {
49 let storage = storage()?;
50 let Some(username) = storage
51 .get_item(DB_USERNAME)
52 .context("Failed to get item from LocalStorage")?
53 else {
54 return Ok(());
55 };
56 let Some(private_key) = storage
57 .get_item(DB_PRIVATE_KEY)
58 .context("Failed to get item from LocalStorage")?
59 else {
60 return Ok(());
61 };
62 let Some(encryption_key) = storage
63 .get_item(DB_ENCRYPTION_KEY)
64 .context("Failed to get item from LocalStorage")?
65 else {
66 return Ok(());
67 };
68 let Some(decapsulation_key) = storage
69 .get_item(DB_DECAPSULATION_KEY)
70 .context("Failed to get item from LocalStorage")?
71 else {
72 return Ok(());
73 };
74
75 let private_key = hex::decode(&private_key).context("Failed to decode private key")?;
76 let encryption_key =
77 hex::decode(&encryption_key).context("Failed to decode encryption key")?;
78 let decapsulation_key =
79 hex::decode(&decapsulation_key).context("Failed to decode decapsulation key")?;
80
81 let private_key = crypto::signature::PrivateKey::from_bytes(private_key)
82 .context("Failed to construct key")?;
83 let encryption_key = crypto::encryption::Key::from_bytes(&encryption_key)
84 .context("Failed to construct key")?;
85 let decapsulation_key =
86 crypto::encapsulation::DecapsulationKey::from_bytes(decapsulation_key)
87 .context("Failed to construct key")?;
88
89 CLIENT.update(|c| {
90 *c = Some(Client {
91 username,
92 private_key,
93 encryption_cipher: crypto::encryption::Cipher::new(encryption_key),
94 decapsulation_key,
95 });
96 });
97
98 Ok(())
99 }
100
101 pub async fn create(username: &str, password: &str) -> rustolio_utils::Result<()> {
102 let private_key =
103 crypto::signature::PrivateKey::generate().context("Failed to create private key")?;
104 let encryption_key =
105 crypto::encryption::Key::generate().context("Failed to create encryption key")?;
106 let decapsulation_key = crypto::encapsulation::DecapsulationKey::generate()
107 .context("Failed to create decapsulation key")?;
108
109 let (encrypted_private_key, encrypted_encryption_key, encrypted_decapsulation_key) = {
110 let password_hash = crypto::hash::Hasher::once_raw(password.as_bytes());
111 let password_key = crypto::encryption::Key::from_bytes(password_hash.as_ref())
112 .context("Failed to create encryption key from password")?;
113 let cipher = crypto::encryption::Cipher::new(password_key);
114 (
115 cipher
116 .encrypt(private_key.to_bytes())
117 .context("Failed to encrypt private key")?,
118 cipher
119 .encrypt(encryption_key.to_bytes())
120 .context("Failed to encrypt encryption key")?,
121 cipher
122 .encrypt(decapsulation_key.to_bytes())
123 .context("Failed to encrypt decapsulation key")?,
124 )
125 };
126
127 {
128 let storage = storage()?;
129
130 let private_key = hex::encode(private_key.to_bytes());
131 let encryption_key = hex::encode(encryption_key.to_bytes());
132 let decapsulation_key = hex::encode(decapsulation_key.to_bytes());
133
134 storage.set_item(DB_USERNAME, username)?;
135 storage.set_item(DB_PRIVATE_KEY, &private_key)?;
136 storage.set_item(DB_ENCRYPTION_KEY, &encryption_key)?;
137 storage.set_item(DB_DECAPSULATION_KEY, &decapsulation_key)?;
138 }
139
140 let encapsulation_key = decapsulation_key.encapsulation_key().to_bytes();
141 let client = Client {
142 username: username.to_string(),
143 private_key,
144 encryption_cipher: crypto::encryption::Cipher::new(&encryption_key),
145 decapsulation_key,
146 };
147
148 let encrypted_client = EncryptedClient {
149 encrypted_decapsulation_key,
150 encrypted_encryption_key,
151 encrypted_private_key,
152 };
153 client
154 .set(&["username", username], &encrypted_client)
155 .await
156 .context("Failed to create new user")?;
157 client
158 .set(&["shared", username], &encapsulation_key)
159 .await
160 .context("Failed to create shared user")?;
161
162 CLIENT.update(|c| {
163 *c = Some(client);
164 });
165
166 Ok(())
167 }
168
169 pub async fn login(username: &str, password: &str) -> rustolio_utils::Result<()> {
170 let EncryptedClient {
171 encrypted_private_key,
172 encrypted_encryption_key,
173 encrypted_decapsulation_key,
174 } = ops::get(
175 encode_to_bytes(&["username", username]).context("Failed to encode username")?,
176 )
177 .await
178 .context("Failed to load user")?
179 .context("User does not exists")?
180 .into_value()?;
181
182 let (private_key, encryption_key, decapsulation_key) = {
183 let password_hash = crypto::hash::Hasher::once_raw(password.as_bytes());
184 let password_key = crypto::encryption::Key::from_bytes(password_hash.as_ref())
185 .context("Failed to create encryption key from password")?;
186 let cipher = crypto::encryption::Cipher::new(password_key);
187 (
188 cipher
189 .decrypt(&encrypted_private_key)
190 .context("Failed to decrypt private key")?,
191 cipher
192 .decrypt(&encrypted_encryption_key)
193 .context("Failed to decrypt encryption key")?,
194 cipher
195 .decrypt(&encrypted_decapsulation_key)
196 .context("Failed to decrypt decapsulation key")?,
197 )
198 };
199
200 let private_key = crypto::signature::PrivateKey::from_bytes(private_key)
201 .context("Failed to construct key")?;
202 let encryption_key = crypto::encryption::Key::from_bytes(&encryption_key)
203 .context("Failed to construct key")?;
204 let decapsulation_key =
205 crypto::encapsulation::DecapsulationKey::from_bytes(decapsulation_key)
206 .context("Failed to construct key")?;
207
208 {
209 let storage = storage()?;
210
211 let private_key = hex::encode(private_key.to_bytes());
212 let encryption_key = hex::encode(encryption_key.to_bytes());
213 let decapsulation_key = hex::encode(decapsulation_key.to_bytes());
214
215 storage.set_item(DB_USERNAME, username)?;
216 storage.set_item(DB_PRIVATE_KEY, &private_key)?;
217 storage.set_item(DB_ENCRYPTION_KEY, &encryption_key)?;
218 storage.set_item(DB_DECAPSULATION_KEY, &decapsulation_key)?;
219 }
220
221 let client = Client {
222 username: username.to_string(),
223 private_key,
224 encryption_cipher: crypto::encryption::Cipher::new(encryption_key),
225 decapsulation_key,
226 };
227
228 CLIENT.update(|c| {
229 *c = Some(client);
230 });
231
232 Ok(())
233 }
234
235 pub fn logout() -> rustolio_utils::Result<()> {
236 let storage = storage()?;
237 storage.delete(DB_USERNAME)?;
239 storage.delete(DB_PRIVATE_KEY)?;
240 storage.delete(DB_ENCRYPTION_KEY)?;
241 storage.delete(DB_DECAPSULATION_KEY)?;
242
243 CLIENT.update(|c| {
244 *c = None;
245 });
246
247 Ok(())
248 }
249
250 pub fn local() -> Option<Self> {
251 CLIENT.value()
252 }
253
254 pub async fn get(&self, key: &impl Encode) -> Result<Option<Value>, ServerFnError<String>> {
255 let key = encode_to_bytes(key).unwrap();
256 ops::get(key).await
257 }
258
259 pub async fn set(
260 &self,
261 key: &impl Encode,
262 value: &impl Encode,
263 ) -> Result<(), ServerFnError<String>> {
264 let key = encode_to_bytes(key).unwrap();
265 let value = Value::from_value(value).unwrap();
266 ops::set((key, value)).await
267 }
268
269 pub async fn secure_get(
270 &self,
271 key: &impl Encode,
272 ) -> Result<Option<Value>, ServerFnError<String>> {
273 let key = encode_to_bytes(key).unwrap();
274 let signed_key = self.private_key.sign(key);
275 ops::secure_get(signed_key).await
276 }
277
278 pub async fn secure_set(
279 &self,
280 key: &impl Encode,
281 value: &impl Encode,
282 ) -> Result<(), ServerFnError<String>> {
283 let key = encode_to_bytes(key).unwrap();
284 let value = Value::from_value(value).unwrap();
285 let signed_msg = self.private_key.sign((key, value));
286 ops::secure_set(signed_msg).await
287 }
288}
289
290fn storage() -> rustolio_utils::Result<web_sys::Storage> {
291 web_sys::window()
292 .context("No window available")?
293 .local_storage()?
294 .context("No storage available")
295}
296
297#[derive(Debug, Clone, Encode, Decode)]
298struct EncryptedClient {
299 encrypted_private_key: crypto::encryption::Encypted,
300 encrypted_encryption_key: crypto::encryption::Encypted,
301 encrypted_decapsulation_key: crypto::encryption::Encypted,
302}