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()
77 }) {
78 None => AuthResult::InvalidKey,
79 Some(k) if !k.has_permission(permission) => AuthResult::InsufficientPermission,
80 Some(_) => AuthResult::Allowed,
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 fn test_keys() -> Vec<ApiKeyConfig> {
89 vec![
90 ApiKeyConfig {
91 key: "rw-key".to_string(),
92 name: "Read-Write".to_string(),
93 permissions: vec!["read".to_string(), "write".to_string()],
94 },
95 ApiKeyConfig {
96 key: "ro-key".to_string(),
97 name: "Read-Only".to_string(),
98 permissions: vec!["read".to_string()],
99 },
100 ]
101 }
102
103 #[test]
104 fn test_empty_keys_allows_all() {
105 assert_eq!(check_auth(&[], Some("anything"), "write"), AuthResult::Open);
106 assert_eq!(check_auth(&[], None, "read"), AuthResult::Open);
107 }
108
109 #[test]
110 fn test_missing_key() {
111 let keys = test_keys();
112 assert_eq!(check_auth(&keys, None, "read"), AuthResult::MissingKey);
113 assert_eq!(check_auth(&keys, Some(""), "read"), AuthResult::MissingKey);
114 }
115
116 #[test]
117 fn test_invalid_key() {
118 let keys = test_keys();
119 assert_eq!(
120 check_auth(&keys, Some("bad-key"), "read"),
121 AuthResult::InvalidKey
122 );
123 }
124
125 #[test]
126 fn test_insufficient_permission() {
127 let keys = test_keys();
128 assert_eq!(
129 check_auth(&keys, Some("ro-key"), "write"),
130 AuthResult::InsufficientPermission
131 );
132 }
133
134 #[test]
135 fn test_valid_read() {
136 let keys = test_keys();
137 assert_eq!(
138 check_auth(&keys, Some("ro-key"), "read"),
139 AuthResult::Allowed
140 );
141 assert_eq!(
142 check_auth(&keys, Some("rw-key"), "read"),
143 AuthResult::Allowed
144 );
145 }
146
147 #[test]
148 fn test_valid_write() {
149 let keys = test_keys();
150 assert_eq!(
151 check_auth(&keys, Some("rw-key"), "write"),
152 AuthResult::Allowed
153 );
154 }
155}