1use subtle::ConstantTimeEq;
8
9use crate::config::ApiKeyConfig;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum AuthResult {
14 Open,
16 Allowed,
18 MissingKey,
20 InvalidKey,
22 InsufficientPermission,
24}
25
26impl AuthResult {
27 pub fn is_allowed(&self) -> bool {
29 matches!(self, AuthResult::Open | AuthResult::Allowed)
30 }
31
32 pub fn error_message(&self, permission: &str) -> Option<String> {
34 match self {
35 AuthResult::Open | AuthResult::Allowed => None,
36 AuthResult::MissingKey => Some("Missing API key".to_string()),
37 AuthResult::InvalidKey => Some("Invalid API key".to_string()),
38 AuthResult::InsufficientPermission => {
39 Some(format!("API key does not have '{permission}' permission"))
40 }
41 }
42 }
43}
44
45pub fn extract_bearer_from_value(value: &str) -> Option<&str> {
49 value.strip_prefix("Bearer ")
50}
51
52pub fn check_auth(
60 api_keys: &[ApiKeyConfig],
61 provided_key: Option<&str>,
62 permission: &str,
63) -> AuthResult {
64 if api_keys.is_empty() {
65 return AuthResult::Open;
66 }
67
68 let key = match provided_key {
69 Some(k) if !k.is_empty() => k,
70 _ => return AuthResult::MissingKey,
71 };
72
73 match api_keys.iter().find(|k| {
74 let a = k.key.as_bytes();
75 let b = key.as_bytes();
76 a.len() == b.len() && a.ct_eq(b).into()
84 }) {
85 None => AuthResult::InvalidKey,
86 Some(k) if !k.has_permission(permission) => AuthResult::InsufficientPermission,
87 Some(_) => AuthResult::Allowed,
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn test_keys() -> Vec<ApiKeyConfig> {
96 vec![
97 ApiKeyConfig {
98 key: "rw-key".to_string(),
99 name: "Read-Write".to_string(),
100 permissions: vec!["read".to_string(), "write".to_string()],
101 },
102 ApiKeyConfig {
103 key: "ro-key".to_string(),
104 name: "Read-Only".to_string(),
105 permissions: vec!["read".to_string()],
106 },
107 ]
108 }
109
110 #[test]
111 fn test_empty_keys_allows_all() {
112 assert_eq!(check_auth(&[], Some("anything"), "write"), AuthResult::Open);
113 assert_eq!(check_auth(&[], None, "read"), AuthResult::Open);
114 }
115
116 #[test]
117 fn test_missing_key() {
118 let keys = test_keys();
119 assert_eq!(check_auth(&keys, None, "read"), AuthResult::MissingKey);
120 assert_eq!(check_auth(&keys, Some(""), "read"), AuthResult::MissingKey);
121 }
122
123 #[test]
124 fn test_invalid_key() {
125 let keys = test_keys();
126 assert_eq!(
127 check_auth(&keys, Some("bad-key"), "read"),
128 AuthResult::InvalidKey
129 );
130 }
131
132 #[test]
133 fn test_insufficient_permission() {
134 let keys = test_keys();
135 assert_eq!(
136 check_auth(&keys, Some("ro-key"), "write"),
137 AuthResult::InsufficientPermission
138 );
139 }
140
141 #[test]
142 fn test_valid_read() {
143 let keys = test_keys();
144 assert_eq!(
145 check_auth(&keys, Some("ro-key"), "read"),
146 AuthResult::Allowed
147 );
148 assert_eq!(
149 check_auth(&keys, Some("rw-key"), "read"),
150 AuthResult::Allowed
151 );
152 }
153
154 #[test]
155 fn test_valid_write() {
156 let keys = test_keys();
157 assert_eq!(
158 check_auth(&keys, Some("rw-key"), "write"),
159 AuthResult::Allowed
160 );
161 }
162}