Skip to main content

coven_storage/
invite_storage.rs

1//! Provider admission and credentials carried together inside an invite code.
2
3use crate::{CloudProvider, S3Credentials, StorageError, StorageFailure, StorageInvitation};
4use coven_crypto::SecretBytes;
5use coven_format::code_fields::{pack, FieldReader};
6
7/// The storage part of format.md D13's invitation. Account credentials are
8/// never shared; S3 credentials are the key created specifically for this invite.
9#[derive(Clone)]
10pub enum InviteStorage {
11    /// The recipient signs in to their own provider account.
12    Account(StorageInvitation),
13    /// The recipient uses the key made for them in the provider console.
14    S3 {
15        /// The store location to open.
16        invitation: StorageInvitation,
17        /// The invited member's own key.
18        credentials: S3Credentials,
19    },
20}
21
22impl InviteStorage {
23    /// Pack the invitation and, on S3, the member's key (format.md D13), in
24    /// zeroizing storage. An S3 invitation without a key, or a key for
25    /// another provider, is refused.
26    pub fn encode(&self) -> Result<SecretBytes, StorageError> {
27        let (invitation, credentials) = match self {
28            Self::Account(invitation) => (invitation, None),
29            Self::S3 { invitation, credentials } => (invitation, Some(credentials)),
30        };
31        if (invitation.location().provider() == CloudProvider::S3) != credentials.is_some() {
32            return Err(StorageFailure::InvalidConfiguration
33                .with_source("an S3 invite carries the member's key; no other invite does"));
34        }
35        if let Some(credentials) = credentials {
36            credentials.validate()?;
37        }
38        Ok(pack(|out| {
39            invitation.pack(out)?;
40            match credentials {
41                Some(credentials) => credentials.pack(out),
42                None => Ok(()),
43            }
44        })?)
45    }
46    /// Decode a packed payload after checking the enclosing invitation: the
47    /// location's provider says whether a key follows.
48    pub fn decode(bytes: &[u8]) -> Result<Self, StorageError> {
49        let mut input = FieldReader::new(bytes)?;
50        let invitation = StorageInvitation::unpack(&mut input)?;
51        let storage = match invitation.location().provider() {
52            CloudProvider::S3 => {
53                Self::S3 { invitation, credentials: S3Credentials::unpack(&mut input)? }
54            }
55            _ => Self::Account(invitation),
56        };
57        input.finish()?;
58        Ok(storage)
59    }
60}
61
62#[cfg(test)]
63#[path = "invite_storage_tests.rs"]
64mod tests;