Skip to main content

im_core/core/
options.rs

1use std::fmt;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7use crate::vault::{DeviceVaultRootKey, FileSecretVault, FileSecretVaultStore, SecretVault};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum IdentitySecretStoragePolicy {
12    FileCompat,
13    VaultPreferred,
14    VaultRequired,
15}
16
17impl Default for IdentitySecretStoragePolicy {
18    fn default() -> Self {
19        Self::FileCompat
20    }
21}
22
23pub struct ImCoreSecretVaultOptions {
24    pub root_key: DeviceVaultRootKey,
25    pub vault_dir: PathBuf,
26    pub workspace_id: String,
27    pub device_id: String,
28}
29
30impl ImCoreSecretVaultOptions {
31    pub fn new(
32        root_key: DeviceVaultRootKey,
33        vault_dir: impl Into<PathBuf>,
34        workspace_id: impl Into<String>,
35        device_id: impl Into<String>,
36    ) -> Self {
37        Self {
38            root_key,
39            vault_dir: vault_dir.into(),
40            workspace_id: workspace_id.into(),
41            device_id: device_id.into(),
42        }
43    }
44}
45
46impl fmt::Debug for ImCoreSecretVaultOptions {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        f.debug_struct("ImCoreSecretVaultOptions")
49            .field("root_key", &"<redacted-root-key>")
50            .field("vault_dir", &self.vault_dir)
51            .field("workspace_id", &self.workspace_id)
52            .field("device_id", &self.device_id)
53            .finish()
54    }
55}
56
57#[derive(Debug, Default)]
58pub struct ImCoreOpenOptions {
59    pub identity_secret_storage_policy: IdentitySecretStoragePolicy,
60    pub identity_secret_vault: Option<ImCoreSecretVaultOptions>,
61}
62
63impl ImCoreOpenOptions {
64    pub fn file_compat() -> Self {
65        Self::default()
66    }
67
68    pub fn with_identity_secret_vault(
69        mut self,
70        identity_secret_storage_policy: IdentitySecretStoragePolicy,
71        identity_secret_vault: ImCoreSecretVaultOptions,
72    ) -> Self {
73        self.identity_secret_storage_policy = identity_secret_storage_policy;
74        self.identity_secret_vault = Some(identity_secret_vault);
75        self
76    }
77}
78
79#[derive(Clone)]
80pub(crate) struct IdentityVaultContext {
81    policy: IdentitySecretStoragePolicy,
82    vault: Arc<dyn SecretVault + Send + Sync>,
83    workspace_id: String,
84    device_id: String,
85}
86
87impl IdentityVaultContext {
88    pub(crate) fn from_options(options: ImCoreSecretVaultOptions) -> crate::ImResult<Self> {
89        let workspace_id = required_non_empty("workspace_id", options.workspace_id)?;
90        let device_id = required_non_empty("device_id", options.device_id)?;
91        if options.vault_dir.as_os_str().is_empty() {
92            return Err(crate::ImError::invalid_input(
93                Some("vault_dir".to_owned()),
94                "vault directory must not be empty",
95            ));
96        }
97        let vault = Arc::new(FileSecretVault::new(
98            options.root_key,
99            FileSecretVaultStore::new(options.vault_dir),
100        ));
101        Ok(Self {
102            policy: IdentitySecretStoragePolicy::FileCompat,
103            vault,
104            workspace_id,
105            device_id,
106        })
107    }
108
109    pub(crate) fn with_policy(mut self, policy: IdentitySecretStoragePolicy) -> Self {
110        self.policy = policy;
111        self
112    }
113
114    pub(crate) fn vault(&self) -> Arc<dyn SecretVault + Send + Sync> {
115        self.vault.clone()
116    }
117
118    pub(crate) fn workspace_id(&self) -> &str {
119        &self.workspace_id
120    }
121
122    pub(crate) fn device_id(&self) -> &str {
123        &self.device_id
124    }
125}
126
127impl fmt::Debug for IdentityVaultContext {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.debug_struct("IdentityVaultContext")
130            .field("policy", &self.policy)
131            .field("vault", &"<redacted-secret-vault>")
132            .field("workspace_id", &self.workspace_id)
133            .field("device_id", &self.device_id)
134            .finish()
135    }
136}
137
138fn required_non_empty(field: &str, value: String) -> crate::ImResult<String> {
139    let value = value.trim().to_owned();
140    if value.is_empty() {
141        return Err(crate::ImError::invalid_input(
142            Some(field.to_owned()),
143            format!("{field} must not be empty"),
144        ));
145    }
146    Ok(value)
147}