sos_login/
identity.rs

1//! Login identity folder management.
2//!
3//! Enables user interfaces to protect folder passwords
4//! using a single primary password.
5//!
6//! Provides access to an identity folder containing
7//! the delegated passwords used by an account to decrypt
8//! the folders for the account.
9use crate::{
10    device::DeviceManager, DelegatedAccess, Error, IdentityFolder,
11    PublicIdentity, Result,
12};
13use async_trait::async_trait;
14use sos_backend::BackendTarget;
15use sos_core::{
16    crypto::AccessKey, AccountId, AuthenticationError, SecretId, VaultId,
17};
18use std::collections::HashMap;
19use urn::Urn;
20
21#[cfg(feature = "files")]
22use secrecy::SecretString;
23
24/// Collection of folder access keys.
25pub struct FolderKeys(pub HashMap<VaultId, AccessKey>);
26
27impl FolderKeys {
28    /// Find an access key by folder id.
29    pub fn find(&self, id: &VaultId) -> Option<&AccessKey> {
30        self.0
31            .iter()
32            .find_map(|(k, v)| if k == id { Some(v) } else { None })
33    }
34}
35
36#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
37#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
38impl DelegatedAccess for FolderKeys {
39    type Error = Error;
40
41    async fn find_folder_password(
42        &self,
43        folder_id: &VaultId,
44    ) -> Result<Option<AccessKey>> {
45        Ok(self.find(folder_id).cloned())
46    }
47
48    async fn remove_folder_password(
49        &mut self,
50        folder_id: &VaultId,
51    ) -> Result<()> {
52        self.0.remove(folder_id);
53        Ok(())
54    }
55
56    async fn save_folder_password(
57        &mut self,
58        folder_id: &VaultId,
59        key: AccessKey,
60    ) -> Result<()> {
61        self.0.insert(*folder_id, key);
62        Ok(())
63    }
64}
65
66/// Cache of mapping between secret URN
67/// and secret identifiers so we can find identity
68/// vault secrets quickly.
69pub type UrnLookup = HashMap<(VaultId, Urn), SecretId>;
70
71/// Identity manages access to an identity vault
72/// and the private keys for a user.
73pub struct Identity {
74    target: BackendTarget,
75    account: Option<PublicIdentity>,
76    identity: Option<IdentityFolder>,
77}
78
79impl Identity {
80    /// Create a new unauthenticated login identity.
81    pub fn new(target: BackendTarget) -> Self {
82        Self {
83            target,
84            identity: None,
85            account: None,
86        }
87    }
88
89    /// Device manager.
90    pub fn devices(&self) -> Result<&DeviceManager> {
91        Ok(self
92            .identity
93            .as_ref()
94            .ok_or(AuthenticationError::NotAuthenticated)?
95            .devices()?)
96    }
97
98    /// Account information.
99    pub fn account(&self) -> Result<&PublicIdentity> {
100        Ok(self
101            .account
102            .as_ref()
103            .ok_or(AuthenticationError::NotAuthenticated)?)
104    }
105
106    fn account_mut(&mut self) -> Result<&mut PublicIdentity> {
107        Ok(self
108            .account
109            .as_mut()
110            .ok_or(AuthenticationError::NotAuthenticated)?)
111    }
112
113    /// Private identity.
114    pub fn identity(&self) -> Result<&IdentityFolder> {
115        Ok(self
116            .identity
117            .as_ref()
118            .ok_or(AuthenticationError::NotAuthenticated)?)
119    }
120
121    #[doc(hidden)]
122    pub fn identity_mut(&mut self) -> Result<&mut IdentityFolder> {
123        Ok(self
124            .identity
125            .as_mut()
126            .ok_or(AuthenticationError::NotAuthenticated)?)
127    }
128
129    /// Verify the access key for this account.
130    pub async fn verify(&self, key: &AccessKey) -> bool {
131        if let Some(identity) = &self.identity {
132            identity.verify(key).await
133        } else {
134            false
135        }
136    }
137
138    /// Rename this account by changing the name of the identity vault.
139    pub async fn rename_account(
140        &mut self,
141        account_name: String,
142    ) -> Result<()> {
143        // Update identity vault
144        self.identity_mut()?.rename(account_name.clone()).await?;
145
146        // Update in-memory account information
147        self.account_mut()?.set_label(account_name);
148
149        Ok(())
150    }
151
152    /// Create the file encryption password.
153    #[cfg(feature = "files")]
154    pub async fn create_file_encryption_password(&mut self) -> Result<()> {
155        self.identity_mut()?.create_file_encryption_password().await
156    }
157
158    /// Find the password used for symmetric file encryption (AGE).
159    #[cfg(feature = "files")]
160    pub async fn find_file_encryption_password(
161        &self,
162    ) -> Result<SecretString> {
163        self.identity()?.find_file_encryption_password().await
164    }
165
166    /// Login to an identity folder.
167    pub async fn login(
168        &mut self,
169        account_id: &AccountId,
170        key: &AccessKey,
171    ) -> Result<()> {
172        let target = self.target.clone().with_account_id(account_id);
173        let mut identity =
174            IdentityFolder::login(&target, account_id, key).await?;
175        identity.ensure_device_vault(target).await?;
176        self.identity = Some(identity);
177        Ok(())
178    }
179
180    /// Sign in to a user account.
181    pub async fn sign_in(
182        &mut self,
183        account_id: &AccountId,
184        key: &AccessKey,
185    ) -> Result<()> {
186        let accounts = self.target.list_accounts().await?;
187        let account = accounts
188            .into_iter()
189            .find(|a| a.account_id() == account_id)
190            .ok_or_else(|| Error::NoAccount(account_id.to_string()))?;
191
192        tracing::debug!("identity::sign_in");
193        self.login(account_id, key).await?;
194        tracing::debug!("identity::verified");
195
196        self.account = Some(account);
197        Ok(())
198    }
199
200    /// Sign out this user by locking the account identity vault.
201    pub async fn sign_out(&mut self) -> Result<()> {
202        tracing::debug!("identity::sign_out");
203
204        // Sign out the identity vault
205        self.identity_mut()?.sign_out().await?;
206
207        self.account = None;
208        self.identity = None;
209        Ok(())
210    }
211}
212
213impl From<Identity> for BackendTarget {
214    fn from(value: Identity) -> Self {
215        value.target
216    }
217}
218
219#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
220#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
221impl DelegatedAccess for Identity {
222    type Error = Error;
223
224    async fn find_folder_password(
225        &self,
226        folder_id: &VaultId,
227    ) -> Result<Option<AccessKey>> {
228        self.identity()?.find_folder_password(folder_id).await
229    }
230
231    async fn remove_folder_password(
232        &mut self,
233        folder_id: &VaultId,
234    ) -> Result<()> {
235        self.identity_mut()?.remove_folder_password(folder_id).await
236    }
237
238    async fn save_folder_password(
239        &mut self,
240        folder_id: &VaultId,
241        key: AccessKey,
242    ) -> Result<()> {
243        self.identity_mut()?
244            .save_folder_password(folder_id, key)
245            .await
246    }
247}