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}