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.as_bytes())
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    const WELCOME_MESSAGE: &'static str = r#"# Markdown Syntax
210Markdown is a language for easily formatting your documents. This document can help you get started.
211
212## Styled Text
213To style text, wrap your text in the corresponding characters.
214| Style         | Syntax              | Example           |
215|---------------|---------------------|-------------------|
216| emphasis      | `*emphasis*`        | *emphasis*        |
217| strong        | `**strong**`        | **strong**        |
218| strikethrough | `~~strikethrough~~` | ~~strikethrough~~ |
219| underline     | `__underline__`     | __underline__     |
220| code          | ``code``            | `code`            |
221| spoiler       | `||spoiler||`       | ||spoiler||       |
222| superscript   | `^superscript^`     | ^superscript^     |
223| subscript     | `~subscript~`       | ~subscript~       |
224
225## Links
226To make text into a link, wrap it with `[` `]`, add a link destination to the end , and wrap the destination with `(` `)`. The link destination can be a web URL or a relative path to another Lockbook file.
227```md
228[Lockbook's website](https://lockbook.net)
229```
230> [Lockbook's website](https://lockbook.net)
231
232## Images
233To embed an image, add a `!` to the beginning of the link syntax.
234```md
235![Lockbook's favicon](https://lockbook.net/favicon/favicon-96x96.png)
236```
237> ![Lockbook's favicon](https://lockbook.net/favicon/favicon-96x96.png)
238
239## Headings
240To create a heading, add up to six `#`'s plus a space before your text. More `#`'s create a smaller heading.
241```md
242# Heading 1
243## Heading 2
244### Heading 3
245#### Heading 4
246##### Heading 5
247###### Heading 6
248```
249> # Heading 1
250> ## Heading 2
251> ### Heading 3
252> #### Heading 4
253> ##### Heading 5
254> ###### Heading 6
255
256You can also create a heading using an underline composed of `=`'s (Heading 1) or `-`'s (Heading 2) on the following line.
257```md
258Heading 1
259===
260Heading 2
261---
262```
263> Heading 1
264> ===
265> Heading 2
266> ---
267
268## Lists
269Create a list item by adding `- `, `+ `, or `* ` for a bulleted list, `1. ` for a numbered list, or `- [ ] `, `+ [ ] `, or `* [ ] ` for a task list at the start of the line. The added characters are called the *list marker*.
270```md
271* bulleted list item
272- bulleted list item
273+ bulleted list item
274
2751. numbered list item
2761. numbered list item
2771. numbered list item
278
279- [ ] task list item
280- [x] task list item
281```
282>* bulleted list item
283>- bulleted list item
284>+ bulleted list item
285>
286>1. numbered list item
287>1. numbered list item
288>1. numbered list item
289>
290>- [ ] task list item
291>- [x] task list item
292
293List items can be nested. To nest an inner item in an outer one, the inner item's line must start with at least one space for each character in the outer item's list marker: usually 2 for bulleted lists, 3 for numbered lists, or 2 for tasks lists (the trailing `[ ] ` is excluded).
294```md
295* This is a bulleted list
296    * An inner item needs at least 2 spaces
2971. This is a numbered list
298    1. An inner item needs at least 3 spaces
299* [ ] This is a task list
300    * [ ] An inner item needs at least 2 spaces
301```
302> * This is a bulleted list
303>   * An inner item needs at least 2 spaces
304> 1. This is a numbered list
305>    1. An inner item needs at least 3 spaces
306> * [ ] This is a task list
307>   * [ ] An inner item needs at least 2 spaces
308
309List items can contain formatted content. For non-text content, each line must start with the same number of spaces as an inner list item would.
310```md
311* This item contains text,
312    > a quote
313    ### and a heading.
314* This item contains two lines of text.
315The second line doesn't need spaces.
316```
317> * This item contains text,
318>   > a quote
319>   ### and a heading.
320> * This item contains two lines of text.
321> The second line doesn't need spaces.
322
323## Quotes
324To create a block quote, add `> ` to each line.
325```md
326> This is a quote
327```
328> This is a quote
329
330Like list items, block quotes can contain formatted content.
331```md
332> This quote contains some text,
333> ```rust
334> // some code
335> fn main() { println!("Hello, world!"); }
336> ```
337> ### and a heading.
338
339> This quote contains two lines of text.
340The second line doesn't need added characters.
341```
342> This quote contains some text,
343> ```rust
344> // some code
345> fn main() { println!("Hello, world!"); }
346> ```
347> ### and a heading.
348
349> This quote contains two lines of text.
350The second line doesn't need added characters.
351
352## Alerts
353To create an alert, add one of 5 tags to the first line of a quote: `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!CAUTION]`. An alternate title can be added after the tag.
354```md
355> [!NOTE]
356> This is a note.
357
358> [!TIP]
359> This is a tip.
360
361> [!IMPORTANT]
362> This is important.
363
364> [!WARNING]
365> This is a warning.
366
367> [!CAUTION] Caution!!!!!
368> This is a caution.
369```
370> [!NOTE]
371> This is a note.
372
373> [!TIP]
374> This is a tip.
375
376> [!IMPORTANT]
377> This is important.
378
379> [!WARNING]
380> This is a warning.
381
382> [!CAUTION] Caution!!!!!
383> This is a caution.
384
385## Tables
386A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
387```md
388| Style         | Syntax              | Example           |
389|---------------|---------------------|-------------------|
390| emphasis      | `*emphasis*`        | *emphasis*        |
391| strong        | `**strong**`        | **strong**        |
392```
393> | Style         | Syntax              | Example           |
394> |---------------|---------------------|-------------------|
395> | emphasis      | `*emphasis*`        | *emphasis*        |
396> | strong        | `**strong**`        | **strong**        |
397
398## Code
399A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
400```md
401    ```rust
402    // some code
403    fn main() { println!("Hello, world!"); }
404    ```
405```
406> ```rust
407> // some code
408> fn main() { println!("Hello, world!"); }
409> ```
410
411You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
412```md
413    // some code
414    fn main() { println!("Hello, world!"); }
415```
416>     // some code
417>     fn main() { println!("Hello, world!"); }
418
419## Thematic Breaks
420A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
421```md
422***
423---
424___
425```
426> ***
427> ---
428> ___
429"#;
430}