Skip to main content

rustolio_db/client/
mod.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11mod ops;
12// mod teams;
13
14use 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
24// pub use teams::Teams;
25
26const 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        // Teams::logout()?;
238        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}