lb_rs/service/
account.rs

1use crate::model::account::{Account, MAX_USERNAME_LENGTH};
2use crate::model::api::{
3    DeleteAccountRequest, GetPublicKeyRequest, GetUsernameRequest, NewAccountRequestV2,
4};
5use crate::model::errors::{LbErrKind, LbResult, core_err_unexpected};
6use crate::model::file_like::FileLike;
7use crate::model::file_metadata::{FileType, Owner};
8use crate::model::meta::Meta;
9use crate::{DEFAULT_API_LOCATION, Lb};
10use libsecp256k1::SecretKey;
11use qrcode_generator::QrCodeEcc;
12
13use crate::io::network::ApiError;
14
15impl Lb {
16    /// CoreError::AccountExists,
17    /// CoreError::UsernameTaken,
18    /// CoreError::UsernameInvalid,
19    /// CoreError::ServerDisabled,
20    /// CoreError::ServerUnreachable,
21    /// CoreError::ClientUpdateRequired,
22    #[instrument(level = "debug", skip(self), err(Debug))]
23    pub async fn create_account(
24        &self, username: &str, api_url: &str, welcome_doc: bool,
25    ) -> LbResult<Account> {
26        let username = String::from(username).to_lowercase();
27
28        if username.len() > MAX_USERNAME_LENGTH {
29            return Err(LbErrKind::UsernameInvalid.into());
30        }
31
32        let mut tx = self.begin_tx().await;
33        let db = tx.db();
34
35        if db.account.get().is_some() {
36            return Err(LbErrKind::AccountExists.into());
37        }
38
39        let account = Account::new(username.clone(), api_url.to_string());
40
41        let root = Meta::create_root(&account)?.sign_with(&account)?;
42        let root_id = *root.id();
43
44        let last_synced = self
45            .client
46            .request(&account, NewAccountRequestV2::new(&account, &root))
47            .await?
48            .last_synced;
49
50        db.account.insert(account.clone())?;
51        db.base_metadata.insert(root_id, root)?;
52        db.last_synced.insert(last_synced as i64)?;
53        db.root.insert(root_id)?;
54        db.pub_key_lookup
55            .insert(Owner(account.public_key()), account.username.clone())?;
56
57        self.keychain.cache_account(account.clone()).await?;
58
59        tx.end();
60
61        self.events.meta_changed();
62
63        if welcome_doc {
64            let welcome_doc = self
65                .create_file("welcome.md", &root_id, FileType::Document)
66                .await?;
67            self.write_document(welcome_doc.id, &Self::welcome_message(&username))
68                .await?;
69            self.sync(None).await?;
70        }
71
72        Ok(account)
73    }
74
75    #[instrument(level = "debug", skip(self, key), err(Debug))]
76    pub async fn import_account(&self, key: &str, api_url: Option<&str>) -> LbResult<Account> {
77        if self.get_account().is_ok() {
78            warn!("tried to import an account, but account exists already.");
79            return Err(LbErrKind::AccountExists.into());
80        }
81
82        if let Ok(key) = base64::decode(key) {
83            if let Ok(account) = bincode::deserialize(&key[..]) {
84                return self.import_account_private_key_v1(account).await;
85            } else if let Ok(key) = SecretKey::parse_slice(&key) {
86                return self
87                    .import_account_private_key_v2(key, api_url.unwrap_or(DEFAULT_API_LOCATION))
88                    .await;
89            }
90        }
91
92        let phrase: [&str; 24] = key
93            .split([' ', ','])
94            .filter(|maybe_word| !maybe_word.is_empty())
95            .collect::<Vec<_>>()
96            .try_into()
97            .map_err(|_| LbErrKind::AccountStringCorrupted)?;
98
99        self.import_account_phrase(phrase, api_url.unwrap_or(DEFAULT_API_LOCATION))
100            .await
101    }
102
103    pub async fn import_account_private_key_v1(&self, account: Account) -> LbResult<Account> {
104        let server_public_key = self
105            .client
106            .request(&account, GetPublicKeyRequest { username: account.username.clone() })
107            .await?
108            .key;
109
110        let account_public_key = account.public_key();
111
112        if account_public_key != server_public_key {
113            return Err(LbErrKind::UsernamePublicKeyMismatch.into());
114        }
115
116        let mut tx = self.begin_tx().await;
117        let db = tx.db();
118        db.account.insert(account.clone())?;
119        self.keychain.cache_account(account.clone()).await?;
120
121        Ok(account)
122    }
123
124    pub async fn import_account_private_key_v2(
125        &self, private_key: SecretKey, api_url: &str,
126    ) -> LbResult<Account> {
127        let mut account =
128            Account { username: "".to_string(), api_url: api_url.to_string(), private_key };
129        let public_key = account.public_key();
130
131        account.username = self
132            .client
133            .request(&account, GetUsernameRequest { key: public_key })
134            .await?
135            .username;
136
137        let mut tx = self.begin_tx().await;
138        let db = tx.db();
139        db.account.insert(account.clone())?;
140        self.keychain.cache_account(account.clone()).await?;
141
142        Ok(account)
143    }
144
145    pub async fn import_account_phrase(
146        &self, phrase: [&str; 24], api_url: &str,
147    ) -> LbResult<Account> {
148        let private_key = Account::phrase_to_private_key(phrase)?;
149        self.import_account_private_key_v2(private_key, api_url)
150            .await
151    }
152
153    #[instrument(level = "debug", skip(self), err(Debug))]
154    pub fn export_account_private_key(&self) -> LbResult<String> {
155        self.export_account_private_key_v1()
156    }
157
158    pub(crate) fn export_account_private_key_v1(&self) -> LbResult<String> {
159        let account = self.get_account()?;
160        let encoded: Vec<u8> = bincode::serialize(account).map_err(core_err_unexpected)?;
161        Ok(base64::encode(encoded))
162    }
163
164    #[allow(dead_code)]
165    pub(crate) fn export_account_private_key_v2(&self) -> LbResult<String> {
166        let account = self.get_account()?;
167        Ok(base64::encode(account.private_key.serialize()))
168    }
169
170    pub fn export_account_phrase(&self) -> LbResult<String> {
171        let account = self.get_account()?;
172        Ok(account.get_phrase()?.join(" "))
173    }
174
175    pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
176        let acct_secret = self.export_account_private_key_v1()?;
177        qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
178            .map_err(|err| core_err_unexpected(err).into())
179    }
180
181    #[instrument(level = "debug", skip(self), err(Debug))]
182    pub async fn delete_account(&self) -> LbResult<()> {
183        let account = self.get_account()?;
184
185        self.client
186            .request(account, DeleteAccountRequest {})
187            .await
188            .map_err(|err| match err {
189                ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
190                ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
191                _ => core_err_unexpected(err),
192            })?;
193
194        let mut tx = self.begin_tx().await;
195        let db = tx.db();
196
197        db.account.clear()?;
198        db.last_synced.clear()?;
199        db.base_metadata.clear()?;
200        db.root.clear()?;
201        db.local_metadata.clear()?;
202        db.pub_key_lookup.clear()?;
203
204        // todo: clear cache?
205
206        Ok(())
207    }
208
209    fn welcome_message(username: &str) -> Vec<u8> {
210        format!(r#"# Hello {username}
211
212Welcome to Lockbook! This is an example note to help you get started with our note editor. You can keep it to use as a cheat sheet or delete it anytime.
213
214Lockbook uses Markdown, a lightweight language for formatting plain text. You can use all our supported formatting just by typing. Here’s how it works:
215
216# This is a heading
217
218## This is a smaller heading
219
220### This is an even smaller heading
221
222###### Headings have 6 levels
223
224For italic, use single *asterisks* or _underscores_.
225
226For bold, use double **asterisks** or __underscores__.
227
228For inline code, use single `backticks`
229
230For code blocks, use
231```
232triple
233backticks
234```
235
236>For block quotes,
237use a greater-than sign
238
239Bulleted list items
240* start
241* with
242* asterisks
243- or
244- hyphens
245+ or
246+ plus
247+ signs
248
249Numbered list items
2501. start
2512. with
2523. numbers
2534. and
2545. periods
255
256Happy note taking! You can report any issues to our [Github project](https://github.com/lockbook/lockbook/issues/new) or join our [Discord server](https://discord.gg/qv9fmAZCm6)."#).into()
257    }
258}