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, LocalLb};
11use libsecp256k1::SecretKey;
12use qrcode_generator::QrCodeEcc;
13
14use crate::io::network::ApiError;
15
16impl LocalLb {
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 async fn delete_account(&self) -> LbResult<()> {
157        let account = self.get_account()?;
158
159        self.client
160            .request(account, DeleteAccountRequest {})
161            .await
162            .map_err(|err| match err {
163                ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
164                ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
165                _ => core_err_unexpected(err),
166            })?;
167
168        let mut tx = self.begin_tx().await;
169        let db = tx.db();
170
171        db.account.clear()?;
172        db.last_synced.clear()?;
173        db.base_metadata.clear()?;
174        db.root.clear()?;
175        db.local_metadata.clear()?;
176        db.pub_key_lookup.clear()?;
177
178        // todo: clear cache?
179
180        Ok(())
181    }
182
183    const WELCOME_MESSAGE: &'static str = r#"# Markdown Syntax
184Markdown is a language for easily formatting your documents. This document can help you get started.
185
186## Styled Text
187To style text, wrap your text in the corresponding characters.
188| Style         | Syntax              | Example           |
189|---------------|---------------------|-------------------|
190| emphasis      | `*emphasis*`        | *emphasis*        |
191| strong        | `**strong**`        | **strong**        |
192| strikethrough | `~~strikethrough~~` | ~~strikethrough~~ |
193| underline     | `__underline__`     | __underline__     |
194| code          | ``code``            | `code`            |
195| highlight     | `==highlight==`     | ==highlight==     |
196| spoiler       | `||spoiler||`       | ||spoiler||       |
197| superscript   | `^superscript^`     | ^superscript^     |
198| subscript     | `~subscript~`       | ~subscript~       |
199
200## Links
201To 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.
202```md
203[Lockbook's website](https://lockbook.net)
204```
205> [Lockbook's website](https://lockbook.net)
206
207## Images
208To embed an image, add a `!` to the beginning of the link syntax.
209```md
210![Lockbook's logo](https://raw.githubusercontent.com/lockbook/lockbook/master/docs/graphics/logo.svg)
211```
212> ![Lockbook's logo](https://raw.githubusercontent.com/lockbook/lockbook/master/docs/graphics/logo.svg)
213
214## Headings
215To create a heading, add up to six `#`'s plus a space before your text. More `#`'s create a smaller heading.
216```md
217# Heading 1
218## Heading 2
219### Heading 3
220#### Heading 4
221##### Heading 5
222###### Heading 6
223```
224> # Heading 1
225> ## Heading 2
226> ### Heading 3
227> #### Heading 4
228> ##### Heading 5
229> ###### Heading 6
230
231## Lists
232Create 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*.
233```md
234* bulleted list item
235- bulleted list item
236+ bulleted list item
237
2381. numbered list item
2391. numbered list item
2401. numbered list item
241
242- [ ] task list item
243- [x] task list item
244```
245>* bulleted list item
246>- bulleted list item
247>+ bulleted list item
248>
249>1. numbered list item
250>1. numbered list item
251>1. numbered list item
252>
253>- [ ] task list item
254>- [x] task list item
255
256List 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).
257```md
258* This is a bulleted list
259    * An inner item needs at least 2 spaces
2601. This is a numbered list
261    1. An inner item needs at least 3 spaces
262* [ ] This is a task list
263    * [ ] An inner item needs at least 2 spaces
264```
265> * This is a bulleted list
266>   * An inner item needs at least 2 spaces
267> 1. This is a numbered list
268>    1. An inner item needs at least 3 spaces
269> * [ ] This is a task list
270>   * [ ] An inner item needs at least 2 spaces
271
272List 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.
273```md
274* This item contains text,
275    > a quote
276    ### and a heading.
277* This item contains two lines of text.
278The second line doesn't need spaces.
279```
280> * This item contains text,
281>   > a quote
282>   ### and a heading.
283> * This item contains two lines of text.
284> The second line doesn't need spaces.
285
286## Quotes
287To create a block quote, add `> ` to each line.
288```md
289> This is a quote
290```
291> This is a quote
292
293Like list items, block quotes can contain formatted content.
294```md
295> This quote contains some text,
296> ```rust
297> // some code
298> fn main() { println!("Hello, world!"); }
299> ```
300> ### and a heading.
301
302> This quote contains two lines of text.
303The second line doesn't need added characters.
304```
305> This quote contains some text,
306> ```rust
307> // some code
308> fn main() { println!("Hello, world!"); }
309> ```
310> ### and a heading.
311
312> This quote contains two lines of text.
313The second line doesn't need added characters.
314
315## Alerts
316To 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.
317```md
318> [!NOTE]
319> This is a note.
320
321> [!TIP]
322> This is a tip.
323
324> [!IMPORTANT]
325> This is important.
326
327> [!WARNING]
328> This is a warning.
329
330> [!CAUTION] Caution!!!!!
331> This is a caution.
332```
333> [!NOTE]
334> This is a note.
335
336> [!TIP]
337> This is a tip.
338
339> [!IMPORTANT]
340> This is important.
341
342> [!WARNING]
343> This is a warning.
344
345> [!CAUTION] Caution!!!!!
346> This is a caution.
347
348## Tables
349A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
350```md
351| Style         | Syntax              | Example           |
352|---------------|---------------------|-------------------|
353| emphasis      | `*emphasis*`        | *emphasis*        |
354| strong        | `**strong**`        | **strong**        |
355```
356> | Style         | Syntax              | Example           |
357> |---------------|---------------------|-------------------|
358> | emphasis      | `*emphasis*`        | *emphasis*        |
359> | strong        | `**strong**`        | **strong**        |
360
361## Code
362A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
363```md
364    ```rust
365    // some code
366    fn main() { println!("Hello, world!"); }
367    ```
368```
369> ```rust
370> // some code
371> fn main() { println!("Hello, world!"); }
372> ```
373
374You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
375```md
376    // some code
377    fn main() { println!("Hello, world!"); }
378```
379>     // some code
380>     fn main() { println!("Hello, world!"); }
381
382## Thematic Breaks
383A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
384```md
385***
386---
387___
388```
389> ***
390> ---
391> ___
392"#;
393}
394
395impl crate::Lb {
396    #[instrument(level = "debug", skip(self), err(Debug))]
397    pub fn export_account_private_key(&self) -> LbResult<String> {
398        let account = self.get_account()?;
399        Ok(base64::encode(account.private_key.serialize()))
400    }
401
402    pub fn export_account_phrase(&self) -> LbResult<String> {
403        let account = self.get_account()?;
404        Ok(account.get_phrase()?.join(" "))
405    }
406
407    pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
408        let acct_secret = self.export_account_private_key()?;
409        qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
410            .map_err(|err| core_err_unexpected(err).into())
411    }
412}