1use crate::container::App;
35use crate::http::{HttpResponse, Request, Response};
36use crate::middleware::{Middleware, Next};
37use async_trait::async_trait;
38use sha2::{Digest, Sha256};
39use std::sync::Arc;
40use subtle::ConstantTimeEq;
41
42const BASE62: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
43
44#[derive(Debug, Clone)]
49pub struct GeneratedApiKey {
50 pub raw_key: String,
52 pub prefix: String,
54 pub hashed_key: String,
56}
57
58#[derive(Debug, Clone)]
60pub struct ApiKeyInfo {
61 pub id: i64,
63 pub name: String,
65 pub scopes: Vec<String>,
67}
68
69#[async_trait]
105pub trait ApiKeyProvider: Send + Sync + 'static {
106 async fn verify_key(&self, raw_key: &str) -> Result<ApiKeyInfo, ()>;
108}
109
110pub fn generate_api_key(environment: &str) -> GeneratedApiKey {
115 let prefix_str = format!("fe_{environment}_");
116 let mut rng = rand::thread_rng();
117 let random: String = (0..43)
118 .map(|_| {
119 let idx = rand::Rng::gen_range(&mut rng, 0..62);
120 BASE62[idx] as char
121 })
122 .collect();
123
124 let raw_key = format!("{prefix_str}{random}");
125 let prefix = raw_key[..16].to_string();
126 let hashed_key = hash_api_key(&raw_key);
127
128 GeneratedApiKey {
129 raw_key,
130 prefix,
131 hashed_key,
132 }
133}
134
135pub fn hash_api_key(raw_key: &str) -> String {
137 let mut hasher = Sha256::new();
138 hasher.update(raw_key.as_bytes());
139 format!("{:x}", hasher.finalize())
140}
141
142pub fn verify_api_key_hash(raw_key: &str, stored_hash: &str) -> bool {
146 let incoming_hash = hash_api_key(raw_key);
147 incoming_hash
148 .as_bytes()
149 .ct_eq(stored_hash.as_bytes())
150 .into()
151}
152
153pub struct ApiKeyMiddleware {
173 required_scopes: Vec<String>,
174}
175
176impl ApiKeyMiddleware {
177 pub fn new() -> Self {
179 Self {
180 required_scopes: Vec::new(),
181 }
182 }
183
184 pub fn scopes(scopes: &[&str]) -> Self {
186 Self {
187 required_scopes: scopes.iter().map(|s| s.to_string()).collect(),
188 }
189 }
190}
191
192impl Default for ApiKeyMiddleware {
193 fn default() -> Self {
194 Self::new()
195 }
196}
197
198fn extract_bearer_token(request: &Request) -> Result<&str, HttpResponse> {
200 let header = request.header("Authorization").ok_or_else(|| {
201 HttpResponse::json(serde_json::json!({
202 "error": "API key required",
203 "hint": "Include Authorization: Bearer <key> header"
204 }))
205 .status(401)
206 })?;
207
208 let token = header.strip_prefix("Bearer ").ok_or_else(|| {
209 HttpResponse::json(serde_json::json!({
210 "error": "Invalid API key format"
211 }))
212 .status(401)
213 })?;
214
215 if token.is_empty() {
216 return Err(HttpResponse::json(serde_json::json!({
217 "error": "Invalid API key format"
218 }))
219 .status(401));
220 }
221
222 Ok(token)
223}
224
225#[async_trait]
226impl Middleware for ApiKeyMiddleware {
227 async fn handle(&self, mut request: Request, next: Next) -> Response {
228 let raw_key = extract_bearer_token(&request)?;
229
230 let provider: Arc<dyn ApiKeyProvider> =
231 App::make::<dyn ApiKeyProvider>().ok_or_else(|| {
232 HttpResponse::json(serde_json::json!({
233 "error": "API key authentication not configured"
234 }))
235 .status(500)
236 })?;
237
238 let key_info = provider.verify_key(raw_key).await.map_err(|()| {
239 HttpResponse::json(serde_json::json!({
240 "error": "Invalid API key"
241 }))
242 .status(401)
243 })?;
244
245 if !self.required_scopes.is_empty() {
247 let has_wildcard = key_info.scopes.iter().any(|s| s == "*");
248 if !has_wildcard {
249 let missing: Vec<&String> = self
250 .required_scopes
251 .iter()
252 .filter(|required| !key_info.scopes.contains(required))
253 .collect();
254
255 if !missing.is_empty() {
256 return Err(HttpResponse::json(serde_json::json!({
257 "error": "Insufficient permissions",
258 "required": self.required_scopes,
259 "provided": key_info.scopes
260 }))
261 .status(403));
262 }
263 }
264 }
265
266 request.insert(key_info);
267 next(request).await
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn generate_api_key_format() {
277 let key = generate_api_key("live");
278 assert!(key.raw_key.starts_with("fe_live_"));
279 assert_eq!(key.raw_key.len(), 51);
281 }
282
283 #[test]
284 fn generate_api_key_test_env() {
285 let key = generate_api_key("test");
286 assert!(key.raw_key.starts_with("fe_test_"));
287 assert_eq!(key.raw_key.len(), 51);
288 }
289
290 #[test]
291 fn generate_api_key_prefix_length() {
292 let key = generate_api_key("live");
293 assert_eq!(key.prefix.len(), 16);
294 assert!(key.prefix.starts_with("fe_live_"));
295 }
296
297 #[test]
298 fn generate_api_key_hash_is_sha256_hex() {
299 let key = generate_api_key("live");
300 assert_eq!(key.hashed_key.len(), 64);
302 assert!(key.hashed_key.chars().all(|c| c.is_ascii_hexdigit()));
303 }
304
305 #[test]
306 fn generate_api_key_uniqueness() {
307 let key1 = generate_api_key("live");
308 let key2 = generate_api_key("live");
309 assert_ne!(key1.raw_key, key2.raw_key);
310 assert_ne!(key1.hashed_key, key2.hashed_key);
311 }
312
313 #[test]
314 fn generate_api_key_base62_chars_only() {
315 let key = generate_api_key("live");
316 let random_part = &key.raw_key[8..]; assert!(random_part.chars().all(|c| c.is_ascii_alphanumeric()));
318 }
319
320 #[test]
321 fn hash_api_key_deterministic() {
322 let hash1 = hash_api_key("fe_live_abc123");
323 let hash2 = hash_api_key("fe_live_abc123");
324 assert_eq!(hash1, hash2);
325 }
326
327 #[test]
328 fn hash_api_key_different_inputs() {
329 let hash1 = hash_api_key("fe_live_abc123");
330 let hash2 = hash_api_key("fe_live_xyz789");
331 assert_ne!(hash1, hash2);
332 }
333
334 #[test]
335 fn verify_api_key_hash_correct() {
336 let key = generate_api_key("live");
337 assert!(verify_api_key_hash(&key.raw_key, &key.hashed_key));
338 }
339
340 #[test]
341 fn verify_api_key_hash_wrong_key() {
342 let key = generate_api_key("live");
343 assert!(!verify_api_key_hash("wrong_key", &key.hashed_key));
344 }
345
346 #[test]
347 fn verify_api_key_hash_wrong_hash() {
348 let key = generate_api_key("live");
349 assert!(!verify_api_key_hash(
350 &key.raw_key,
351 "0000000000000000000000000000000000000000000000000000000000000000"
352 ));
353 }
354
355 #[test]
356 fn verify_api_key_hash_manual() {
357 let raw = "fe_test_SomeRandomBase62String";
358 let hash = hash_api_key(raw);
359 assert!(verify_api_key_hash(raw, &hash));
360 }
361}