elif_auth/
traits.rs

1//! Core authentication and authorization traits
2
3use crate::{AuthError, AuthResult};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Trait for types that can be authenticated
10#[async_trait]
11pub trait Authenticatable: Send + Sync + Clone {
12    type Id: Clone + Send + Sync + std::fmt::Debug + PartialEq;
13    type Credentials: Send + Sync;
14
15    /// Get the user's unique identifier
16    fn id(&self) -> &Self::Id;
17
18    /// Get the user's username/email for authentication
19    fn username(&self) -> &str;
20
21    /// Check if the user account is active
22    fn is_active(&self) -> bool {
23        true
24    }
25
26    /// Check if the user account is locked
27    fn is_locked(&self) -> bool {
28        false
29    }
30
31    /// Get the user's role names
32    fn roles(&self) -> Vec<String> {
33        vec![]
34    }
35
36    /// Get the user's direct permissions
37    fn permissions(&self) -> Vec<String> {
38        vec![]
39    }
40
41    /// Verify credentials against this user
42    async fn verify_credentials(&self, credentials: &Self::Credentials) -> AuthResult<bool>;
43
44    /// Get additional user data for token/session storage
45    fn additional_data(&self) -> HashMap<String, serde_json::Value> {
46        HashMap::new()
47    }
48}
49
50/// Authentication provider trait for different auth mechanisms
51#[async_trait]
52pub trait AuthProvider<User>: Send + Sync
53where
54    User: Authenticatable,
55{
56    type Token: Clone + Send + Sync + std::fmt::Debug;
57    type Credentials: Send + Sync;
58
59    /// Authenticate user with credentials and return a token
60    async fn authenticate(
61        &self,
62        credentials: &Self::Credentials,
63    ) -> AuthResult<AuthenticationResult<User, Self::Token>>;
64
65    /// Validate an existing token and return user information
66    async fn validate_token(&self, token: &Self::Token) -> AuthResult<User>;
67
68    /// Refresh a token if supported
69    async fn refresh_token(&self, _token: &Self::Token) -> AuthResult<Self::Token> {
70        Err(AuthError::token_error("Token refresh not supported"))
71    }
72
73    /// Revoke a token
74    async fn revoke_token(&self, _token: &Self::Token) -> AuthResult<()> {
75        Ok(()) // Default implementation does nothing
76    }
77
78    /// Get provider name for identification
79    fn provider_name(&self) -> &str;
80}
81
82/// Result of authentication attempt
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct AuthenticationResult<User, Token> {
85    /// The authenticated user
86    pub user: User,
87
88    /// The authentication token
89    pub token: Token,
90
91    /// Optional refresh token
92    pub refresh_token: Option<Token>,
93
94    /// Whether MFA is required
95    pub requires_mfa: bool,
96
97    /// MFA setup information if required
98    pub mfa_setup: Option<MfaSetup>,
99
100    /// Token expiration time
101    pub expires_at: Option<DateTime<Utc>>,
102
103    /// Additional metadata
104    pub metadata: HashMap<String, serde_json::Value>,
105}
106
107/// MFA setup information
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct MfaSetup {
110    /// TOTP secret key
111    pub secret: String,
112
113    /// QR code URL for easy setup
114    pub qr_code_url: String,
115
116    /// Backup codes
117    pub backup_codes: Vec<String>,
118}
119
120/// Authorization provider trait for role-based access control
121#[async_trait]
122pub trait AuthorizationProvider: Send + Sync {
123    type User: Authenticatable;
124    type Role: Send + Sync + Clone;
125    type Permission: Send + Sync + Clone;
126
127    /// Check if user has a specific role
128    async fn has_role(&self, user: &Self::User, role: &str) -> AuthResult<bool>;
129
130    /// Check if user has a specific permission
131    async fn has_permission(&self, user: &Self::User, permission: &str) -> AuthResult<bool>;
132
133    /// Check if user has a specific permission with context
134    async fn has_permission_with_context(
135        &self,
136        user: &Self::User,
137        resource: &str,
138        action: &str,
139        context: Option<&HashMap<String, serde_json::Value>>,
140    ) -> AuthResult<bool>;
141
142    /// Check if user has any of the specified roles
143    async fn has_any_role(&self, user: &Self::User, roles: &[String]) -> AuthResult<bool> {
144        for role in roles {
145            if self.has_role(user, role).await? {
146                return Ok(true);
147            }
148        }
149        Ok(false)
150    }
151
152    /// Check if user has all of the specified roles
153    async fn has_all_roles(&self, user: &Self::User, roles: &[String]) -> AuthResult<bool> {
154        for role in roles {
155            if !self.has_role(user, role).await? {
156                return Ok(false);
157            }
158        }
159        Ok(true)
160    }
161
162    /// Check if user has any of the specified permissions
163    async fn has_any_permission(
164        &self,
165        user: &Self::User,
166        permissions: &[String],
167    ) -> AuthResult<bool> {
168        for permission in permissions {
169            if self.has_permission(user, permission).await? {
170                return Ok(true);
171            }
172        }
173        Ok(false)
174    }
175
176    /// Check if user has all of the specified permissions
177    async fn has_all_permissions(
178        &self,
179        user: &Self::User,
180        permissions: &[String],
181    ) -> AuthResult<bool> {
182        for permission in permissions {
183            if !self.has_permission(user, permission).await? {
184                return Ok(false);
185            }
186        }
187        Ok(true)
188    }
189
190    /// Get all roles for a user
191    async fn get_user_roles(&self, user: &Self::User) -> AuthResult<Vec<Self::Role>>;
192
193    /// Get all permissions for a user (direct and through roles)
194    async fn get_user_permissions(&self, user: &Self::User) -> AuthResult<Vec<Self::Permission>>;
195}
196
197/// Session storage trait for session-based authentication
198#[async_trait]
199pub trait SessionStorage: Send + Sync {
200    type SessionId: Clone + Send + Sync + std::fmt::Debug + PartialEq;
201    type SessionData: Clone + Send + Sync;
202
203    /// Create a new session
204    async fn create_session(
205        &self,
206        data: Self::SessionData,
207        expires_at: DateTime<Utc>,
208    ) -> AuthResult<Self::SessionId>;
209
210    /// Get session data by ID
211    async fn get_session(&self, id: &Self::SessionId) -> AuthResult<Option<Self::SessionData>>;
212
213    /// Update session data
214    async fn update_session(
215        &self,
216        id: &Self::SessionId,
217        data: Self::SessionData,
218        expires_at: DateTime<Utc>,
219    ) -> AuthResult<()>;
220
221    /// Delete a session
222    async fn delete_session(&self, id: &Self::SessionId) -> AuthResult<()>;
223
224    /// Clean up expired sessions
225    async fn cleanup_expired_sessions(&self) -> AuthResult<u64>;
226
227    /// Get session expiration time
228    async fn get_session_expiry(&self, id: &Self::SessionId) -> AuthResult<Option<DateTime<Utc>>>;
229
230    /// Extend session expiration
231    async fn extend_session(
232        &self,
233        id: &Self::SessionId,
234        expires_at: DateTime<Utc>,
235    ) -> AuthResult<()>;
236}
237
238/// Multi-factor authentication provider
239#[async_trait]
240pub trait MfaProvider: Send + Sync {
241    type User: Authenticatable;
242    type Secret: Clone + Send + Sync;
243    type Code: Send + Sync;
244
245    /// Generate MFA setup information for a user
246    async fn setup_mfa(&self, user: &Self::User) -> AuthResult<MfaSetup>;
247
248    /// Verify MFA code
249    async fn verify_code(
250        &self,
251        user: &Self::User,
252        code: &Self::Code,
253        secret: &Self::Secret,
254    ) -> AuthResult<bool>;
255
256    /// Generate backup codes
257    async fn generate_backup_codes(&self, user: &Self::User) -> AuthResult<Vec<String>>;
258
259    /// Verify backup code
260    async fn verify_backup_code(&self, user: &Self::User, code: &str) -> AuthResult<bool>;
261
262    /// Check if user has MFA enabled
263    async fn is_mfa_enabled(&self, user: &Self::User) -> AuthResult<bool>;
264
265    /// Disable MFA for a user
266    async fn disable_mfa(&self, user: &Self::User) -> AuthResult<()>;
267}
268
269/// Password hasher trait for different hashing algorithms
270pub trait PasswordHasher: Send + Sync {
271    /// Hash a password
272    fn hash_password(&self, password: &str) -> AuthResult<String>;
273
274    /// Verify a password against its hash
275    fn verify_password(&self, password: &str, hash: &str) -> AuthResult<bool>;
276
277    /// Get the hasher name
278    fn hasher_name(&self) -> &str;
279}
280
281/// User context extracted from authentication
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct UserContext {
284    /// User ID
285    pub user_id: String,
286
287    /// Username/email
288    pub username: String,
289
290    /// User roles
291    pub roles: Vec<String>,
292
293    /// User permissions
294    pub permissions: Vec<String>,
295
296    /// Authentication provider used
297    pub auth_provider: String,
298
299    /// Authentication timestamp
300    pub authenticated_at: DateTime<Utc>,
301
302    /// Token expiration (if applicable)
303    pub expires_at: Option<DateTime<Utc>>,
304
305    /// Additional user data
306    pub additional_data: HashMap<String, serde_json::Value>,
307}
308
309impl UserContext {
310    /// Create a new user context
311    pub fn new(user_id: String, username: String, auth_provider: String) -> Self {
312        Self {
313            user_id,
314            username,
315            roles: vec![],
316            permissions: vec![],
317            auth_provider,
318            authenticated_at: Utc::now(),
319            expires_at: None,
320            additional_data: HashMap::new(),
321        }
322    }
323
324    /// Check if user has a specific role
325    pub fn has_role(&self, role: &str) -> bool {
326        self.roles.contains(&role.to_string())
327    }
328
329    /// Check if user has a specific permission
330    pub fn has_permission(&self, permission: &str) -> bool {
331        self.permissions.contains(&permission.to_string())
332    }
333
334    /// Check if user has any of the specified roles
335    pub fn has_any_role(&self, roles: &[String]) -> bool {
336        roles.iter().any(|role| self.has_role(role))
337    }
338
339    /// Check if user has all of the specified roles
340    pub fn has_all_roles(&self, roles: &[String]) -> bool {
341        roles.iter().all(|role| self.has_role(role))
342    }
343
344    /// Check if authentication has expired
345    pub fn is_expired(&self) -> bool {
346        self.expires_at.is_some_and(|exp| Utc::now() > exp)
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn test_user_context_creation() {
356        let context = UserContext::new(
357            "123".to_string(),
358            "user@example.com".to_string(),
359            "jwt".to_string(),
360        );
361
362        assert_eq!(context.user_id, "123");
363        assert_eq!(context.username, "user@example.com");
364        assert_eq!(context.auth_provider, "jwt");
365        assert!(context.roles.is_empty());
366        assert!(context.permissions.is_empty());
367    }
368
369    #[test]
370    fn test_user_context_role_checking() {
371        let mut context = UserContext::new(
372            "123".to_string(),
373            "user@example.com".to_string(),
374            "jwt".to_string(),
375        );
376
377        context.roles = vec!["admin".to_string(), "editor".to_string()];
378
379        assert!(context.has_role("admin"));
380        assert!(context.has_role("editor"));
381        assert!(!context.has_role("viewer"));
382
383        assert!(context.has_any_role(&["admin".to_string(), "viewer".to_string()]));
384        assert!(!context.has_any_role(&["viewer".to_string(), "guest".to_string()]));
385
386        assert!(context.has_all_roles(&["admin".to_string(), "editor".to_string()]));
387        assert!(!context.has_all_roles(&["admin".to_string(), "viewer".to_string()]));
388    }
389
390    #[test]
391    fn test_user_context_permission_checking() {
392        let mut context = UserContext::new(
393            "123".to_string(),
394            "user@example.com".to_string(),
395            "jwt".to_string(),
396        );
397
398        context.permissions = vec!["read".to_string(), "write".to_string()];
399
400        assert!(context.has_permission("read"));
401        assert!(context.has_permission("write"));
402        assert!(!context.has_permission("delete"));
403    }
404
405    #[test]
406    fn test_user_context_expiration() {
407        let mut context = UserContext::new(
408            "123".to_string(),
409            "user@example.com".to_string(),
410            "jwt".to_string(),
411        );
412
413        // No expiration set
414        assert!(!context.is_expired());
415
416        // Set expiration in the past
417        context.expires_at = Some(Utc::now() - chrono::Duration::hours(1));
418        assert!(context.is_expired());
419
420        // Set expiration in the future
421        context.expires_at = Some(Utc::now() + chrono::Duration::hours(1));
422        assert!(!context.is_expired());
423    }
424}