Skip to main content

origin_auth/
store.rs

1use crate::TokenSet;
2use crate::token::StoredTokenSet;
3use origin_domain::{AccountId, AppError, ConnectorId, Result};
4use origin_secrets::{Secret, SecretKey, SecretStore};
5use std::sync::Arc;
6
7/// Credentials in the OS credential store, one entry per account (ADR-0016).
8///
9/// Tokens never touch the application database (ADR-0008); revoking one account never
10/// affects another.
11#[derive(Debug, Clone)]
12pub struct TokenStore {
13    secrets: Arc<dyn SecretStore>,
14}
15
16impl TokenStore {
17    pub fn new(secrets: Arc<dyn SecretStore>) -> Self {
18        Self { secrets }
19    }
20
21    fn key(connector: &ConnectorId, account: &AccountId) -> SecretKey {
22        SecretKey::new(format!("oauth.{connector}"), account.as_str())
23    }
24
25    pub async fn load(
26        &self,
27        connector: &ConnectorId,
28        account: &AccountId,
29    ) -> Result<Option<TokenSet>> {
30        let Some(secret) = self.secrets.get(&Self::key(connector, account)).await? else {
31            return Ok(None);
32        };
33
34        let stored: StoredTokenSet = serde_json::from_str(secret.expose()).map_err(|error| {
35            // Corrupt credentials are not recoverable by retrying; the user has to
36            // reconnect, and the error must say so.
37            AppError::Authentication(format!(
38                "stored credentials for {connector}/{account} are unreadable: {error}"
39            ))
40        })?;
41
42        Ok(Some(stored.into()))
43    }
44
45    pub async fn save(
46        &self,
47        connector: &ConnectorId,
48        account: &AccountId,
49        tokens: &TokenSet,
50    ) -> Result<()> {
51        let encoded = serde_json::to_string(&StoredTokenSet::from(tokens))
52            .map_err(|error| AppError::internal(format!("cannot encode credentials: {error}")))?;
53
54        self.secrets
55            .set(&Self::key(connector, account), Secret::new(encoded))
56            .await
57    }
58
59    /// Remove an account's credentials. Used on disconnect and on unrecoverable
60    /// authentication failures.
61    pub async fn delete(&self, connector: &ConnectorId, account: &AccountId) -> Result<()> {
62        self.secrets.delete(&Self::key(connector, account)).await
63    }
64}