Skip to main content

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
256## Lists
257Create 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*.
258```md
259* bulleted list item
260- bulleted list item
261+ bulleted list item
262
2631. numbered list item
2641. numbered list item
2651. numbered list item
266
267- [ ] task list item
268- [x] task list item
269```
270>* bulleted list item
271>- bulleted list item
272>+ bulleted list item
273>
274>1. numbered list item
275>1. numbered list item
276>1. numbered list item
277>
278>- [ ] task list item
279>- [x] task list item
280
281List 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).
282```md
283* This is a bulleted list
284    * An inner item needs at least 2 spaces
2851. This is a numbered list
286    1. An inner item needs at least 3 spaces
287* [ ] This is a task list
288    * [ ] An inner item needs at least 2 spaces
289```
290> * This is a bulleted list
291>   * An inner item needs at least 2 spaces
292> 1. This is a numbered list
293>    1. An inner item needs at least 3 spaces
294> * [ ] This is a task list
295>   * [ ] An inner item needs at least 2 spaces
296
297List 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.
298```md
299* This item contains text,
300    > a quote
301    ### and a heading.
302* This item contains two lines of text.
303The second line doesn't need spaces.
304```
305> * This item contains text,
306>   > a quote
307>   ### and a heading.
308> * This item contains two lines of text.
309> The second line doesn't need spaces.
310
311## Quotes
312To create a block quote, add `> ` to each line.
313```md
314> This is a quote
315```
316> This is a quote
317
318Like list items, block quotes can contain formatted content.
319```md
320> This quote contains some text,
321> ```rust
322> // some code
323> fn main() { println!("Hello, world!"); }
324> ```
325> ### and a heading.
326
327> This quote contains two lines of text.
328The second line doesn't need added characters.
329```
330> This quote contains some text,
331> ```rust
332> // some code
333> fn main() { println!("Hello, world!"); }
334> ```
335> ### and a heading.
336
337> This quote contains two lines of text.
338The second line doesn't need added characters.
339
340## Alerts
341To 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.
342```md
343> [!NOTE]
344> This is a note.
345
346> [!TIP]
347> This is a tip.
348
349> [!IMPORTANT]
350> This is important.
351
352> [!WARNING]
353> This is a warning.
354
355> [!CAUTION] Caution!!!!!
356> This is a caution.
357```
358> [!NOTE]
359> This is a note.
360
361> [!TIP]
362> This is a tip.
363
364> [!IMPORTANT]
365> This is important.
366
367> [!WARNING]
368> This is a warning.
369
370> [!CAUTION] Caution!!!!!
371> This is a caution.
372
373## Tables
374A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
375```md
376| Style         | Syntax              | Example           |
377|---------------|---------------------|-------------------|
378| emphasis      | `*emphasis*`        | *emphasis*        |
379| strong        | `**strong**`        | **strong**        |
380```
381> | Style         | Syntax              | Example           |
382> |---------------|---------------------|-------------------|
383> | emphasis      | `*emphasis*`        | *emphasis*        |
384> | strong        | `**strong**`        | **strong**        |
385
386## Code
387A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
388```md
389    ```rust
390    // some code
391    fn main() { println!("Hello, world!"); }
392    ```
393```
394> ```rust
395> // some code
396> fn main() { println!("Hello, world!"); }
397> ```
398
399You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
400```md
401    // some code
402    fn main() { println!("Hello, world!"); }
403```
404>     // some code
405>     fn main() { println!("Hello, world!"); }
406
407## Thematic Breaks
408A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
409```md
410***
411---
412___
413```
414> ***
415> ---
416> ___
417"#;
418}