api_keys_simplified/
domain.rs1use crate::{
2 config::{Environment, KeyConfig, KeyPrefix, Separator},
3 error::Result,
4 generator::KeyGenerator,
5 hasher::KeyHasher,
6 secure::SecureString,
7 validator::KeyValidator,
8};
9
10#[derive(Clone)]
15pub struct ApiKey {
16 key: SecureString,
17 hash: String,
18}
19
20impl std::fmt::Debug for ApiKey {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("ApiKey")
24 .field("key", &"[REDACTED]")
25 .field("hash", &self.hash)
26 .finish()
27 }
28}
29
30impl ApiKey {
31 pub fn generate(
32 prefix: impl Into<String>,
33 environment: impl Into<Environment>,
34 config: KeyConfig,
35 ) -> Result<Self> {
36 let prefix = KeyPrefix::new(prefix, &config.separator)?;
37
38 let key = KeyGenerator::generate(prefix, environment.into(), &config)?;
39 let hash = KeyHasher::hash(&key, &config.hash_config)?;
40
41 Ok(Self { key, hash })
42 }
43
44 pub fn generate_default(
45 prefix: impl Into<String>,
46 environment: impl Into<Environment>,
47 ) -> Result<Self> {
48 Self::generate(prefix, environment, KeyConfig::default())
49 }
50
51 pub fn generate_high_security(
52 prefix: impl Into<String>,
53 environment: impl Into<Environment>,
54 ) -> Result<Self> {
55 Self::generate(prefix, environment, KeyConfig::high_security())
56 }
57
58 pub fn verify(provided_key: impl AsRef<str>, stored_hash: impl AsRef<str>) -> Result<bool> {
59 KeyValidator::verify(provided_key.as_ref(), stored_hash.as_ref())
60 }
61
62 pub fn verify_checksum(key: impl AsRef<str>) -> Result<bool> {
63 KeyGenerator::verify_checksum(key.as_ref())
64 }
65
66 pub fn key(&self) -> &SecureString {
81 &self.key
82 }
83
84 pub fn hash(&self) -> &str {
85 &self.hash
86 }
87
88 pub fn parse_prefix(key: &SecureString, separator: Separator) -> Result<String> {
89 KeyGenerator::parse_key(key.as_ref(), separator).map(|(prefix, _)| prefix)
90 }
91
92 pub fn parse_environment(key: &SecureString, separator: Separator) -> Result<String> {
93 KeyGenerator::parse_key(key.as_ref(), separator).map(|(_, env)| env)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_full_lifecycle() {
103 let api_key = ApiKey::generate_default("sk", Environment::production()).unwrap();
104
105 let key_str = api_key.key();
106 let hash_str = api_key.hash();
107
108 assert!(key_str.as_ref().starts_with("sk-live-"));
109 assert!(hash_str.starts_with("$argon2id$"));
110
111 assert!(ApiKey::verify(key_str.as_ref(), hash_str).unwrap());
112 assert!(!ApiKey::verify("wrong_key", hash_str).unwrap());
113 }
114
115 #[test]
116 fn test_different_presets() {
117 let balanced = ApiKey::generate_default("pk", Environment::test()).unwrap();
118 let high_sec = ApiKey::generate_high_security("sk", Environment::Production).unwrap();
119
120 assert!(!balanced.key().is_empty());
121 assert!(high_sec.key().len() > balanced.key().len());
122 }
123
124 #[test]
125 fn test_parsing() {
126 let key = SecureString::from("sk/live/abc123xyz789");
127 let prefix = ApiKey::parse_prefix(&key, Separator::Slash).unwrap();
128 let env = ApiKey::parse_environment(&key, Separator::Slash).unwrap();
129
130 assert_eq!(prefix, "sk");
131 assert_eq!(env, "live");
132 }
133
134 #[test]
135 fn test_custom_config() {
136 let config = KeyConfig::new()
137 .with_entropy(32)
138 .unwrap()
139 .with_checksum(true);
140
141 let key = ApiKey::generate("custom", Environment::production(), config).unwrap();
142 assert!(ApiKey::verify_checksum(key.key()).unwrap());
143 }
144}