1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
//! Create new local account.

use urn::Urn;

use crate::{
    constants::{
        DEFAULT_ARCHIVE_VAULT_NAME, DEFAULT_AUTHENTICATOR_VAULT_NAME,
        DEFAULT_CONTACTS_VAULT_NAME, FILE_PASSWORD_URN,
    },
    crypto::AccessKey,
    encode,
    storage::AppPaths,
    vault::{
        secret::{Secret, SecretMeta, UserData},
        Gatekeeper, Summary, Vault, VaultBuilder, VaultFlags,
    },
    vfs, Result,
};
use web3_address::ethereum::Address;

use super::{DelegatedPassphrase, Identity, UserIdentity};

use secrecy::SecretString;

/// Newly created account information.
pub struct NewAccount {
    /// Address of the account signing key.
    pub address: Address,
    /// Identity for the new user.
    pub user: UserIdentity,
    /// Default vault.
    pub default_vault: Vault,
    /// Archive vault.
    pub archive: Option<Vault>,
    /// Authenticator vault.
    pub authenticator: Option<Vault>,
    /// Contacts vault.
    pub contacts: Option<Vault>,
}

/// New account vaults that have been imported into
/// a storage provider.
pub struct ImportedAccount {
    /// Summary for the default folder.
    pub summary: Summary,
    /// Sumary for an archive if the new account
    /// includes an archive folder.
    pub archive: Option<Summary>,
    /// Sumary for an authenticator if the new account
    /// includes an authenticator folder.
    pub authenticator: Option<Summary>,
    /// Sumary for contacts if the new account
    /// includes a contacts folder.
    pub contacts: Option<Summary>,
}

/// Create a new account.
pub struct AccountBuilder {
    account_name: String,
    passphrase: SecretString,
    save_passphrase: bool,
    create_archive: bool,
    create_authenticator: bool,
    create_contacts: bool,
    create_file_password: bool,
    default_folder_name: Option<String>,
}

impl AccountBuilder {
    /// Create a new account builder.
    pub fn new(account_name: String, passphrase: SecretString) -> Self {
        Self {
            account_name,
            passphrase,
            save_passphrase: false,
            create_archive: false,
            create_authenticator: false,
            create_contacts: false,
            create_file_password: false,
            default_folder_name: None,
        }
    }

    /// Enable saving the master passphrase in the default folder.
    pub fn save_passphrase(mut self, value: bool) -> Self {
        self.save_passphrase = value;
        self
    }

    /// Enable creating an archive folder.
    pub fn create_archive(mut self, value: bool) -> Self {
        self.create_archive = value;
        self
    }

    /// Enable creating an authenticator folder.
    pub fn create_authenticator(mut self, value: bool) -> Self {
        self.create_authenticator = value;
        self
    }

    /// Enable creating an contacts folder.
    pub fn create_contacts(mut self, value: bool) -> Self {
        self.create_contacts = value;
        self
    }

    /// Enable creating a password for symmetric file encryption.
    pub fn create_file_password(mut self, value: bool) -> Self {
        self.create_file_password = value;
        self
    }

    /// Set the name of the default folder.
    pub fn default_folder_name(mut self, value: Option<String>) -> Self {
        self.default_folder_name = value;
        self
    }

    /// Create a new identity vault and account folders.
    pub async fn build(self) -> Result<(Vault, NewAccount)> {
        let AccountBuilder {
            account_name,
            passphrase,
            save_passphrase,
            create_archive,
            create_authenticator,
            create_contacts,
            create_file_password,
            mut default_folder_name,
        } = self;

        // Prepare the identity vault
        let (address, identity_vault) = Identity::new_login_vault(
            account_name.clone(),
            passphrase.clone(),
        )
        .await?;

        // Authenticate on the newly created identity vault so we
        // can get the signing key for provider communication
        let buffer = encode(&identity_vault).await?;
        let user =
            Identity::login_buffer(buffer, passphrase.clone(), None, None)
                .await?;

        // Prepare the passphrase for the default vault
        let vault_passphrase =
            DelegatedPassphrase::generate_vault_passphrase()?;

        // Prepare the default vault
        let mut builder = VaultBuilder::new().flags(VaultFlags::DEFAULT);
        if let Some(name) = default_folder_name.take() {
            builder = builder.public_name(name);
        }
        let mut default_vault =
            builder.password(vault_passphrase.clone(), None).await?;

        // Save the master passphrase in the default vault
        if save_passphrase {
            let mut keeper = Gatekeeper::new(default_vault, None);
            keeper.unlock(vault_passphrase.clone().into()).await?;

            let secret = Secret::Account {
                account: account_name,
                password: passphrase.clone(),
                url: None,
                user_data: UserData::new_comment(address.to_string()),
            };
            let mut meta =
                SecretMeta::new("Account Password".to_string(), secret.kind());
            meta.set_favorite(true);
            keeper.create(meta, secret).await?;

            default_vault = keeper.into();
        }

        // Store the vault passphrase in the identity vault
        let mut keeper = Gatekeeper::new(identity_vault, None);
        keeper.unlock(passphrase.into()).await?;

        DelegatedPassphrase::save_vault_passphrase(
            &mut keeper,
            default_vault.id(),
            AccessKey::Password(vault_passphrase),
        )
        .await?;

        if create_file_password {
            let file_passphrase =
                DelegatedPassphrase::generate_vault_passphrase()?;
            let secret = Secret::Password {
                password: file_passphrase,
                name: None,
                user_data: UserData::new_comment(address.to_string()),
            };
            let mut meta =
                SecretMeta::new("File Encryption".to_string(), secret.kind());
            let urn: Urn = FILE_PASSWORD_URN.parse()?;
            meta.set_urn(Some(urn));
            keeper.create(meta, secret).await?;
        }

        let archive = if create_archive {
            // Prepare the passphrase for the archive vault
            let archive_passphrase =
                DelegatedPassphrase::generate_vault_passphrase()?;

            // Prepare the archive vault
            let vault = VaultBuilder::new()
                .public_name(DEFAULT_ARCHIVE_VAULT_NAME.to_string())
                .flags(VaultFlags::ARCHIVE)
                .password(archive_passphrase.clone(), None)
                .await?;

            DelegatedPassphrase::save_vault_passphrase(
                &mut keeper,
                vault.id(),
                AccessKey::Password(archive_passphrase),
            )
            .await?;
            Some(vault)
        } else {
            None
        };

        let authenticator = if create_authenticator {
            // Prepare the passphrase for the authenticator vault
            let auth_passphrase =
                DelegatedPassphrase::generate_vault_passphrase()?;

            // Prepare the authenticator vault
            let vault = VaultBuilder::new()
                .public_name(DEFAULT_AUTHENTICATOR_VAULT_NAME.to_string())
                .flags(VaultFlags::AUTHENTICATOR | VaultFlags::NO_SYNC_SELF)
                .password(auth_passphrase.clone(), None)
                .await?;

            DelegatedPassphrase::save_vault_passphrase(
                &mut keeper,
                vault.id(),
                AccessKey::Password(auth_passphrase),
            )
            .await?;
            Some(vault)
        } else {
            None
        };

        let contacts = if create_contacts {
            // Prepare the passphrase for the authenticator vault
            let auth_passphrase =
                DelegatedPassphrase::generate_vault_passphrase()?;

            // Prepare the authenticator vault
            let vault = VaultBuilder::new()
                .public_name(DEFAULT_CONTACTS_VAULT_NAME.to_string())
                .flags(VaultFlags::CONTACT)
                .password(auth_passphrase.clone(), None)
                .await?;

            DelegatedPassphrase::save_vault_passphrase(
                &mut keeper,
                vault.id(),
                AccessKey::Password(auth_passphrase),
            )
            .await?;
            Some(vault)
        } else {
            None
        };

        Ok((
            keeper.into(),
            NewAccount {
                address,
                user,
                default_vault,
                archive,
                authenticator,
                contacts,
            },
        ))
    }

    /// Write the identity vault to disc and prepare storage directories.
    pub async fn write(
        identity_vault: Vault,
        account: NewAccount,
    ) -> Result<NewAccount> {
        let address = account.address.to_string();
        // Persist the identity vault to disc, MUST re-encode the buffer
        // as we have modified the identity vault
        let identity_vault_file = AppPaths::identity_vault(&address)?;
        let buffer = encode(&identity_vault).await?;
        vfs::write(identity_vault_file, buffer).await?;

        // Ensure the files directory exists
        AppPaths::files_dir(&address)?;

        Ok(account)
    }

    /// Create a new account and write the identity vault to disc.
    pub async fn finish(self) -> Result<NewAccount> {
        let (identity_vault, account) = self.build().await?;
        Self::write(identity_vault, account).await
    }
}