Skip to main content

ri/auth/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Authentication Module
19//! 
20//! This module provides comprehensive authentication and authorization functionality for Ri,
21//! offering multiple authentication methods and a robust permission system.
22//! 
23//! ## Key Components
24//! 
25//! - **RiAuthModule**: Main auth module implementing service module traits
26//! - **RiAuthConfig**: Configuration for authentication behavior
27//! - **RiJWTManager**: JWT token management for stateless authentication
28//! - **RiSessionManager**: Session management for stateful authentication
29//! - **RiPermissionManager**: Permission and role management
30//! - **RiOAuthManager**: OAuth provider integration
31//! - **RiJWTClaims**: JWT token claims structure
32//! - **RiJWTValidationOptions**: JWT validation options
33//! - **RiOAuthProvider**: OAuth provider interface
34//! - **RiOAuthToken**: OAuth token structure
35//! - **RiOAuthUserInfo**: OAuth user information
36//! - **RiPermission**: Permission structure
37//! - **RiRole**: Role structure with permissions
38//! - **RiSession**: Session structure
39//! 
40//! ## Design Principles
41//! 
42//! 1. **Multiple Authentication Methods**: Supports JWT, sessions, OAuth, and API keys
43//! 2. **Configurable**: Highly configurable authentication behavior
44//! 3. **Async Support**: Full async/await compatibility for session and OAuth operations
45//! 4. **Role-Based Access Control**: Comprehensive permission system with roles
46//! 5. **Stateless and Stateful Options**: Supports both stateless (JWT) and stateful (session) authentication
47//! 6. **Service Module Integration**: Implements service module traits for seamless integration
48//! 7. **Thread-safe**: Uses Arc and RwLock for safe concurrent access
49//! 8. **Non-critical**: Auth failures should not break the entire application
50//! 9. **Extensible**: Easy to add new authentication methods and OAuth providers
51//! 10. **Secure by Default**: Sensible default configurations for security
52//! 
53//! ## Usage
54//! 
55//! ```rust,ignore
56//! use ri::prelude::*;
57//! use ri::auth::{RiAuthConfig, RiJWTManager, RiJWTClaims};
58//! use serde_json::json;
59//! 
60//! async fn example() -> RiResult<()> {
61//!     // Create auth configuration
62//!     let auth_config = RiAuthConfig {
63//!         enabled: true,
64//!         jwt_secret: "secure-secret-key".to_string(),
65//!         jwt_expiry_secs: 3600,
66//!         session_timeout_secs: 86400,
67//!         oauth_providers: vec![],
68//!         enable_api_keys: true,
69//!         enable_session_auth: true,
70//!     };
71//!     
72//!     // Create auth module
73//!     let auth_module = RiAuthModule::new(auth_config);
74//!     
75//!     // Get JWT manager
76//!     let jwt_manager = auth_module.jwt_manager();
77//!     
78//!     // Create JWT claims
79//!     let claims = RiJWTClaims {
80//!         sub: "user-123".to_string(),
81//!         email: "user@example.com".to_string(),
82//!         roles: vec!["user".to_string()],
83//!         permissions: vec!["read:data".to_string()],
84//!         extra: json!({ "custom": "value" }),
85//!     };
86//!     
87//!     // Generate JWT token
88//!     let token = jwt_manager.generate_token(claims)?;
89//!     println!("Generated JWT token: {}", token);
90//!     
91//!     // Validate JWT token
92//!     let validated_claims = jwt_manager.validate_token(&token)?;
93//!     println!("Validated claims: {:?}", validated_claims);
94//!     
95//!     // Get session manager
96//!     let session_manager = auth_module.session_manager();
97//!     
98//!     // Create a session
99//!     let session = session_manager.write().await.create_session("user-123").await?;
100//!     println!("Created session: {}", session.id);
101//!     
102//!     Ok(())
103//! }
104//! ```
105
106mod jwt;
107mod oauth;
108mod permissions;
109mod session;
110mod security;
111mod revocation;
112
113pub use jwt::{RiJWTManager, RiJWTClaims, RiJWTValidationOptions};
114pub use oauth::{RiOAuthManager, RiOAuthToken, RiOAuthUserInfo, RiOAuthProvider};
115pub use permissions::{RiPermissionManager, RiPermission, RiRole};
116pub use session::{RiSessionManager, RiSession};
117pub use security::RiSecurityManager;
118pub use revocation::{RiJWTRevocationList, RiRevokedTokenInfo};
119
120use crate::core::{RiResult, RiError, RiServiceContext};
121use rand::RngCore;
122use serde::Deserialize;
123use serde::Serialize;
124use std::collections::HashMap;
125use std::env;
126use std::sync::Arc;
127use std::time::{SystemTime, UNIX_EPOCH};
128use tokio::sync::RwLock;
129#[cfg(feature = "pyo3")]
130use tokio::runtime::Handle;
131
132const DEFAULT_JWT_SECRET_ENV: &str = "Ri_JWT_SECRET";
133const FALLBACK_SECRET_LENGTH: usize = 64;
134
135#[derive(Debug, Clone)]
136struct LoginAttempt {
137    count: u32,
138    first_attempt: u64,
139    locked_until: Option<u64>,
140}
141
142#[derive(Debug, Clone)]
143pub struct RiRateLimiter {
144    attempts: Arc<RwLock<HashMap<String, LoginAttempt>>>,
145    max_attempts: u32,
146    lockout_secs: u64,
147    window_secs: u64,
148}
149
150impl RiRateLimiter {
151    pub fn new(max_attempts: u32, lockout_secs: u64, window_secs: u64) -> Self {
152        Self {
153            attempts: Arc::new(RwLock::new(HashMap::new())),
154            max_attempts,
155            lockout_secs,
156            window_secs,
157        }
158    }
159
160    pub async fn check_and_record(&self, identifier: &str) -> Result<(), u64> {
161        let now = SystemTime::now()
162            .duration_since(UNIX_EPOCH)
163            .unwrap()
164            .as_secs();
165
166        let mut attempts = self.attempts.write().await;
167
168        if let Some(attempt) = attempts.get_mut(identifier) {
169            if let Some(locked_until) = attempt.locked_until {
170                if now < locked_until {
171                    return Err(locked_until - now);
172                }
173                attempts.remove(identifier);
174                return Ok(());
175            }
176
177            if now - attempt.first_attempt > self.window_secs {
178                attempts.remove(identifier);
179                return Ok(());
180            }
181
182            attempt.count += 1;
183            if attempt.count >= self.max_attempts {
184                attempt.locked_until = Some(now + self.lockout_secs);
185                return Err(self.lockout_secs);
186            }
187        } else {
188            attempts.insert(identifier.to_string(), LoginAttempt {
189                count: 1,
190                first_attempt: now,
191                locked_until: None,
192            });
193        }
194
195        Ok(())
196    }
197
198    pub async fn reset(&self, identifier: &str) {
199        self.attempts.write().await.remove(identifier);
200    }
201}
202
203#[derive(Debug, Clone, Serialize)]
204pub struct RiAuditEvent {
205    pub timestamp: String,
206    pub event_type: String,
207    pub user_identifier: Option<String>,
208    pub ip_address: Option<String>,
209    pub action: String,
210    pub resource: Option<String>,
211    pub success: bool,
212    pub details: Option<String>,
213}
214
215impl RiAuditEvent {
216    pub fn new(event_type: &str, action: &str) -> Self {
217        Self {
218            timestamp: chrono::Utc::now().to_rfc3339(),
219            event_type: event_type.to_string(),
220            user_identifier: None,
221            ip_address: None,
222            action: action.to_string(),
223            resource: None,
224            success: true,
225            details: None,
226        }
227    }
228
229    pub fn with_user(mut self, user: &str) -> Self {
230        self.user_identifier = Some(user.to_string());
231        self
232    }
233
234    pub fn with_ip(mut self, ip: &str) -> Self {
235        self.ip_address = Some(ip.to_string());
236        self
237    }
238
239    pub fn with_resource(mut self, resource: &str) -> Self {
240        self.resource = Some(resource.to_string());
241        self
242    }
243
244    pub fn with_details(mut self, details: &str) -> Self {
245        self.details = Some(details.to_string());
246        self
247    }
248
249    pub fn with_success(mut self, success: bool) -> Self {
250        self.success = success;
251        self
252    }
253}
254
255#[derive(Debug, Clone)]
256pub struct RiAuditLogger {
257    events: Arc<RwLock<Vec<RiAuditEvent>>>,
258    max_events: usize,
259}
260
261impl RiAuditLogger {
262    pub fn new(max_events: usize) -> Self {
263        Self {
264            events: Arc::new(RwLock::new(Vec::with_capacity(max_events))),
265            max_events,
266        }
267    }
268
269    pub async fn log(&self, event: RiAuditEvent) {
270        let mut events = self.events.write().await;
271        if events.len() >= self.max_events {
272            events.remove(0);
273        }
274        events.push(event);
275    }
276
277    pub async fn get_events(&self) -> Vec<RiAuditEvent> {
278        self.events.read().await.clone()
279    }
280}
281
282fn load_jwt_secret_from_env() -> String {
283    env::var(DEFAULT_JWT_SECRET_ENV).unwrap_or_else(|_| {
284        let mut secret = vec![0u8; FALLBACK_SECRET_LENGTH];
285        rand::thread_rng().fill_bytes(&mut secret);
286        hex::encode(secret)
287    })
288}
289
290fn load_oauth_env_var(provider_name: &str, suffix: &str) -> Result<String, RiError> {
291    let env_var = format!("Ri_OAUTH_{}_{}", provider_name.to_uppercase(), suffix);
292    env::var(&env_var).map_err(|_| {
293        RiError::Config(format!(
294            "OAuth {} is not set for provider '{}'. Please set the environment variable {}",
295            suffix.to_lowercase(),
296            provider_name,
297            env_var
298        ))
299    })
300}
301
302fn get_oauth_url(provider_name: &str, endpoint: &str) -> String {
303    match load_oauth_env_var(provider_name, endpoint) {
304        Ok(url) if !url.is_empty() => url,
305        _ => format!("https://{}.com/oauth/{}", provider_name, endpoint)
306    }
307}
308
309#[cfg(feature = "pyo3")]
310use pyo3::PyResult;
311
312/// Configuration for the authentication module.
313/// 
314/// This struct defines the configuration options for authentication behavior, including
315/// JWT settings, session settings, OAuth providers, and enabled authentication methods.
316/// 
317/// ## Security
318/// 
319/// The JWT secret is loaded from the `Ri_JWT_SECRET` environment variable. If not set,
320/// a cryptographically secure random secret is generated automatically.
321/// 
322/// **Important**: For production environments, always set the `Ri_JWT_SECRET` environment
323/// variable to a strong, unique value. Do not rely on auto-generated secrets in production.
324#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
325#[derive(Debug, Clone)]
326#[derive(Deserialize)]
327pub struct RiAuthConfig {
328    /// Whether authentication is enabled
329    pub enabled: bool,
330    /// Secret key for JWT token generation and validation
331    pub jwt_secret: String,
332    /// JWT token expiry time in seconds
333    pub jwt_expiry_secs: u64,
334    /// Session timeout in seconds
335    pub session_timeout_secs: u64,
336    /// List of OAuth providers to enable
337    pub oauth_providers: Vec<String>,
338    /// Whether API key authentication is enabled
339    pub enable_api_keys: bool,
340    /// Whether session authentication is enabled
341    pub enable_session_auth: bool,
342    /// OAuth token cache backend type (Memory or Redis)
343    #[cfg(feature = "cache")]
344    pub oauth_cache_backend_type: crate::cache::RiCacheBackendType,
345    /// Redis URL for OAuth token cache (used when backend is Redis)
346    #[cfg(feature = "cache")]
347    pub oauth_cache_redis_url: String,
348    /// Rate limiting: max login attempts per IP before lockout
349    pub rate_limit_max_login_attempts: u32,
350    /// Rate limiting: lockout duration in seconds after max attempts exceeded
351    pub rate_limit_lockout_secs: u64,
352    /// Rate limiting: window size in seconds for tracking attempts
353    pub rate_limit_window_secs: u64,
354}
355
356impl Default for RiAuthConfig {
357    fn default() -> Self {
358        Self {
359            enabled: true,
360            jwt_secret: load_jwt_secret_from_env(),
361            jwt_expiry_secs: 3600,
362            session_timeout_secs: 86400,
363            oauth_providers: vec![],
364            enable_api_keys: true,
365            enable_session_auth: true,
366            #[cfg(feature = "cache")]
367            oauth_cache_backend_type: crate::cache::RiCacheBackendType::Memory,
368            #[cfg(feature = "cache")]
369            oauth_cache_redis_url: "redis://127.0.0.1:6379".to_string(),
370            rate_limit_max_login_attempts: 5,
371            rate_limit_lockout_secs: 300,
372            rate_limit_window_secs: 900,
373        }
374    }
375}
376
377/// Main authentication module for Ri.
378///
379/// This module provides comprehensive authentication and authorization functionality,
380/// including JWT management, session management, permission management, and OAuth integration.
381#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
382pub struct RiAuthModule {
383    /// Authentication configuration
384    config: RiAuthConfig,
385    /// JWT manager for stateless authentication
386    jwt_manager: Arc<RiJWTManager>,
387    /// Session manager for stateful authentication, protected by a RwLock for thread-safe access
388    session_manager: Arc<RwLock<RiSessionManager>>,
389    /// Permission manager for role-based access control, protected by a RwLock for thread-safe access
390    permission_manager: Arc<RwLock<RiPermissionManager>>,
391    /// OAuth manager for OAuth provider integration, protected by a RwLock for thread-safe access
392    oauth_manager: Arc<RwLock<RiOAuthManager>>,
393    /// JWT token revocation list for token invalidation
394    revocation_list: Arc<RiJWTRevocationList>,
395    /// Rate limiter for login attempts
396    rate_limiter: RiRateLimiter,
397    /// Audit logger for security events
398    audit_logger: RiAuditLogger,
399}
400
401impl RiAuthModule {
402    /// Creates a new authentication module with the given configuration.
403    /// 
404    /// **Performance Note**: This method creates a permission manager using the synchronous
405    /// `new()` method which uses `blocking_write` during initialization. For async contexts,
406    /// consider using `new_async()` to avoid blocking the runtime.
407    /// 
408    /// # Parameters
409    /// 
410    /// - `config`: The authentication configuration to use
411    /// 
412    /// # Returns
413    /// 
414    /// A `RiResult` containing the new `RiAuthModule` instance
415    /// 
416    /// # Errors
417    /// 
418    /// Returns an error if Redis cache creation fails when Redis backend is configured
419    pub async fn new(config: RiAuthConfig) -> crate::core::error::RiResult<Self> {
420        let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
421        let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
422        let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new()));
423        
424        #[cfg(feature = "cache")]
425        let cache: Arc<dyn crate::cache::RiCache> = match config.oauth_cache_backend_type {
426            crate::cache::RiCacheBackendType::Memory => {
427                Arc::new(crate::cache::RiMemoryCache::new())
428            }
429            crate::cache::RiCacheBackendType::Redis => {
430                let cache = crate::cache::RiRedisCache::new(&config.oauth_cache_redis_url).await
431                    .map_err(|e| crate::core::error::RiError::RedisError(format!("Failed to create Redis cache for OAuth: {}", e)))?;
432                Arc::new(cache)
433            }
434            _ => Arc::new(crate::cache::RiMemoryCache::new()),
435        };
436        
437        #[cfg(not(feature = "cache"))]
438        let cache = Arc::new(crate::cache::RiMemoryCache::new());
439        
440        let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
441        let revocation_list = Arc::new(RiJWTRevocationList::new());
442        let rate_limiter = RiRateLimiter::new(
443            config.rate_limit_max_login_attempts,
444            config.rate_limit_lockout_secs,
445            config.rate_limit_window_secs,
446        );
447        let audit_logger = RiAuditLogger::new(10000);
448
449        Ok(Self {
450            config,
451            jwt_manager,
452            session_manager,
453            permission_manager,
454            oauth_manager,
455            revocation_list,
456            rate_limiter,
457            audit_logger,
458        })
459    }
460
461    /// Creates a new authentication module with the given configuration (synchronous version).
462    /// 
463    /// This is a synchronous wrapper for use in the builder pattern.
464    /// 
465    /// # Parameters
466    /// 
467    /// - `config`: The authentication configuration to use
468    /// 
469    /// # Returns
470    /// 
471    /// A new `RiAuthModule` instance
472    pub fn with_config(config: RiAuthConfig) -> Self {
473        let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
474        let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
475        let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new()));
476        let cache = Arc::new(crate::cache::RiMemoryCache::new());
477        let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
478        let revocation_list = Arc::new(RiJWTRevocationList::new());
479        let rate_limiter = RiRateLimiter::new(
480            config.rate_limit_max_login_attempts,
481            config.rate_limit_lockout_secs,
482            config.rate_limit_window_secs,
483        );
484        let audit_logger = RiAuditLogger::new(10000);
485
486        Self {
487            config,
488            jwt_manager,
489            session_manager,
490            permission_manager,
491            oauth_manager,
492            revocation_list,
493            rate_limiter,
494            audit_logger,
495        }
496    }
497
498    /// Creates a new authentication module with the given configuration asynchronously.
499    /// 
500    /// This method is preferred for async contexts as it avoids blocking the runtime
501    /// during permission manager initialization by using the async `new_async()` method.
502    /// 
503    /// # Parameters
504    /// 
505    /// - `config`: The authentication configuration to use
506    /// 
507    /// # Returns
508    /// 
509    /// A `RiResult` containing the new `RiAuthModule` instance
510    /// 
511    /// # Errors
512    /// 
513    /// Returns an error if Redis cache creation fails when Redis backend is configured
514    pub async fn new_async(config: RiAuthConfig) -> crate::core::error::RiResult<Self> {
515        let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
516        let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
517        let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new_async().await));
518        
519        #[cfg(feature = "cache")]
520        let cache: Arc<dyn crate::cache::RiCache> = match config.oauth_cache_backend_type {
521            crate::cache::RiCacheBackendType::Memory => {
522                Arc::new(crate::cache::RiMemoryCache::new())
523            }
524            crate::cache::RiCacheBackendType::Redis => {
525                let cache = crate::cache::RiRedisCache::new(&config.oauth_cache_redis_url).await
526                    .map_err(|e| crate::core::error::RiError::RedisError(format!("Failed to create Redis cache: {}", e)))?;
527                Arc::new(cache)
528            }
529            _ => Arc::new(crate::cache::RiMemoryCache::new()),
530        };
531        
532        #[cfg(not(feature = "cache"))]
533        let cache = Arc::new(crate::cache::RiMemoryCache::new());
534        
535        let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
536        let revocation_list = Arc::new(RiJWTRevocationList::new());
537        let rate_limiter = RiRateLimiter::new(
538            config.rate_limit_max_login_attempts,
539            config.rate_limit_lockout_secs,
540            config.rate_limit_window_secs,
541        );
542        let audit_logger = RiAuditLogger::new(10000);
543
544        Ok(Self {
545            config,
546            jwt_manager,
547            session_manager,
548            permission_manager,
549            oauth_manager,
550            revocation_list,
551            rate_limiter,
552            audit_logger,
553        })
554    }
555
556    /// Returns a reference to the JWT revocation list.
557    /// 
558    /// # Returns
559    /// 
560    /// An Arc<RiJWTRevocationList> providing thread-safe access to the token revocation list
561    pub fn revocation_list(&self) -> Arc<RiJWTRevocationList> {
562        self.revocation_list.clone()
563    }
564
565    /// Check if a login attempt from the given identifier (IP address or username) should be allowed.
566    ///
567    /// This method implements rate limiting for login attempts. If the rate limit is exceeded,
568    /// the method returns an error with the remaining lockout time in seconds.
569    ///
570    /// # Parameters
571    ///
572    /// - `identifier`: The identifier to check (typically IP address or username)
573    ///
574    /// # Returns
575    ///
576    /// - `Ok(())` if the attempt is allowed
577    /// - `Err(lockout_remaining_secs)` if the identifier is locked out
578    pub async fn check_rate_limit(&self, identifier: &str) -> Result<(), u64> {
579        self.rate_limiter.check_and_record(identifier).await
580    }
581
582    /// Reset the rate limit for a given identifier (e.g., after successful login).
583    ///
584    /// # Parameters
585    ///
586    /// - `identifier`: The identifier to reset
587    pub async fn reset_rate_limit(&self, identifier: &str) {
588        self.rate_limiter.reset(identifier).await;
589    }
590
591    /// Log an audit event for security tracking.
592    ///
593    /// # Parameters
594    ///
595    /// - `event`: The audit event to log
596    pub async fn log_audit_event(&self, event: RiAuditEvent) {
597        self.audit_logger.log(event).await;
598    }
599
600    /// Log a login attempt event.
601    ///
602    /// # Parameters
603    ///
604    /// - `username`: The username that attempted to log in
605    /// - `ip_address`: The IP address of the login attempt
606    /// - `success`: Whether the login was successful
607    /// - `reason`: Optional reason for failure or success
608    pub async fn log_login_attempt(&self, username: &str, ip_address: Option<&str>, success: bool, reason: Option<&str>) {
609        let mut event = RiAuditEvent::new("LOGIN_ATTEMPT", if success { "login_success" } else { "login_failure" });
610        event = event.with_user(username);
611        if let Some(ip) = ip_address {
612            event = event.with_ip(ip);
613        }
614        if let Some(r) = reason {
615            event = event.with_details(r);
616        }
617        event = event.with_success(success);
618        self.audit_logger.log(event).await;
619    }
620
621    /// Get recent audit events.
622    ///
623    /// # Returns
624    ///
625    /// A vector of recent audit events
626    pub async fn get_audit_events(&self) -> Vec<RiAuditEvent> {
627        self.audit_logger.get_events().await
628    }
629
630    /// Returns a reference to the JWT manager.
631    /// 
632    /// # Returns
633    /// 
634    /// An Arc<RiJWTManager> providing thread-safe access to the JWT manager
635    pub fn jwt_manager(&self) -> Arc<RiJWTManager> {
636        self.jwt_manager.clone()
637    }
638
639    /// Returns a reference to the session manager.
640    /// 
641    /// # Returns
642    /// 
643    /// An Arc<RwLock<RiSessionManager>> providing thread-safe access to the session manager
644    pub fn session_manager(&self) -> Arc<RwLock<RiSessionManager>> {
645        self.session_manager.clone()
646    }
647
648    /// Returns a reference to the permission manager.
649    /// 
650    /// # Returns
651    /// 
652    /// An Arc<RwLock<RiPermissionManager>> providing thread-safe access to the permission manager
653    pub fn permission_manager(&self) -> Arc<RwLock<RiPermissionManager>> {
654        self.permission_manager.clone()
655    }
656
657    /// Returns a reference to the OAuth manager.
658    /// 
659    /// # Returns
660    /// 
661    /// An Arc<RwLock<RiOAuthManager>> providing thread-safe access to the OAuth manager
662    pub fn oauth_manager(&self) -> Arc<RwLock<RiOAuthManager>> {
663        self.oauth_manager.clone()
664    }
665}
666
667#[cfg(feature = "pyo3")]
668#[pyo3::prelude::pymethods]
669impl RiAuthConfig {
670    /// Creates a new authentication configuration with the specified parameters.
671    ///
672    /// All parameters have sensible defaults, making it easy to create a basic configuration.
673    /// The JWT secret is automatically loaded from the `Ri_JWT_SECRET` environment variable
674    /// if not provided.
675    ///
676    /// # Parameters
677    ///
678    /// - `enabled`: Whether authentication is enabled (default: true)
679    /// - `jwt_secret`: Secret key for JWT tokens (default: loaded from Ri_JWT_SECRET env var)
680    /// - `jwt_expiry_secs`: JWT token expiry time in seconds (default: 3600)
681    /// - `session_timeout_secs`: Session timeout in seconds (default: 86400)
682    /// - `oauth_providers`: List of OAuth providers to enable (default: empty)
683    /// - `enable_api_keys`: Whether API key authentication is enabled (default: true)
684    /// - `enable_session_auth`: Whether session authentication is enabled (default: true)
685    /// - `oauth_cache_backend_type`: Cache backend type - "Memory" or "Redis" (default: "Memory")
686    /// - `oauth_cache_redis_url`: Redis URL for OAuth cache (default: "redis://127.0.0.1:6379")
687    ///
688    /// # Returns
689    ///
690    /// A new `RiAuthConfig` instance
691    ///
692    /// # Example
693    ///
694    /// ```python
695    /// from ri import RiAuthConfig
696    ///
697    /// # Create with defaults
698    /// config = RiAuthConfig()
699    ///
700    /// # Create with custom settings
701    /// config = RiAuthConfig(
702    ///     enabled=True,
703    ///     jwt_secret="my-secret-key",
704    ///     jwt_expiry_secs=7200,
705    ///     oauth_providers=["google", "github"]
706    /// )
707    /// ```
708    #[new]
709    #[pyo3(signature = (
710        enabled = true,
711        jwt_secret = "",
712        jwt_expiry_secs = 3600,
713        session_timeout_secs = 86400,
714        oauth_providers = vec![],
715        enable_api_keys = true,
716        enable_session_auth = true,
717        oauth_cache_backend_type = None,
718        oauth_cache_redis_url = "redis://127.0.0.1:6379",
719        rate_limit_max_login_attempts = 5,
720        rate_limit_lockout_secs = 300,
721        rate_limit_window_secs = 900
722    ))]
723    fn py_new(
724        enabled: bool,
725        jwt_secret: &str,
726        jwt_expiry_secs: u64,
727        session_timeout_secs: u64,
728        oauth_providers: Vec<String>,
729        enable_api_keys: bool,
730        enable_session_auth: bool,
731        oauth_cache_backend_type: Option<String>,
732        oauth_cache_redis_url: &str,
733        rate_limit_max_login_attempts: u32,
734        rate_limit_lockout_secs: u64,
735        rate_limit_window_secs: u64,
736    ) -> Self {
737        let secret = if jwt_secret.is_empty() {
738            load_jwt_secret_from_env()
739        } else {
740            jwt_secret.to_string()
741        };
742        
743        #[cfg(feature = "cache")]
744        {
745            let backend_type = match oauth_cache_backend_type.as_deref() {
746                Some("Redis") => crate::cache::RiCacheBackendType::Redis,
747                _ => crate::cache::RiCacheBackendType::Memory,
748            };
749            
750            Self {
751                enabled,
752                jwt_secret: secret,
753                jwt_expiry_secs,
754                session_timeout_secs,
755                oauth_providers,
756                enable_api_keys,
757                enable_session_auth,
758                oauth_cache_backend_type: backend_type,
759                oauth_cache_redis_url: oauth_cache_redis_url.to_string(),
760                rate_limit_max_login_attempts,
761                rate_limit_lockout_secs,
762                rate_limit_window_secs,
763            }
764        }
765        
766        #[cfg(not(feature = "cache"))]
767        {
768            let _ = oauth_cache_backend_type;
769            let _ = oauth_cache_redis_url;
770            
771            Self {
772                enabled,
773                jwt_secret: secret,
774                jwt_expiry_secs,
775                session_timeout_secs,
776                oauth_providers,
777                enable_api_keys,
778                enable_session_auth,
779                rate_limit_max_login_attempts,
780                rate_limit_lockout_secs,
781                rate_limit_window_secs,
782            }
783        }
784    }
785
786    /// Creates a new authentication configuration with default values.
787    ///
788    /// This is a convenience method that creates a configuration with all default settings.
789    /// The JWT secret is loaded from the `Ri_JWT_SECRET` environment variable.
790    ///
791    /// # Returns
792    ///
793    /// A new `RiAuthConfig` instance with default values
794    ///
795    /// # Example
796    ///
797    /// ```python
798    /// from ri import RiAuthConfig
799    ///
800    /// config = RiAuthConfig.default()
801    /// ```
802    #[staticmethod]
803    fn default() -> Self {
804        <Self as Default>::default()
805    }
806
807    /// Creates a new authentication configuration from environment variables.
808    ///
809    /// This method loads the JWT secret from the `Ri_JWT_SECRET` environment variable
810    /// and uses default values for all other settings.
811    ///
812    /// # Returns
813    ///
814    /// A new `RiAuthConfig` instance with values from environment
815    ///
816    /// # Example
817    ///
818    /// ```python
819    /// import os
820    /// from ri import RiAuthConfig
821    ///
822    /// os.environ["Ri_JWT_SECRET"] = "my-secret-key"
823    /// config = RiAuthConfig.from_env()
824    /// ```
825    #[staticmethod]
826    fn from_env() -> Self {
827        Self {
828            jwt_secret: load_jwt_secret_from_env(),
829            ..Self::default()
830        }
831    }
832
833    /// Returns whether authentication is enabled.
834    #[getter]
835    fn get_enabled(&self) -> bool {
836        self.enabled
837    }
838
839    /// Returns the JWT secret key.
840    #[getter]
841    fn get_jwt_secret(&self) -> String {
842        self.jwt_secret.clone()
843    }
844
845    /// Returns the JWT token expiry time in seconds.
846    #[getter]
847    fn get_jwt_expiry_secs(&self) -> u64 {
848        self.jwt_expiry_secs
849    }
850
851    /// Returns the session timeout in seconds.
852    #[getter]
853    fn get_session_timeout_secs(&self) -> u64 {
854        self.session_timeout_secs
855    }
856
857    /// Returns the list of OAuth providers.
858    #[getter]
859    fn get_oauth_providers(&self) -> Vec<String> {
860        self.oauth_providers.clone()
861    }
862
863    /// Returns whether API key authentication is enabled.
864    #[getter]
865    fn get_enable_api_keys(&self) -> bool {
866        self.enable_api_keys
867    }
868
869    /// Returns whether session authentication is enabled.
870    #[getter]
871    fn get_enable_session_auth(&self) -> bool {
872        self.enable_session_auth
873    }
874}
875
876#[cfg(feature = "pyo3")]
877/// Python bindings for the Ri Authentication Module.
878///
879/// This module provides Python interface to Ri authentication functionality,
880/// enabling Python applications to leverage Ri's authentication capabilities.
881///
882/// ## Supported Operations
883///
884/// - JWT token generation and validation
885/// - Session management for stateful authentication
886/// - Permission and role management for RBAC
887/// - OAuth provider integration
888///
889/// ## Python Usage Example
890///
891/// ```python
892/// from ri import RiAuthConfig, RiJWTManager
893///
894/// # Create auth configuration
895/// config = RiAuthConfig(
896///     enabled=True,
897///     jwt_secret="secure-secret-key",
898///     jwt_expiry_secs=3600,
899///     session_timeout_secs=86400,
900///     oauth_providers=["google", "github"],
901///     enable_api_keys=True,
902///     enable_session_auth=True,
903/// )
904///
905/// # Create auth module
906/// auth_module = RiAuthModule(config)
907///
908/// # Get JWT manager and generate token
909/// jwt_manager = auth_module.jwt_manager()
910/// token = jwt_manager.generate_token("user123", ["user"], ["read:data"])
911/// ```
912#[pyo3::prelude::pymethods]
913impl RiAuthModule {
914    #[new]
915    fn py_new(config: RiAuthConfig) -> PyResult<Self> {
916        let rt = Handle::current();
917        rt.block_on(async {
918            Self::new(config).await
919                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
920        })
921    }
922
923    #[getter]
924    fn get_config(&self) -> RiAuthConfig {
925        self.config.clone()
926    }
927
928    #[getter]
929    fn get_jwt_expiry_secs(&self) -> u64 {
930        self.jwt_manager.get_token_expiry()
931    }
932
933    #[getter]
934    fn get_session_timeout_secs(&self) -> u64 {
935        self.config.session_timeout_secs
936    }
937
938    #[getter]
939    fn is_enabled(&self) -> bool {
940        self.config.enabled
941    }
942
943    #[getter]
944    fn is_api_keys_enabled(&self) -> bool {
945        self.config.enable_api_keys
946    }
947
948    #[getter]
949    fn is_session_auth_enabled(&self) -> bool {
950        self.config.enable_session_auth
951    }
952
953    #[getter]
954    fn get_oauth_providers(&self) -> Vec<String> {
955        self.config.oauth_providers.clone()
956    }
957
958    fn validate_jwt_token(&self, token: &str) -> bool {
959        self.jwt_manager.validate_token(token).is_ok()
960    }
961
962    fn generate_test_token(&self, subject: &str, roles: Vec<String>, permissions: Vec<String>) -> PyResult<String> {
963        self.jwt_manager.generate_token(subject, roles, permissions)
964            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
965    }
966}
967
968impl crate::core::ServiceModule for RiAuthModule {
969    fn name(&self) -> &str {
970        "Ri.Auth"
971    }
972
973    fn is_critical(&self) -> bool {
974        false
975    }
976
977    fn priority(&self) -> i32 {
978        20
979    }
980
981    fn dependencies(&self) -> Vec<&str> {
982        vec![]
983    }
984
985    fn init(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
986        Ok(())
987    }
988
989    fn start(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
990        Ok(())
991    }
992
993    fn shutdown(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
994        Ok(())
995    }
996}
997
998#[async_trait::async_trait]
999impl crate::core::RiModule for RiAuthModule {
1000    /// Returns the name of the authentication module.
1001    /// 
1002    /// # Returns
1003    /// 
1004    /// The module name as a string
1005    fn name(&self) -> &str {
1006        "Ri.Auth"
1007    }
1008
1009    /// Indicates whether the authentication module is critical.
1010    /// 
1011    /// The authentication module is non-critical, meaning that if it fails to initialize or operate,
1012    /// it should not break the entire application. This allows the core functionality to continue
1013    /// even if authentication features are unavailable.
1014    /// 
1015    /// # Returns
1016    /// 
1017    /// `false` since authentication is non-critical
1018    fn is_critical(&self) -> bool {
1019        false // Auth failures should not break the application
1020    }
1021
1022    /// Initializes the authentication module asynchronously.
1023    /// 
1024    /// This method performs the following steps:
1025    /// 1. Loads configuration from the service context
1026    /// 2. Updates the module configuration if provided
1027    /// 3. Reinitializes the JWT manager with the new configuration
1028    /// 4. Initializes OAuth providers if configured
1029    /// 
1030    /// # Parameters
1031    /// 
1032    /// - `ctx`: The service context containing configuration
1033    /// 
1034    /// # Returns
1035    /// 
1036    /// A `RiResult<()>` indicating success or failure
1037    async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
1038        log::info!("Initializing Ri Auth Module");
1039
1040        // Load configuration
1041        let binding = ctx.config();
1042        let cfg = binding.config();
1043        
1044        // Update configuration if provided
1045        if let Some(auth_config) = cfg.get("auth") {
1046            self.config = serde_yaml::from_str(auth_config)
1047                .unwrap_or_else(|_| RiAuthConfig::default());
1048        }
1049
1050        // Initialize JWT manager with new config
1051        self.jwt_manager = Arc::new(RiJWTManager::create(self.config.jwt_secret.clone(), self.config.jwt_expiry_secs));
1052
1053        // Initialize OAuth providers if configured
1054        if !self.config.oauth_providers.is_empty() {
1055            for provider_name in &self.config.oauth_providers {
1056                let client_id = load_oauth_env_var(provider_name, "CLIENT_ID")?;
1057                let client_secret = load_oauth_env_var(provider_name, "CLIENT_SECRET")?;
1058
1059                let provider_config = crate::auth::oauth::RiOAuthProvider {
1060                    id: provider_name.clone(),
1061                    name: provider_name.clone(),
1062                    client_id,
1063                    client_secret,
1064                    auth_url: get_oauth_url(provider_name, "authorize"),
1065                    token_url: get_oauth_url(provider_name, "token"),
1066                    user_info_url: get_oauth_url(provider_name, "userinfo"),
1067                    scopes: vec!["openid".to_string(), "profile".to_string(), "email".to_string()],
1068                    enabled: true,
1069                    redirect_uri: None,
1070                    allowed_redirect_uris: vec![],
1071                };
1072                
1073                let oauth_mgr = self.oauth_manager.write().await;
1074                oauth_mgr.register_provider(provider_config).await?;
1075                log::info!("OAuth provider registered: {provider_name}");
1076            }
1077        }
1078
1079        log::info!("Ri Auth Module initialized successfully");
1080        Ok(())
1081    }
1082
1083    /// Performs asynchronous cleanup after the application has shut down.
1084    /// 
1085    /// This method cleans up all sessions managed by the session manager.
1086    /// 
1087    /// # Parameters
1088    /// 
1089    /// - `_ctx`: The service context (not used in this implementation)
1090    /// 
1091    /// # Returns
1092    /// 
1093    /// A `RiResult<()>` indicating success or failure
1094    async fn after_shutdown(&mut self, _ctx: &mut RiServiceContext) -> RiResult<()> {
1095        log::info!("Cleaning up Ri Auth Module");
1096        
1097        // Cleanup sessions
1098        let session_mgr = self.session_manager.write().await;
1099        session_mgr.cleanup_all().await?;
1100        
1101        log::info!("Ri Auth Module cleanup completed");
1102        Ok(())
1103    }
1104}