1use crate::model::account::{Account, MAX_USERNAME_LENGTH};
2use crate::model::api::{
3 DeleteAccountRequest, GetPublicKeyRequest, GetUsernameRequest, NewAccountRequest,
4};
5use crate::model::errors::{core_err_unexpected, LbErrKind, LbResult};
6use crate::model::file_like::FileLike;
7use crate::model::file_metadata::{FileMetadata, FileType, Owner};
8use crate::{Lb, DEFAULT_API_LOCATION};
9use libsecp256k1::SecretKey;
10use qrcode_generator::QrCodeEcc;
11
12use crate::io::network::ApiError;
13
14impl Lb {
15 #[instrument(level = "debug", skip(self), err(Debug))]
22 pub async fn create_account(
23 &self, username: &str, api_url: &str, welcome_doc: bool,
24 ) -> LbResult<Account> {
25 let username = String::from(username).to_lowercase();
26
27 if username.len() > MAX_USERNAME_LENGTH {
28 return Err(LbErrKind::UsernameInvalid.into());
29 }
30
31 let mut tx = self.begin_tx().await;
32 let db = tx.db();
33
34 if db.account.get().is_some() {
35 return Err(LbErrKind::AccountExists.into());
36 }
37
38 let account = Account::new(username.clone(), api_url.to_string());
39
40 let root = FileMetadata::create_root(&account)?.sign_with(&account)?;
41 let root_id = *root.id();
42
43 let last_synced = self
44 .client
45 .request(&account, NewAccountRequest::new(&account, &root))
46 .await?
47 .last_synced;
48
49 db.account.insert(account.clone())?;
50 db.base_metadata.insert(root_id, root)?;
51 db.last_synced.insert(last_synced as i64)?;
52 db.root.insert(root_id)?;
53 db.pub_key_lookup
54 .insert(Owner(account.public_key()), account.username.clone())?;
55
56 self.keychain.cache_account(account.clone()).await?;
57
58 tx.end();
59
60 self.events.meta_changed(root_id);
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(&username))
67 .await?;
68 self.sync(None).await?;
69 }
70
71 Ok(account)
72 }
73
74 #[instrument(level = "debug", skip(self, key), err(Debug))]
75 pub async fn import_account(&self, key: &str, api_url: Option<&str>) -> LbResult<Account> {
76 if self.get_account().is_ok() {
77 warn!("tried to import an account, but account exists already.");
78 return Err(LbErrKind::AccountExists.into());
79 }
80
81 if let Ok(key) = base64::decode(key) {
82 if let Ok(account) = bincode::deserialize(&key[..]) {
83 return self.import_account_private_key_v1(account).await;
84 } else if let Ok(key) = SecretKey::parse_slice(&key) {
85 return self
86 .import_account_private_key_v2(key, api_url.unwrap_or(DEFAULT_API_LOCATION))
87 .await;
88 }
89 }
90
91 let phrase: [&str; 24] = key
92 .split([' ', ','])
93 .filter(|maybe_word| !maybe_word.is_empty())
94 .collect::<Vec<_>>()
95 .try_into()
96 .map_err(|_| LbErrKind::AccountStringCorrupted)?;
97
98 self.import_account_phrase(phrase, api_url.unwrap_or(DEFAULT_API_LOCATION))
99 .await
100 }
101
102 pub async fn import_account_private_key_v1(&self, account: Account) -> LbResult<Account> {
103 let server_public_key = self
104 .client
105 .request(&account, GetPublicKeyRequest { username: account.username.clone() })
106 .await?
107 .key;
108
109 let account_public_key = account.public_key();
110
111 if account_public_key != server_public_key {
112 return Err(LbErrKind::UsernamePublicKeyMismatch.into());
113 }
114
115 let mut tx = self.begin_tx().await;
116 let db = tx.db();
117 db.account.insert(account.clone())?;
118 self.keychain.cache_account(account.clone()).await?;
119
120 Ok(account)
121 }
122
123 pub async fn import_account_private_key_v2(
124 &self, private_key: SecretKey, api_url: &str,
125 ) -> LbResult<Account> {
126 let mut account =
127 Account { username: "".to_string(), api_url: api_url.to_string(), private_key };
128 let public_key = account.public_key();
129
130 account.username = self
131 .client
132 .request(&account, GetUsernameRequest { key: public_key })
133 .await?
134 .username;
135
136 let mut tx = self.begin_tx().await;
137 let db = tx.db();
138 db.account.insert(account.clone())?;
139 self.keychain.cache_account(account.clone()).await?;
140
141 Ok(account)
142 }
143
144 pub async fn import_account_phrase(
145 &self, phrase: [&str; 24], api_url: &str,
146 ) -> LbResult<Account> {
147 let private_key = Account::phrase_to_private_key(phrase)?;
148 self.import_account_private_key_v2(private_key, api_url)
149 .await
150 }
151
152 #[instrument(level = "debug", skip(self), err(Debug))]
153 pub fn export_account_private_key(&self) -> LbResult<String> {
154 self.export_account_private_key_v1()
155 }
156
157 pub(crate) fn export_account_private_key_v1(&self) -> LbResult<String> {
158 let account = self.get_account()?;
159 let encoded: Vec<u8> = bincode::serialize(account).map_err(core_err_unexpected)?;
160 Ok(base64::encode(encoded))
161 }
162
163 #[allow(dead_code)]
164 pub(crate) fn export_account_private_key_v2(&self) -> LbResult<String> {
165 let account = self.get_account()?;
166 Ok(base64::encode(account.private_key.serialize()))
167 }
168
169 pub fn export_account_phrase(&self) -> LbResult<String> {
170 let account = self.get_account()?;
171 Ok(account.get_phrase()?.join(" "))
172 }
173
174 pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
175 let acct_secret = self.export_account_private_key_v1()?;
176 qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
177 .map_err(|err| core_err_unexpected(err).into())
178 }
179
180 #[instrument(level = "debug", skip(self), err(Debug))]
181 pub async fn delete_account(&self) -> LbResult<()> {
182 let account = self.get_account()?;
183
184 self.client
185 .request(account, DeleteAccountRequest {})
186 .await
187 .map_err(|err| match err {
188 ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
189 ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
190 _ => core_err_unexpected(err),
191 })?;
192
193 let mut tx = self.begin_tx().await;
194 let db = tx.db();
195
196 db.account.clear()?;
197 db.last_synced.clear()?;
198 db.base_metadata.clear()?;
199 db.root.clear()?;
200 db.local_metadata.clear()?;
201 db.pub_key_lookup.clear()?;
202
203 Ok(())
206 }
207
208 fn welcome_message(username: &str) -> Vec<u8> {
209 format!(r#"# Hello {username}
210
211Welcome 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.
212
213Lockbook uses Markdown, a lightweight language for formatting plain text. You can use all our supported formatting just by typing. Here’s how it works:
214
215# This is a heading
216
217## This is a smaller heading
218
219### This is an even smaller heading
220
221###### Headings have 6 levels
222
223For italic, use single *asterisks* or _underscores_.
224
225For bold, use double **asterisks** or __underscores__.
226
227For inline code, use single `backticks`
228
229For code blocks, use
230```
231triple
232backticks
233```
234
235>For block quotes,
236use a greater-than sign
237
238Bulleted list items
239* start
240* with
241* asterisks
242- or
243- hyphens
244+ or
245+ plus
246+ signs
247
248Numbered list items
2491. start
2502. with
2513. numbers
2524. and
2535. periods
254
255Happy 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()
256 }
257}