sos_net/pairing/
enrollment.rs

1//! Enroll a device to an account on a remote server.
2use crate::{
3    pairing::{Error, Result},
4    NetworkAccount,
5};
6use sos_account::Account;
7use sos_backend::{BackendTarget, ServerOrigins};
8use sos_client_storage::{
9    ClientAccountStorage, ClientBaseStorage, ClientStorage,
10};
11use sos_core::{
12    crypto::AccessKey, AccountId, Origin, PublicIdentity, RemoteOrigins,
13};
14use sos_login::device::DeviceSigner;
15use sos_protocol::{network_client::HttpClient, SyncClient};
16use sos_signer::ed25519::BoxedEd25519Signer;
17use std::collections::HashSet;
18
19/// Enroll a device.
20///
21/// Once pairing is completed call [DeviceEnrollment::fetch_account]
22/// to retrieve the account data and then [DeviceEnrollment::finish]
23/// to authenticate the account.
24pub struct DeviceEnrollment {
25    /// Account identifier.
26    account_id: AccountId,
27    /// Client account storage.
28    storage: ClientStorage,
29    /// Client used to fetch the account data.
30    client: HttpClient,
31    /// Public identity.
32    ///
33    /// This is available once the account data
34    /// has been successfully fetched.
35    public_identity: Option<PublicIdentity>,
36    /// Device vault.
37    device_vault: Vec<u8>,
38    /// Account name supplied by the other device.
39    account_name: String,
40    /// Collection of server origins.
41    servers: HashSet<Origin>,
42}
43
44impl DeviceEnrollment {
45    /// Create a new device enrollment.
46    pub(crate) async fn new(
47        target: BackendTarget,
48        account_id: AccountId,
49        account_name: String,
50        origin: Origin,
51        device_signer: DeviceSigner,
52        device_vault: Vec<u8>,
53        servers: HashSet<Origin>,
54    ) -> Result<Self> {
55        let target = target.with_account_id(&account_id);
56        match &target {
57            BackendTarget::FileSystem(paths) => {
58                #[cfg(debug_assertions)]
59                sos_core::Paths::scaffold(paths.documents_dir()).await?;
60                paths.ensure().await?;
61            }
62            BackendTarget::Database(_, _) => {}
63        }
64
65        let accounts = target.list_accounts().await?;
66        if accounts
67            .iter()
68            .find(|a| a.account_id() == &account_id)
69            .is_some()
70        {
71            return Err(Error::EnrollAccountExists(account_id));
72        }
73
74        let storage = ClientStorage::new_account(
75            target,
76            &account_id,
77            account_name.clone(),
78        )
79        .await?;
80        let device_signing_key = device_signer.clone();
81        let device: BoxedEd25519Signer = device_signing_key.into();
82        let client =
83            HttpClient::new(account_id, origin, device, String::new())?;
84        Ok(Self {
85            account_id,
86            storage,
87            client,
88            public_identity: None,
89            device_vault,
90            account_name,
91            servers,
92        })
93    }
94
95    /// Account identifier.
96    pub fn account_id(&self) -> &AccountId {
97        &self.account_id
98    }
99
100    /// Public identity of the account.
101    ///
102    /// Only available after a successful call to
103    /// [DeviceEnrollment::fetch_account].
104    pub fn public_identity(&self) -> Option<&PublicIdentity> {
105        self.public_identity.as_ref()
106    }
107
108    /// Fetch the account data for this enrollment.
109    pub async fn fetch_account(&mut self) -> Result<()> {
110        // Fetch the account from the server
111        let create_set = self.client.fetch_account().await?;
112
113        // Create the account data in storage
114        self.storage.import_account(&create_set).await?;
115
116        // Create the device vault containing the private
117        // key for this new device
118        self.storage.create_device_vault(&self.device_vault).await?;
119
120        // Add origin servers early so that they will be registered
121        // as remotes when the enrollment is finished and the account
122        // is authenticated
123        self.add_origin_servers().await?;
124
125        // Set up the public identity which can be shown
126        // to the user before they authenticate by calling finish()
127        self.public_identity = Some(PublicIdentity::new(
128            self.account_id,
129            self.account_name.clone(),
130        ));
131
132        Ok(())
133    }
134
135    /// Finish device enrollment by authenticating the new account.
136    pub async fn finish(&self, key: &AccessKey) -> Result<NetworkAccount> {
137        self.public_identity
138            .as_ref()
139            .ok_or_else(|| Error::AccountNotFetched)?;
140
141        let mut account = NetworkAccount::new_unauthenticated(
142            self.account_id,
143            self.storage.backend_target().clone(),
144            Default::default(),
145        )
146        .await?;
147
148        // Sign in to the new account
149        account.sign_in(key).await?;
150
151        // Ensure the account name is correct
152        account.set_account_name(self.account_name.clone()).await?;
153
154        Ok(account)
155    }
156
157    /// Add the server origins to the enrolled account paths.
158    async fn add_origin_servers(&self) -> Result<()> {
159        let mut origins = ServerOrigins::new(
160            self.storage.backend_target().clone(),
161            &self.account_id,
162        );
163        for server in &self.servers {
164            origins.add_server(server.clone()).await?;
165        }
166        Ok(())
167    }
168}