Skip to main content

coven_storage/
credentials.rs

1use coven_crypto::SecretBytes;
2use coven_crypto::SecretText;
3use coven_format::code_fields::{FieldReader, FieldWriter};
4use serde::{Deserialize, Serialize};
5
6/// This member's S3 credentials, made by an admin in the provider console (§20.10).
7#[derive(Clone, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct S3Credentials {
10    /// The public access key id the admin reads or deletes in the provider console.
11    pub access_key_id: String,
12    /// The corresponding secret key.
13    pub secret_access_key: SecretText,
14}
15
16impl S3Credentials {
17    /// Encode the key as it is saved beside a saved restore code (§13.1).
18    pub fn encode(&self) -> Result<SecretBytes, crate::StorageError> {
19        self.validate()?;
20        crate::secret_json::encode(self)
21    }
22
23    /// Read a key saved beside a saved restore code.
24    pub fn decode(bytes: &[u8]) -> Result<Self, crate::StorageError> {
25        let keys: Self = crate::secret_json::decode(bytes)?;
26        keys.validate()?;
27        Ok(keys)
28    }
29
30    /// Pack the key into a code's storage part (format.md D13): the key id,
31    /// then the secret, each a `text`.
32    pub(crate) fn pack(&self, out: &mut FieldWriter) -> Result<(), coven_format::Error> {
33        out.text(&self.access_key_id)?;
34        out.text(self.secret_access_key.as_str())
35    }
36
37    /// Read a key packed by [`Self::pack`]; an empty id or secret is refused.
38    pub(crate) fn unpack(input: &mut FieldReader<'_>) -> Result<Self, crate::StorageError> {
39        let keys = Self {
40            access_key_id: input.text("access key id")?,
41            secret_access_key: input.secret_text("secret access key")?,
42        };
43        keys.validate()?;
44        Ok(keys)
45    }
46
47    /// Refuse a key whose id or secret is empty: S3 needs both.
48    pub fn validate(&self) -> Result<(), crate::StorageError> {
49        if self.access_key_id.is_empty() || self.secret_access_key.as_str().is_empty() {
50            return Err(crate::StorageFailure::InvalidConfiguration
51                .with_source("S3 requires an access key id and secret"));
52        }
53        Ok(())
54    }
55}
56
57/// Device sign-in tokens exchanged between provider code and credential custody.
58#[derive(Clone, Serialize, Deserialize)]
59#[serde(deny_unknown_fields)]
60pub struct OAuthTokens {
61    /// The app client that issued these tokens; refresh must use this client.
62    pub client_id: String,
63    /// Access confirmed by the provider's token response.
64    pub access: crate::OAuthAccess,
65    /// The token authorizing requests.
66    pub access_token: SecretText,
67    /// The token used to obtain a new access token, when issued. The access
68    /// token has no local expiry: it is used until the provider rejects it (§20.2).
69    pub refresh_token: Option<SecretText>,
70}
71
72/// Device credentials committed separately from provider settings.
73#[derive(Clone, Serialize, Deserialize)]
74pub enum StorageCredentials {
75    /// A member's manually created S3 key.
76    S3(S3Credentials),
77    /// A device's own provider sign-in.
78    OAuth(OAuthTokens),
79    /// CloudKit uses the Apple account through the built-in native adapter.
80    CloudKit,
81}
82
83impl StorageCredentials {
84    /// Encode for the facade to seal or place in key custody, never in settings.
85    pub fn encode(&self) -> Result<SecretBytes, crate::StorageError> {
86        crate::secret_json::encode(self)
87    }
88    /// Decode credentials obtained from device custody.
89    pub fn decode(bytes: &[u8]) -> Result<Self, crate::StorageError> {
90        crate::secret_json::decode(bytes)
91    }
92}
93
94#[cfg(test)]
95#[path = "credentials_tests.rs"]
96mod tests;