ipfrs_interface/
auth.rs

1//! Authentication and Authorization Module
2//!
3//! Provides JWT-based authentication and role-based access control (RBAC)
4//! for the IPFRS HTTP API.
5
6use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9use std::time::{SystemTime, UNIX_EPOCH};
10use uuid::Uuid;
11
12/// Authentication errors
13#[derive(Debug, thiserror::Error)]
14pub enum AuthError {
15    #[error("Invalid credentials")]
16    InvalidCredentials,
17
18    #[error("Invalid token: {0}")]
19    InvalidToken(String),
20
21    #[error("Token expired")]
22    TokenExpired,
23
24    #[error("Insufficient permissions")]
25    InsufficientPermissions,
26
27    #[error("User not found")]
28    UserNotFound,
29
30    #[error("Hashing error: {0}")]
31    HashError(String),
32
33    #[error("JWT error: {0}")]
34    JwtError(#[from] jsonwebtoken::errors::Error),
35}
36
37pub type AuthResult<T> = Result<T, AuthError>;
38
39/// User roles in the system
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum Role {
43    /// Administrator - full access to all operations
44    Admin,
45    /// User - can read and write data
46    User,
47    /// Read-only access
48    ReadOnly,
49}
50
51impl Role {
52    /// Check if this role has at least the required permission level
53    pub fn has_permission(&self, required: Role) -> bool {
54        matches!(
55            (self, required),
56            (Role::Admin, _)
57                | (Role::User, Role::User)
58                | (Role::User, Role::ReadOnly)
59                | (Role::ReadOnly, Role::ReadOnly)
60        )
61    }
62}
63
64/// Permissions that can be granted to users
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub enum Permission {
68    // Block operations
69    BlockRead,
70    BlockWrite,
71    BlockDelete,
72
73    // Semantic operations
74    SemanticIndex,
75    SemanticSearch,
76
77    // Logic operations
78    LogicRead,
79    LogicWrite,
80
81    // Network operations
82    NetworkRead,
83    NetworkWrite,
84
85    // System operations
86    SystemRead,
87    SystemWrite,
88    SystemAdmin,
89}
90
91impl Permission {
92    /// Get all permissions for a role
93    pub fn for_role(role: Role) -> HashSet<Permission> {
94        match role {
95            Role::Admin => {
96                // Admin has all permissions
97                vec![
98                    Permission::BlockRead,
99                    Permission::BlockWrite,
100                    Permission::BlockDelete,
101                    Permission::SemanticIndex,
102                    Permission::SemanticSearch,
103                    Permission::LogicRead,
104                    Permission::LogicWrite,
105                    Permission::NetworkRead,
106                    Permission::NetworkWrite,
107                    Permission::SystemRead,
108                    Permission::SystemWrite,
109                    Permission::SystemAdmin,
110                ]
111                .into_iter()
112                .collect()
113            }
114            Role::User => {
115                // User can read and write, but not admin operations
116                vec![
117                    Permission::BlockRead,
118                    Permission::BlockWrite,
119                    Permission::SemanticIndex,
120                    Permission::SemanticSearch,
121                    Permission::LogicRead,
122                    Permission::LogicWrite,
123                    Permission::NetworkRead,
124                    Permission::SystemRead,
125                ]
126                .into_iter()
127                .collect()
128            }
129            Role::ReadOnly => {
130                // Read-only can only read
131                vec![
132                    Permission::BlockRead,
133                    Permission::SemanticSearch,
134                    Permission::LogicRead,
135                    Permission::NetworkRead,
136                    Permission::SystemRead,
137                ]
138                .into_iter()
139                .collect()
140            }
141        }
142    }
143}
144
145/// User information stored in the system
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct User {
148    /// Unique user ID
149    pub id: Uuid,
150    /// Username
151    pub username: String,
152    /// Password hash (bcrypt)
153    #[serde(skip_serializing)]
154    pub password_hash: String,
155    /// User role
156    pub role: Role,
157    /// Custom permissions (in addition to role permissions)
158    pub custom_permissions: HashSet<Permission>,
159    /// Whether the user is active
160    pub active: bool,
161    /// Creation timestamp
162    pub created_at: u64,
163}
164
165impl User {
166    /// Create a new user with hashed password
167    pub fn new(username: String, password: &str, role: Role) -> AuthResult<Self> {
168        let password_hash = bcrypt::hash(password, bcrypt::DEFAULT_COST)
169            .map_err(|e| AuthError::HashError(e.to_string()))?;
170
171        Ok(Self {
172            id: Uuid::new_v4(),
173            username,
174            password_hash,
175            role,
176            custom_permissions: HashSet::new(),
177            active: true,
178            created_at: SystemTime::now()
179                .duration_since(UNIX_EPOCH)
180                .unwrap()
181                .as_secs(),
182        })
183    }
184
185    /// Verify password
186    pub fn verify_password(&self, password: &str) -> AuthResult<bool> {
187        bcrypt::verify(password, &self.password_hash)
188            .map_err(|e| AuthError::HashError(e.to_string()))
189    }
190
191    /// Get all permissions for this user (role + custom)
192    pub fn permissions(&self) -> HashSet<Permission> {
193        let mut perms = Permission::for_role(self.role);
194        perms.extend(self.custom_permissions.iter().copied());
195        perms
196    }
197
198    /// Check if user has a specific permission
199    pub fn has_permission(&self, permission: Permission) -> bool {
200        self.permissions().contains(&permission)
201    }
202}
203
204/// JWT claims structure
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct Claims {
207    /// Subject (user ID)
208    pub sub: String,
209    /// Username
210    pub username: String,
211    /// User role
212    pub role: Role,
213    /// OAuth2 scopes (optional)
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub scope: Option<String>,
216    /// Issued at (UNIX timestamp)
217    pub iat: u64,
218    /// Expiration time (UNIX timestamp)
219    pub exp: u64,
220}
221
222impl Claims {
223    /// Create new claims for a user
224    pub fn new(user: &User, expiration_hours: u64) -> Self {
225        let now = SystemTime::now()
226            .duration_since(UNIX_EPOCH)
227            .unwrap()
228            .as_secs();
229
230        Self {
231            sub: user.id.to_string(),
232            username: user.username.clone(),
233            role: user.role,
234            scope: None,
235            iat: now,
236            exp: now + (expiration_hours * 3600),
237        }
238    }
239
240    /// Create new claims with OAuth2 scopes
241    pub fn new_with_scopes(sub: &str, scope: &str, expiration_hours: usize) -> Self {
242        let now = SystemTime::now()
243            .duration_since(UNIX_EPOCH)
244            .unwrap()
245            .as_secs();
246
247        Self {
248            sub: sub.to_string(),
249            username: sub.to_string(), // Use sub as username for OAuth2 tokens
250            role: Role::User,          // Default role for OAuth2
251            scope: Some(scope.to_string()),
252            iat: now,
253            exp: now + ((expiration_hours as u64) * 3600),
254        }
255    }
256
257    /// Check if token is expired
258    pub fn is_expired(&self) -> bool {
259        let now = SystemTime::now()
260            .duration_since(UNIX_EPOCH)
261            .unwrap()
262            .as_secs();
263        now > self.exp
264    }
265}
266
267/// JWT token manager
268#[derive(Clone)]
269pub struct JwtManager {
270    encoding_key: EncodingKey,
271    decoding_key: DecodingKey,
272    validation: Validation,
273}
274
275impl JwtManager {
276    /// Create a new JWT manager with a secret key
277    pub fn new(secret: &[u8]) -> Self {
278        let mut validation = Validation::new(Algorithm::HS256);
279        validation.validate_exp = true;
280
281        Self {
282            encoding_key: EncodingKey::from_secret(secret),
283            decoding_key: DecodingKey::from_secret(secret),
284            validation,
285        }
286    }
287
288    /// Generate a JWT token for a user
289    pub fn generate_token(&self, user: &User, expiration_hours: u64) -> AuthResult<String> {
290        let claims = Claims::new(user, expiration_hours);
291        let token = encode(&Header::default(), &claims, &self.encoding_key)?;
292        Ok(token)
293    }
294
295    /// Generate a JWT token with OAuth2 scopes
296    pub fn generate_token_with_scopes(
297        &self,
298        sub: &str,
299        scope: &str,
300        expiration_hours: usize,
301    ) -> AuthResult<String> {
302        let claims = Claims::new_with_scopes(sub, scope, expiration_hours);
303        let token = encode(&Header::default(), &claims, &self.encoding_key)?;
304        Ok(token)
305    }
306
307    /// Validate and decode a JWT token
308    pub fn validate_token(&self, token: &str) -> AuthResult<Claims> {
309        let token_data = decode::<Claims>(token, &self.decoding_key, &self.validation)?;
310
311        if token_data.claims.is_expired() {
312            return Err(AuthError::TokenExpired);
313        }
314
315        Ok(token_data.claims)
316    }
317}
318
319/// User store (in-memory for now, should be persisted in production)
320#[derive(Clone)]
321pub struct UserStore {
322    users: dashmap::DashMap<String, User>,
323}
324
325impl UserStore {
326    /// Create a new user store
327    pub fn new() -> Self {
328        Self {
329            users: dashmap::DashMap::new(),
330        }
331    }
332
333    /// Add a user to the store
334    pub fn add_user(&self, user: User) -> AuthResult<()> {
335        if self.users.contains_key(&user.username) {
336            return Err(AuthError::InvalidCredentials);
337        }
338        self.users.insert(user.username.clone(), user);
339        Ok(())
340    }
341
342    /// Get a user by username
343    pub fn get_user(&self, username: &str) -> AuthResult<User> {
344        self.users
345            .get(username)
346            .map(|entry| entry.clone())
347            .ok_or(AuthError::UserNotFound)
348    }
349
350    /// Get a user by ID
351    pub fn get_by_id(&self, user_id: &uuid::Uuid) -> AuthResult<User> {
352        for entry in self.users.iter() {
353            let user = entry.value();
354            if user.id == *user_id {
355                return Ok(user.clone());
356            }
357        }
358        Err(AuthError::UserNotFound)
359    }
360
361    /// Authenticate a user with username and password
362    pub fn authenticate(&self, username: &str, password: &str) -> AuthResult<User> {
363        let user = self.get_user(username)?;
364
365        if !user.active {
366            return Err(AuthError::InvalidCredentials);
367        }
368
369        if !user.verify_password(password)? {
370            return Err(AuthError::InvalidCredentials);
371        }
372
373        Ok(user)
374    }
375
376    /// Update user permissions
377    pub fn update_permissions(
378        &self,
379        username: &str,
380        permissions: HashSet<Permission>,
381    ) -> AuthResult<()> {
382        self.users
383            .get_mut(username)
384            .map(|mut entry| {
385                entry.custom_permissions = permissions;
386            })
387            .ok_or(AuthError::UserNotFound)
388    }
389
390    /// Deactivate a user
391    pub fn deactivate_user(&self, username: &str) -> AuthResult<()> {
392        self.users
393            .get_mut(username)
394            .map(|mut entry| {
395                entry.active = false;
396            })
397            .ok_or(AuthError::UserNotFound)
398    }
399}
400
401impl Default for UserStore {
402    fn default() -> Self {
403        Self::new()
404    }
405}
406
407/// API Key for long-lived authentication
408#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct ApiKey {
410    /// API key ID
411    pub id: Uuid,
412    /// The actual key (hashed in storage)
413    #[serde(skip_serializing)]
414    pub key_hash: String,
415    /// Key prefix for identification (first 8 chars of key)
416    pub prefix: String,
417    /// Associated user ID
418    pub user_id: Uuid,
419    /// Optional key name/description
420    pub name: String,
421    /// Creation timestamp
422    pub created_at: u64,
423    /// Last used timestamp
424    pub last_used_at: Option<u64>,
425    /// Whether the key is active
426    pub active: bool,
427}
428
429impl ApiKey {
430    /// Generate a new API key
431    pub fn new(user_id: Uuid, name: String) -> AuthResult<(Self, String)> {
432        let key_id = Uuid::new_v4();
433
434        // Generate random API key (32 bytes = 64 hex chars)
435        let key_bytes: [u8; 32] = rand::random();
436        let raw_key = format!("ipfrs_{}", hex::encode(key_bytes));
437
438        // Hash the key for storage
439        let key_hash = bcrypt::hash(&raw_key, bcrypt::DEFAULT_COST)
440            .map_err(|e| AuthError::HashError(e.to_string()))?;
441
442        // Store prefix for identification
443        let prefix = raw_key.chars().take(12).collect();
444
445        let api_key = Self {
446            id: key_id,
447            key_hash,
448            prefix,
449            user_id,
450            name,
451            created_at: SystemTime::now()
452                .duration_since(UNIX_EPOCH)
453                .unwrap()
454                .as_secs(),
455            last_used_at: None,
456            active: true,
457        };
458
459        Ok((api_key, raw_key))
460    }
461
462    /// Verify an API key
463    pub fn verify(&self, key: &str) -> AuthResult<bool> {
464        bcrypt::verify(key, &self.key_hash).map_err(|e| AuthError::HashError(e.to_string()))
465    }
466
467    /// Update last used timestamp
468    pub fn mark_used(&mut self) {
469        self.last_used_at = Some(
470            SystemTime::now()
471                .duration_since(UNIX_EPOCH)
472                .unwrap()
473                .as_secs(),
474        );
475    }
476}
477
478/// API Key store
479#[derive(Clone)]
480pub struct ApiKeyStore {
481    keys: dashmap::DashMap<Uuid, ApiKey>,
482    user_keys: dashmap::DashMap<Uuid, Vec<Uuid>>, // user_id -> key_ids
483}
484
485impl ApiKeyStore {
486    /// Create a new API key store
487    pub fn new() -> Self {
488        Self {
489            keys: dashmap::DashMap::new(),
490            user_keys: dashmap::DashMap::new(),
491        }
492    }
493
494    /// Add an API key
495    pub fn add_key(&self, key: ApiKey) -> AuthResult<()> {
496        let user_id = key.user_id;
497        let key_id = key.id;
498
499        self.keys.insert(key_id, key);
500
501        // Add to user's key list
502        self.user_keys.entry(user_id).or_default().push(key_id);
503
504        Ok(())
505    }
506
507    /// Get an API key by ID
508    pub fn get_key(&self, key_id: &Uuid) -> AuthResult<ApiKey> {
509        self.keys
510            .get(key_id)
511            .map(|entry| entry.clone())
512            .ok_or(AuthError::InvalidCredentials)
513    }
514
515    /// Authenticate with API key
516    pub fn authenticate(&self, key: &str) -> AuthResult<(ApiKey, Uuid)> {
517        // Extract prefix for faster lookup
518        let prefix: String = key.chars().take(12).collect();
519
520        // Find key by prefix
521        for entry in self.keys.iter() {
522            let api_key = entry.value();
523            if api_key.prefix == prefix && api_key.active && api_key.verify(key)? {
524                // Update last used
525                let mut key_mut = self.keys.get_mut(&api_key.id).unwrap();
526                key_mut.mark_used();
527
528                return Ok((api_key.clone(), api_key.user_id));
529            }
530        }
531
532        Err(AuthError::InvalidCredentials)
533    }
534
535    /// List API keys for a user
536    pub fn list_user_keys(&self, user_id: &Uuid) -> Vec<ApiKey> {
537        if let Some(key_ids) = self.user_keys.get(user_id) {
538            key_ids
539                .iter()
540                .filter_map(|id| self.keys.get(id).map(|e| e.clone()))
541                .collect()
542        } else {
543            Vec::new()
544        }
545    }
546
547    /// Revoke (deactivate) an API key
548    pub fn revoke_key(&self, key_id: &Uuid) -> AuthResult<()> {
549        self.keys
550            .get_mut(key_id)
551            .map(|mut entry| {
552                entry.active = false;
553            })
554            .ok_or(AuthError::InvalidCredentials)
555    }
556
557    /// Delete an API key
558    pub fn delete_key(&self, key_id: &Uuid) -> AuthResult<()> {
559        if let Some((_, key)) = self.keys.remove(key_id) {
560            // Remove from user's key list
561            if let Some(mut user_keys) = self.user_keys.get_mut(&key.user_id) {
562                user_keys.retain(|id| id != key_id);
563            }
564            Ok(())
565        } else {
566            Err(AuthError::InvalidCredentials)
567        }
568    }
569}
570
571impl Default for ApiKeyStore {
572    fn default() -> Self {
573        Self::new()
574    }
575}
576
577/// Authentication middleware state
578#[derive(Clone)]
579pub struct AuthState {
580    pub jwt_manager: JwtManager,
581    pub user_store: UserStore,
582    pub api_key_store: ApiKeyStore,
583}
584
585impl AuthState {
586    /// Create new auth state with a secret key
587    pub fn new(secret: &[u8]) -> Self {
588        Self {
589            jwt_manager: JwtManager::new(secret),
590            user_store: UserStore::new(),
591            api_key_store: ApiKeyStore::new(),
592        }
593    }
594
595    /// Initialize with a default admin user
596    pub fn with_default_admin(secret: &[u8], admin_password: &str) -> AuthResult<Self> {
597        let state = Self::new(secret);
598
599        // Create default admin user
600        let admin = User::new("admin".to_string(), admin_password, Role::Admin)?;
601        state.user_store.add_user(admin)?;
602
603        Ok(state)
604    }
605}
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610
611    #[test]
612    fn test_role_permissions() {
613        assert!(Role::Admin.has_permission(Role::User));
614        assert!(Role::Admin.has_permission(Role::ReadOnly));
615        assert!(Role::User.has_permission(Role::ReadOnly));
616        assert!(!Role::ReadOnly.has_permission(Role::User));
617    }
618
619    #[test]
620    fn test_user_creation() {
621        let user = User::new("test".to_string(), "password123", Role::User).unwrap();
622        assert_eq!(user.username, "test");
623        assert_eq!(user.role, Role::User);
624        assert!(user.active);
625        assert!(user.verify_password("password123").unwrap());
626        assert!(!user.verify_password("wrong").unwrap());
627    }
628
629    #[test]
630    fn test_user_permissions() {
631        let user = User::new("test".to_string(), "password123", Role::User).unwrap();
632        assert!(user.has_permission(Permission::BlockRead));
633        assert!(user.has_permission(Permission::BlockWrite));
634        assert!(!user.has_permission(Permission::BlockDelete));
635        assert!(!user.has_permission(Permission::SystemAdmin));
636    }
637
638    #[test]
639    fn test_jwt_generation_and_validation() {
640        let secret = b"test_secret_key_32_bytes_long!!!";
641        let manager = JwtManager::new(secret);
642        let user = User::new("test".to_string(), "password123", Role::User).unwrap();
643
644        let token = manager.generate_token(&user, 24).unwrap();
645        let claims = manager.validate_token(&token).unwrap();
646
647        assert_eq!(claims.username, "test");
648        assert_eq!(claims.role, Role::User);
649        assert!(!claims.is_expired());
650    }
651
652    #[test]
653    fn test_user_store() {
654        let store = UserStore::new();
655        let user = User::new("test".to_string(), "password123", Role::User).unwrap();
656
657        store.add_user(user).unwrap();
658
659        let authenticated = store.authenticate("test", "password123").unwrap();
660        assert_eq!(authenticated.username, "test");
661
662        assert!(store.authenticate("test", "wrong").is_err());
663        assert!(store.authenticate("nonexistent", "password123").is_err());
664    }
665}