Skip to main content

authy/vault/
mod.rs

1pub mod crypto;
2pub mod secret;
3
4use std::fs;
5
6use crate::error::{AuthyError, Result};
7use crate::policy::Policy;
8use crate::session::SessionRecord;
9use crate::types::*;
10use crate::vault::secret::SecretEntry;
11
12/// The in-memory representation of the entire vault.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Vault {
15    pub version: u32,
16    pub created_at: DateTime<Utc>,
17    pub modified_at: DateTime<Utc>,
18    pub secrets: BTreeMap<String, SecretEntry>,
19    pub policies: BTreeMap<String, Policy>,
20    pub sessions: Vec<SessionRecord>,
21}
22
23impl Default for Vault {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl Vault {
30    /// Create a new empty vault.
31    pub fn new() -> Self {
32        let now = Utc::now();
33        Self {
34            version: 1,
35            created_at: now,
36            modified_at: now,
37            secrets: BTreeMap::new(),
38            policies: BTreeMap::new(),
39            sessions: Vec::new(),
40        }
41    }
42
43    /// Touch the modified timestamp.
44    pub fn touch(&mut self) {
45        self.modified_at = Utc::now();
46    }
47}
48
49/// Encryption mode for the vault.
50#[derive(Debug, Clone)]
51pub enum VaultKey {
52    Passphrase(String),
53    Keyfile { identity: String, pubkey: String },
54}
55
56/// Get the default authy directory path (~/.authy).
57pub fn authy_dir() -> PathBuf {
58    dirs::home_dir()
59        .expect("Could not determine home directory")
60        .join(".authy")
61}
62
63/// Get the vault file path.
64pub fn vault_path() -> PathBuf {
65    authy_dir().join("vault.age")
66}
67
68/// Get the config file path.
69pub fn config_path() -> PathBuf {
70    authy_dir().join("authy.toml")
71}
72
73/// Get the audit log path.
74pub fn audit_path() -> PathBuf {
75    authy_dir().join("audit.log")
76}
77
78/// Check if the vault is initialized.
79pub fn is_initialized() -> bool {
80    vault_path().exists()
81}
82
83/// Load and decrypt the vault from disk.
84pub fn load_vault(key: &VaultKey) -> Result<Vault> {
85    let path = vault_path();
86    if !path.exists() {
87        return Err(AuthyError::VaultNotInitialized);
88    }
89
90    let ciphertext = fs::read(&path)?;
91    let plaintext = match key {
92        VaultKey::Passphrase(pass) => crypto::decrypt_with_passphrase(&ciphertext, pass)?,
93        VaultKey::Keyfile { identity, .. } => {
94            crypto::decrypt_with_keyfile(&ciphertext, identity)?
95        }
96    };
97
98    let vault: Vault =
99        rmp_serde::from_slice(&plaintext).map_err(|e| AuthyError::Serialization(e.to_string()))?;
100
101    Ok(vault)
102}
103
104/// Encrypt and save the vault to disk with atomic rename.
105pub fn save_vault(vault: &Vault, key: &VaultKey) -> Result<()> {
106    let path = vault_path();
107    let dir = path.parent().unwrap();
108    fs::create_dir_all(dir)?;
109
110    let plaintext =
111        rmp_serde::to_vec(vault).map_err(|e| AuthyError::Serialization(e.to_string()))?;
112
113    let ciphertext = match key {
114        VaultKey::Passphrase(pass) => crypto::encrypt_with_passphrase(&plaintext, pass)?,
115        VaultKey::Keyfile { pubkey, .. } => crypto::encrypt_with_keyfile(&plaintext, pubkey)?,
116    };
117
118    // Atomic write: write to temp file, then rename
119    let tmp_path = path.with_extension("age.tmp");
120    fs::write(&tmp_path, &ciphertext)?;
121    fs::rename(&tmp_path, &path)?;
122
123    Ok(())
124}