Skip to main content

lb_rs/service/
account.rs

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