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 #[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(&username))
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 Ok(())
207 }
208
209 fn welcome_message(username: &str) -> Vec<u8> {
210 format!(r#"# Hello {username}
211
212Welcome to Lockbook! This is an example note to help you get started with our note editor. You can keep it to use as a cheat sheet or delete it anytime.
213
214Lockbook uses Markdown, a lightweight language for formatting plain text. You can use all our supported formatting just by typing. Here’s how it works:
215
216# This is a heading
217
218## This is a smaller heading
219
220### This is an even smaller heading
221
222###### Headings have 6 levels
223
224For italic, use single *asterisks* or _underscores_.
225
226For bold, use double **asterisks** or __underscores__.
227
228For inline code, use single `backticks`
229
230For code blocks, use
231```
232triple
233backticks
234```
235
236>For block quotes,
237use a greater-than sign
238
239Bulleted list items
240* start
241* with
242* asterisks
243- or
244- hyphens
245+ or
246+ plus
247+ signs
248
249Numbered list items
2501. start
2512. with
2523. numbers
2534. and
2545. periods
255
256Happy note taking! You can report any issues to our [Github project](https://github.com/lockbook/lockbook/issues/new) or join our [Discord server](https://discord.gg/qv9fmAZCm6)."#).into()
257 }
258}