alibi_core/config/
secrets.rs1use crate::AuthError;
2use indexmap::IndexMap;
3
4#[derive(Clone)]
9pub struct ManagedSecrets {
10 current_version: u64,
11 keys: IndexMap<u64, String>,
12 legacy_secret: Option<String>,
13}
14
15impl ManagedSecrets {
16 #[must_use]
17 pub fn new(version: u64, secret: impl Into<String>) -> Self {
18 Self {
19 current_version: version,
20 keys: IndexMap::from([(version, secret.into())]),
21 legacy_secret: None,
22 }
23 }
24
25 #[must_use]
28 pub fn retain(mut self, version: u64, secret: impl Into<String>) -> Self {
29 _ = self.keys.insert(version, secret.into());
30 self
31 }
32
33 #[must_use]
35 pub fn legacy(mut self, secret: impl Into<String>) -> Self {
36 self.legacy_secret = Some(secret.into());
37 self
38 }
39
40 #[must_use]
43 pub fn retire(mut self, version: u64) -> Self {
44 if version != self.current_version {
45 _ = self.keys.shift_remove(&version);
46 }
47 self
48 }
49
50 #[must_use]
51 pub const fn current_version(&self) -> u64 {
52 self.current_version
53 }
54
55 #[must_use]
56 pub fn current_secret(&self) -> &str {
57 self.key(self.current_version).unwrap_or_default()
58 }
59
60 #[must_use]
61 pub fn key(&self, version: u64) -> Option<&str> {
62 self.keys.get(&version).map(String::as_str)
63 }
64
65 #[must_use]
66 pub fn legacy_secret(&self) -> Option<&str> {
67 self.legacy_secret.as_deref()
68 }
69
70 pub fn verification_secrets(&self) -> impl Iterator<Item = &str> {
71 self.keys.values().map(String::as_str).chain(
72 self.legacy_secret
73 .as_deref()
74 .filter(|legacy| !self.keys.values().any(|key| key == legacy)),
75 )
76 }
77
78 pub fn validate(&self) -> Result<(), AuthError> {
81 if self
82 .keys
83 .iter()
84 .any(|(version, key)| *version > 9_007_199_254_740_991 || key.is_empty())
85 {
86 return Err(AuthError::config(
87 "Secret versions must be safe nonnegative integers with nonempty keys",
88 ));
89 }
90 if self.current_secret().len() < 32 {
91 return Err(AuthError::config(
92 "Current secret key must be at least 32 characters",
93 ));
94 }
95 if self.legacy_secret.as_deref().is_some_and(str::is_empty) {
96 return Err(AuthError::config("Legacy secret key cannot be empty"));
97 }
98 Ok(())
99 }
100}
101
102impl std::fmt::Debug for ManagedSecrets {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("ManagedSecrets")
105 .field("current_version", &self.current_version)
106 .field("versions", &self.keys.keys())
107 .finish_non_exhaustive()
108 }
109}