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::service::events::Actor;
10use crate::{DEFAULT_API_LOCATION, Lb};
11use libsecp256k1::SecretKey;
12use qrcode_generator::QrCodeEcc;
13
14use crate::io::network::ApiError;
15
16impl Lb {
17    /// CoreError::AccountExists,
18    /// CoreError::UsernameTaken,
19    /// CoreError::UsernameInvalid,
20    /// CoreError::ServerDisabled,
21    /// CoreError::ServerUnreachable,
22    /// CoreError::ClientUpdateRequired,
23    #[instrument(level = "debug", skip(self), err(Debug))]
24    pub async fn create_account(
25        &self, username: &str, api_url: &str, welcome_doc: bool,
26    ) -> LbResult<Account> {
27        let username = String::from(username).to_lowercase();
28
29        if username.len() > MAX_USERNAME_LENGTH {
30            return Err(LbErrKind::UsernameInvalid.into());
31        }
32
33        let mut tx = self.begin_tx().await;
34        let db = tx.db();
35
36        if db.account.get().is_some() {
37            return Err(LbErrKind::AccountExists.into());
38        }
39
40        let account = Account::new(username.clone(), api_url.to_string());
41
42        let root = Meta::create_root(&account)?.sign_with(&account)?;
43        let root_id = *root.id();
44
45        let last_synced = self
46            .client
47            .request(&account, NewAccountRequestV2::new(&account, &root))
48            .await?
49            .last_synced;
50
51        db.account.insert(account.clone())?;
52        db.base_metadata.insert(root_id, root)?;
53        db.last_synced.insert(last_synced as i64)?;
54        db.root.insert(root_id)?;
55        db.pub_key_lookup
56            .insert(Owner(account.public_key()), account.username.clone())?;
57
58        self.keychain.cache_account(account.clone()).await?;
59
60        tx.end();
61
62        if welcome_doc {
63            let welcome_doc = self
64                .create_file("welcome.md", &root_id, FileType::Document)
65                .await?;
66            self.write_document(welcome_doc.id, Self::WELCOME_MESSAGE.as_bytes())
67                .await?;
68            self.sync().await?;
69        }
70
71        self.events.meta_changed(Actor::User);
72        self.events.signed_in();
73
74        Ok(account)
75    }
76
77    #[instrument(level = "debug", skip(self, key), err(Debug))]
78    pub async fn import_account(&self, key: &str, api_url: Option<&str>) -> LbResult<Account> {
79        if self.get_account().is_ok() {
80            warn!("tried to import an account, but account exists already.");
81            return Err(LbErrKind::AccountExists.into());
82        }
83
84        if let Ok(key) = base64::decode(key) {
85            if let Ok(account) = bincode::deserialize(&key[..]) {
86                return self.import_account_private_key_v1(account).await;
87            } else if let Ok(key) = SecretKey::parse_slice(&key) {
88                return self
89                    .import_account_private_key_v2(key, api_url.unwrap_or(DEFAULT_API_LOCATION))
90                    .await;
91            }
92        }
93
94        let phrase: [&str; 24] = key
95            .split([' ', ','])
96            .filter(|maybe_word| !maybe_word.is_empty())
97            .collect::<Vec<_>>()
98            .try_into()
99            .map_err(|_| LbErrKind::AccountStringCorrupted)?;
100
101        self.import_account_phrase(phrase, api_url.unwrap_or(DEFAULT_API_LOCATION))
102            .await
103    }
104
105    pub async fn import_account_private_key_v1(&self, account: Account) -> LbResult<Account> {
106        let server_public_key = self
107            .client
108            .request(&account, GetPublicKeyRequest { username: account.username.clone() })
109            .await?
110            .key;
111
112        let account_public_key = account.public_key();
113
114        if account_public_key != server_public_key {
115            return Err(LbErrKind::UsernamePublicKeyMismatch.into());
116        }
117
118        let mut tx = self.begin_tx().await;
119        let db = tx.db();
120        db.account.insert(account.clone())?;
121        self.keychain.cache_account(account.clone()).await?;
122
123        Ok(account)
124    }
125
126    pub async fn import_account_private_key_v2(
127        &self, private_key: SecretKey, api_url: &str,
128    ) -> LbResult<Account> {
129        let mut account =
130            Account { username: "".to_string(), api_url: api_url.to_string(), private_key };
131        let public_key = account.public_key();
132
133        account.username = self
134            .client
135            .request(&account, GetUsernameRequest { key: public_key })
136            .await?
137            .username;
138
139        let mut tx = self.begin_tx().await;
140        let db = tx.db();
141        db.account.insert(account.clone())?;
142        self.keychain.cache_account(account.clone()).await?;
143
144        Ok(account)
145    }
146
147    pub async fn import_account_phrase(
148        &self, phrase: [&str; 24], api_url: &str,
149    ) -> LbResult<Account> {
150        let private_key = Account::phrase_to_private_key(phrase)?;
151        self.import_account_private_key_v2(private_key, api_url)
152            .await
153    }
154
155    #[instrument(level = "debug", skip(self), err(Debug))]
156    pub fn export_account_private_key(&self) -> LbResult<String> {
157        self.export_account_private_key_v2()
158    }
159
160    #[allow(dead_code)]
161    pub(crate) fn export_account_private_key_v1(&self) -> LbResult<String> {
162        let account = self.get_account()?;
163        let encoded: Vec<u8> = bincode::serialize(account).map_err(core_err_unexpected)?;
164        Ok(base64::encode(encoded))
165    }
166
167    pub(crate) fn export_account_private_key_v2(&self) -> LbResult<String> {
168        let account = self.get_account()?;
169        Ok(base64::encode(account.private_key.serialize()))
170    }
171
172    pub fn export_account_phrase(&self) -> LbResult<String> {
173        let account = self.get_account()?;
174        Ok(account.get_phrase()?.join(" "))
175    }
176
177    pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
178        let acct_secret = self.export_account_private_key_v2()?;
179        qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
180            .map_err(|err| core_err_unexpected(err).into())
181    }
182
183    #[instrument(level = "debug", skip(self), err(Debug))]
184    pub async fn delete_account(&self) -> LbResult<()> {
185        let account = self.get_account()?;
186
187        self.client
188            .request(account, DeleteAccountRequest {})
189            .await
190            .map_err(|err| match err {
191                ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
192                ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
193                _ => core_err_unexpected(err),
194            })?;
195
196        let mut tx = self.begin_tx().await;
197        let db = tx.db();
198
199        db.account.clear()?;
200        db.last_synced.clear()?;
201        db.base_metadata.clear()?;
202        db.root.clear()?;
203        db.local_metadata.clear()?;
204        db.pub_key_lookup.clear()?;
205
206        // todo: clear cache?
207
208        Ok(())
209    }
210
211    const WELCOME_MESSAGE: &'static str = r#"# Markdown Syntax
212Markdown is a language for easily formatting your documents. This document can help you get started.
213
214## Styled Text
215To style text, wrap your text in the corresponding characters.
216| Style         | Syntax              | Example           |
217|---------------|---------------------|-------------------|
218| emphasis      | `*emphasis*`        | *emphasis*        |
219| strong        | `**strong**`        | **strong**        |
220| strikethrough | `~~strikethrough~~` | ~~strikethrough~~ |
221| underline     | `__underline__`     | __underline__     |
222| code          | ``code``            | `code`            |
223| highlight     | `==highlight==`     | ==highlight==     |
224| spoiler       | `||spoiler||`       | ||spoiler||       |
225| superscript   | `^superscript^`     | ^superscript^     |
226| subscript     | `~subscript~`       | ~subscript~       |
227
228## Links
229To 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.
230```md
231[Lockbook's website](https://lockbook.net)
232```
233> [Lockbook's website](https://lockbook.net)
234
235## Images
236To embed an image, add a `!` to the beginning of the link syntax.
237```md
238![Lockbook's logo](https://raw.githubusercontent.com/lockbook/lockbook/master/docs/graphics/logo.svg)
239```
240> ![Lockbook's logo](https://raw.githubusercontent.com/lockbook/lockbook/master/docs/graphics/logo.svg)
241
242## Headings
243To create a heading, add up to six `#`'s plus a space before your text. More `#`'s create a smaller heading.
244```md
245# Heading 1
246## Heading 2
247### Heading 3
248#### Heading 4
249##### Heading 5
250###### Heading 6
251```
252> # Heading 1
253> ## Heading 2
254> ### Heading 3
255> #### Heading 4
256> ##### Heading 5
257> ###### Heading 6
258
259## Lists
260Create 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*.
261```md
262* bulleted list item
263- bulleted list item
264+ bulleted list item
265
2661. numbered list item
2671. numbered list item
2681. numbered list item
269
270- [ ] task list item
271- [x] task list item
272```
273>* bulleted list item
274>- bulleted list item
275>+ bulleted list item
276>
277>1. numbered list item
278>1. numbered list item
279>1. numbered list item
280>
281>- [ ] task list item
282>- [x] task list item
283
284List 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).
285```md
286* This is a bulleted list
287    * An inner item needs at least 2 spaces
2881. This is a numbered list
289    1. An inner item needs at least 3 spaces
290* [ ] This is a task list
291    * [ ] An inner item needs at least 2 spaces
292```
293> * This is a bulleted list
294>   * An inner item needs at least 2 spaces
295> 1. This is a numbered list
296>    1. An inner item needs at least 3 spaces
297> * [ ] This is a task list
298>   * [ ] An inner item needs at least 2 spaces
299
300List 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.
301```md
302* This item contains text,
303    > a quote
304    ### and a heading.
305* This item contains two lines of text.
306The second line doesn't need spaces.
307```
308> * This item contains text,
309>   > a quote
310>   ### and a heading.
311> * This item contains two lines of text.
312> The second line doesn't need spaces.
313
314## Quotes
315To create a block quote, add `> ` to each line.
316```md
317> This is a quote
318```
319> This is a quote
320
321Like list items, block quotes can contain formatted content.
322```md
323> This quote contains some text,
324> ```rust
325> // some code
326> fn main() { println!("Hello, world!"); }
327> ```
328> ### and a heading.
329
330> This quote contains two lines of text.
331The second line doesn't need added characters.
332```
333> This quote contains some text,
334> ```rust
335> // some code
336> fn main() { println!("Hello, world!"); }
337> ```
338> ### and a heading.
339
340> This quote contains two lines of text.
341The second line doesn't need added characters.
342
343## Alerts
344To 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.
345```md
346> [!NOTE]
347> This is a note.
348
349> [!TIP]
350> This is a tip.
351
352> [!IMPORTANT]
353> This is important.
354
355> [!WARNING]
356> This is a warning.
357
358> [!CAUTION] Caution!!!!!
359> This is a caution.
360```
361> [!NOTE]
362> This is a note.
363
364> [!TIP]
365> This is a tip.
366
367> [!IMPORTANT]
368> This is important.
369
370> [!WARNING]
371> This is a warning.
372
373> [!CAUTION] Caution!!!!!
374> This is a caution.
375
376## Tables
377A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
378```md
379| Style         | Syntax              | Example           |
380|---------------|---------------------|-------------------|
381| emphasis      | `*emphasis*`        | *emphasis*        |
382| strong        | `**strong**`        | **strong**        |
383```
384> | Style         | Syntax              | Example           |
385> |---------------|---------------------|-------------------|
386> | emphasis      | `*emphasis*`        | *emphasis*        |
387> | strong        | `**strong**`        | **strong**        |
388
389## Code
390A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
391```md
392    ```rust
393    // some code
394    fn main() { println!("Hello, world!"); }
395    ```
396```
397> ```rust
398> // some code
399> fn main() { println!("Hello, world!"); }
400> ```
401
402You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
403```md
404    // some code
405    fn main() { println!("Hello, world!"); }
406```
407>     // some code
408>     fn main() { println!("Hello, world!"); }
409
410## Thematic Breaks
411A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
412```md
413***
414---
415___
416```
417> ***
418> ---
419> ___
420"#;
421}