Skip to main content

shaperail_runtime/auth/
api_key.rs

1use std::collections::HashMap;
2
3use super::extractor::AuthenticatedUser;
4
5/// In-memory store mapping API keys to authenticated users.
6///
7/// API keys are provided via `X-API-Key` header as an alternative to JWT.
8/// Keys are loaded at startup (from config or environment).
9#[derive(Debug, Clone)]
10pub struct ApiKeyStore {
11    keys: HashMap<String, AuthenticatedUser>,
12}
13
14impl ApiKeyStore {
15    /// Creates an empty API key store.
16    pub fn new() -> Self {
17        Self {
18            keys: HashMap::new(),
19        }
20    }
21
22    /// Registers an API key mapping to a user identity.
23    pub fn insert(&mut self, key: String, user_id: String, role: String) {
24        self.keys
25            .insert(key, AuthenticatedUser { id: user_id, role });
26    }
27
28    /// Looks up an API key and returns the associated user, if valid.
29    pub fn lookup(&self, key: &str) -> Option<AuthenticatedUser> {
30        self.keys.get(key).cloned()
31    }
32
33    /// Returns the number of registered API keys.
34    pub fn len(&self) -> usize {
35        self.keys.len()
36    }
37
38    /// Returns true if no API keys are registered.
39    pub fn is_empty(&self) -> bool {
40        self.keys.is_empty()
41    }
42}
43
44impl Default for ApiKeyStore {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn insert_and_lookup() {
56        let mut store = ApiKeyStore::new();
57        store.insert(
58            "sk-test-123".to_string(),
59            "user-1".to_string(),
60            "admin".to_string(),
61        );
62        let user = store.lookup("sk-test-123");
63        assert!(user.is_some());
64        let user = user.unwrap();
65        assert_eq!(user.id, "user-1");
66        assert_eq!(user.role, "admin");
67    }
68
69    #[test]
70    fn lookup_missing_returns_none() {
71        let store = ApiKeyStore::new();
72        assert!(store.lookup("nonexistent").is_none());
73    }
74
75    #[test]
76    fn len_and_is_empty() {
77        let mut store = ApiKeyStore::new();
78        assert!(store.is_empty());
79        assert_eq!(store.len(), 0);
80
81        store.insert("k1".to_string(), "u1".to_string(), "admin".to_string());
82        assert!(!store.is_empty());
83        assert_eq!(store.len(), 1);
84    }
85}