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
130const DEFAULT_JWT_SECRET_ENV: &str = "Ri_JWT_SECRET";
131const FALLBACK_SECRET_LENGTH: usize = 64;
132
133#[derive(Debug, Clone)]
134struct LoginAttempt {
135    count: u32,
136    first_attempt: u64,
137    locked_until: Option<u64>,
138}
139
140#[derive(Debug, Clone)]
141pub struct RiRateLimiter {
142    attempts: Arc<RwLock<HashMap<String, LoginAttempt>>>,
143    max_attempts: u32,
144    lockout_secs: u64,
145    window_secs: u64,
146}
147
148impl RiRateLimiter {
149    pub fn new(max_attempts: u32, lockout_secs: u64, window_secs: u64) -> Self {
150        Self {
151            attempts: Arc::new(RwLock::new(HashMap::new())),
152            max_attempts,
153            lockout_secs,
154            window_secs,
155        }
156    }
157
158    pub async fn check_and_record(&self, identifier: &str) -> Result<(), u64> {
159        let now = SystemTime::now()
160            .duration_since(UNIX_EPOCH)
161            .unwrap()
162            .as_secs();
163
164        let mut attempts = self.attempts.write().await;
165
166        if let Some(attempt) = attempts.get_mut(identifier) {
167            if let Some(locked_until) = attempt.locked_until {
168                if now < locked_until {
169                    return Err(locked_until - now);
170                }
171                attempts.remove(identifier);
172                return Ok(());
173            }
174
175            if now - attempt.first_attempt > self.window_secs {
176                attempts.remove(identifier);
177                return Ok(());
178            }
179
180            attempt.count += 1;
181            if attempt.count >= self.max_attempts {
182                attempt.locked_until = Some(now + self.lockout_secs);
183                return Err(self.lockout_secs);
184            }
185        } else {
186            attempts.insert(identifier.to_string(), LoginAttempt {
187                count: 1,
188                first_attempt: now,
189                locked_until: None,
190            });
191        }
192
193        Ok(())
194    }
195
196    pub async fn reset(&self, identifier: &str) {
197        self.attempts.write().await.remove(identifier);
198    }
199}
200
201#[derive(Debug, Clone, Serialize)]
202pub struct RiAuditEvent {
203    pub timestamp: String,
204    pub event_type: String,
205    pub user_identifier: Option<String>,
206    pub ip_address: Option<String>,
207    pub action: String,
208    pub resource: Option<String>,
209    pub success: bool,
210    pub details: Option<String>,
211}
212
213impl RiAuditEvent {
214    pub fn new(event_type: &str, action: &str) -> Self {
215        Self {
216            timestamp: chrono::Utc::now().to_rfc3339(),
217            event_type: event_type.to_string(),
218            user_identifier: None,
219            ip_address: None,
220            action: action.to_string(),
221            resource: None,
222            success: true,
223            details: None,
224        }
225    }
226
227    pub fn with_user(mut self, user: &str) -> Self {
228        self.user_identifier = Some(user.to_string());
229        self
230    }
231
232    pub fn with_ip(mut self, ip: &str) -> Self {
233        self.ip_address = Some(ip.to_string());
234        self
235    }
236
237    pub fn with_resource(mut self, resource: &str) -> Self {
238        self.resource = Some(resource.to_string());
239        self
240    }
241
242    pub fn with_details(mut self, details: &str) -> Self {
243        self.details = Some(details.to_string());
244        self
245    }
246
247    pub fn with_success(mut self, success: bool) -> Self {
248        self.success = success;
249        self
250    }
251}
252
253#[derive(Debug, Clone)]
254pub struct RiAuditLogger {
255    events: Arc<RwLock<Vec<RiAuditEvent>>>,
256    max_events: usize,
257}
258
259impl RiAuditLogger {
260    pub fn new(max_events: usize) -> Self {
261        Self {
262            events: Arc::new(RwLock::new(Vec::with_capacity(max_events))),
263            max_events,
264        }
265    }
266
267    pub async fn log(&self, event: RiAuditEvent) {
268        let mut events = self.events.write().await;
269        if events.len() >= self.max_events {
270            events.remove(0);
271        }
272        events.push(event);
273    }
274
275    pub async fn get_events(&self) -> Vec<RiAuditEvent> {
276        self.events.read().await.clone()
277    }
278}
279
280fn load_jwt_secret_from_env() -> String {
281    env::var(DEFAULT_JWT_SECRET_ENV).unwrap_or_else(|_| {
282        let mut secret = vec![0u8; FALLBACK_SECRET_LENGTH];
283        rand::thread_rng().fill_bytes(&mut secret);
284        hex::encode(secret)
285    })
286}
287
288fn load_oauth_env_var(provider_name: &str, suffix: &str) -> Result<String, RiError> {
289    let env_var = format!("Ri_OAUTH_{}_{}", provider_name.to_uppercase(), suffix);
290    env::var(&env_var).map_err(|_| {
291        RiError::Config(format!(
292            "OAuth {} is not set for provider '{}'. Please set the environment variable {}",
293            suffix.to_lowercase(),
294            provider_name,
295            env_var
296        ))
297    })
298}
299
300fn get_oauth_url(provider_name: &str, endpoint: &str) -> String {
301    match load_oauth_env_var(provider_name, endpoint) {
302        Ok(url) if !url.is_empty() => url,
303        _ => format!("https://{}.com/oauth/{}", provider_name, endpoint)
304    }
305}
306
307#[cfg(feature = "pyo3")]
308use pyo3::PyResult;
309
310/// Configuration for the authentication module.
311/// 
312/// This struct defines the configuration options for authentication behavior, including
313/// JWT settings, session settings, OAuth providers, and enabled authentication methods.
314/// 
315/// ## Security
316/// 
317/// The JWT secret is loaded from the `Ri_JWT_SECRET` environment variable. If not set,
318/// a cryptographically secure random secret is generated automatically.
319/// 
320/// **Important**: For production environments, always set the `Ri_JWT_SECRET` environment
321/// variable to a strong, unique value. Do not rely on auto-generated secrets in production.
322#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
323#[derive(Debug, Clone)]
324#[derive(Deserialize)]
325pub struct RiAuthConfig {
326    /// Whether authentication is enabled
327    pub enabled: bool,
328    /// Secret key for JWT token generation and validation
329    pub jwt_secret: String,
330    /// JWT token expiry time in seconds
331    pub jwt_expiry_secs: u64,
332    /// Session timeout in seconds
333    pub session_timeout_secs: u64,
334    /// List of OAuth providers to enable
335    pub oauth_providers: Vec<String>,
336    /// Whether API key authentication is enabled
337    pub enable_api_keys: bool,
338    /// Whether session authentication is enabled
339    pub enable_session_auth: bool,
340    /// OAuth token cache backend type (Memory or Redis)
341    #[cfg(feature = "cache")]
342    pub oauth_cache_backend_type: crate::cache::RiCacheBackendType,
343    /// Redis URL for OAuth token cache (used when backend is Redis)
344    #[cfg(feature = "cache")]
345    pub oauth_cache_redis_url: String,
346    /// Rate limiting: max login attempts per IP before lockout
347    pub rate_limit_max_login_attempts: u32,
348    /// Rate limiting: lockout duration in seconds after max attempts exceeded
349    pub rate_limit_lockout_secs: u64,
350    /// Rate limiting: window size in seconds for tracking attempts
351    pub rate_limit_window_secs: u64,
352}
353
354impl Default for RiAuthConfig {
355    fn default() -> Self {
356        Self {
357            enabled: true,
358            jwt_secret: load_jwt_secret_from_env(),
359            jwt_expiry_secs: 3600,
360            session_timeout_secs: 86400,
361            oauth_providers: vec![],
362            enable_api_keys: true,
363            enable_session_auth: true,
364            #[cfg(feature = "cache")]
365            oauth_cache_backend_type: crate::cache::RiCacheBackendType::Memory,
366            #[cfg(feature = "cache")]
367            oauth_cache_redis_url: "redis://127.0.0.1:6379".to_string(),
368            rate_limit_max_login_attempts: 5,
369            rate_limit_lockout_secs: 300,
370            rate_limit_window_secs: 900,
371        }
372    }
373}
374
375/// Main authentication module for Ri.
376///
377/// This module provides comprehensive authentication and authorization functionality,
378/// including JWT management, session management, permission management, and OAuth integration.
379#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
380pub struct RiAuthModule {
381    /// Authentication configuration
382    config: RiAuthConfig,
383    /// JWT manager for stateless authentication
384    jwt_manager: Arc<RiJWTManager>,
385    /// Session manager for stateful authentication, protected by a RwLock for thread-safe access
386    session_manager: Arc<RwLock<RiSessionManager>>,
387    /// Permission manager for role-based access control, protected by a RwLock for thread-safe access
388    permission_manager: Arc<RwLock<RiPermissionManager>>,
389    /// OAuth manager for OAuth provider integration, protected by a RwLock for thread-safe access
390    oauth_manager: Arc<RwLock<RiOAuthManager>>,
391    /// JWT token revocation list for token invalidation
392    revocation_list: Arc<RiJWTRevocationList>,
393    /// Rate limiter for login attempts
394    rate_limiter: RiRateLimiter,
395    /// Audit logger for security events
396    audit_logger: RiAuditLogger,
397}
398
399impl RiAuthModule {
400    /// Creates a new authentication module with the given configuration.
401    /// 
402    /// **Performance Note**: This method creates a permission manager using the synchronous
403    /// `new()` method which uses `blocking_write` during initialization. For async contexts,
404    /// consider using `new_async()` to avoid blocking the runtime.
405    /// 
406    /// # Parameters
407    /// 
408    /// - `config`: The authentication configuration to use
409    /// 
410    /// # Returns
411    /// 
412    /// A `RiResult` containing the new `RiAuthModule` instance
413    /// 
414    /// # Errors
415    /// 
416    /// Returns an error if Redis cache creation fails when Redis backend is configured
417    pub async fn new(config: RiAuthConfig) -> crate::core::error::RiResult<Self> {
418        let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
419        let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
420        let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new()));
421        
422        #[cfg(feature = "cache")]
423        let cache: Arc<dyn crate::cache::RiCache> = match config.oauth_cache_backend_type {
424            crate::cache::RiCacheBackendType::Memory => {
425                Arc::new(crate::cache::RiMemoryCache::new())
426            }
427            crate::cache::RiCacheBackendType::Redis => {
428                let cache = crate::cache::RiRedisCache::new(&config.oauth_cache_redis_url).await
429                    .map_err(|e| crate::core::error::RiError::RedisError(format!("Failed to create Redis cache for OAuth: {}", e)))?;
430                Arc::new(cache)
431            }
432            _ => Arc::new(crate::cache::RiMemoryCache::new()),
433        };
434        
435        #[cfg(not(feature = "cache"))]
436        let cache = Arc::new(crate::cache::RiMemoryCache::new());
437        
438        let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
439        let revocation_list = Arc::new(RiJWTRevocationList::new());
440        let rate_limiter = RiRateLimiter::new(
441            config.rate_limit_max_login_attempts,
442            config.rate_limit_lockout_secs,
443            config.rate_limit_window_secs,
444        );
445        let audit_logger = RiAuditLogger::new(10000);
446
447        Ok(Self {
448            config,
449            jwt_manager,
450            session_manager,
451            permission_manager,
452            oauth_manager,
453            revocation_list,
454            rate_limiter,
455            audit_logger,
456        })
457    }
458
459    /// Creates a new authentication module with the given configuration (synchronous version).
460    /// 
461    /// This is a synchronous wrapper for use in the builder pattern.
462    /// 
463    /// # Parameters
464    /// 
465    /// - `config`: The authentication configuration to use
466    /// 
467    /// # Returns
468    /// 
469    /// A new `RiAuthModule` instance
470    pub fn with_config(config: RiAuthConfig) -> Self {
471        let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
472        let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
473        let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new()));
474        let cache = Arc::new(crate::cache::RiMemoryCache::new());
475        let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
476        let revocation_list = Arc::new(RiJWTRevocationList::new());
477        let rate_limiter = RiRateLimiter::new(
478            config.rate_limit_max_login_attempts,
479            config.rate_limit_lockout_secs,
480            config.rate_limit_window_secs,
481        );
482        let audit_logger = RiAuditLogger::new(10000);
483
484        Self {
485            config,
486            jwt_manager,
487            session_manager,
488            permission_manager,
489            oauth_manager,
490            revocation_list,
491            rate_limiter,
492            audit_logger,
493        }
494    }
495
496    /// Creates a new authentication module with the given configuration asynchronously.
497    /// 
498    /// This method is preferred for async contexts as it avoids blocking the runtime
499    /// during permission manager initialization by using the async `new_async()` method.
500    /// 
501    /// # Parameters
502    /// 
503    /// - `config`: The authentication configuration to use
504    /// 
505    /// # Returns
506    /// 
507    /// A `RiResult` containing the new `RiAuthModule` instance
508    /// 
509    /// # Errors
510    /// 
511    /// Returns an error if Redis cache creation fails when Redis backend is configured
512    pub async fn new_async(config: RiAuthConfig) -> crate::core::error::RiResult<Self> {
513        let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
514        let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
515        let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new_async().await));
516        
517        #[cfg(feature = "cache")]
518        let cache: Arc<dyn crate::cache::RiCache> = match config.oauth_cache_backend_type {
519            crate::cache::RiCacheBackendType::Memory => {
520                Arc::new(crate::cache::RiMemoryCache::new())
521            }
522            crate::cache::RiCacheBackendType::Redis => {
523                let cache = crate::cache::RiRedisCache::new(&config.oauth_cache_redis_url).await
524                    .map_err(|e| crate::core::error::RiError::RedisError(format!("Failed to create Redis cache: {}", e)))?;
525                Arc::new(cache)
526            }
527            _ => Arc::new(crate::cache::RiMemoryCache::new()),
528        };
529        
530        #[cfg(not(feature = "cache"))]
531        let cache = Arc::new(crate::cache::RiMemoryCache::new());
532        
533        let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
534        let revocation_list = Arc::new(RiJWTRevocationList::new());
535        let rate_limiter = RiRateLimiter::new(
536            config.rate_limit_max_login_attempts,
537            config.rate_limit_lockout_secs,
538            config.rate_limit_window_secs,
539        );
540        let audit_logger = RiAuditLogger::new(10000);
541
542        Ok(Self {
543            config,
544            jwt_manager,
545            session_manager,
546            permission_manager,
547            oauth_manager,
548            revocation_list,
549            rate_limiter,
550            audit_logger,
551        })
552    }
553
554    /// Returns a reference to the JWT revocation list.
555    /// 
556    /// # Returns
557    /// 
558    /// An Arc<RiJWTRevocationList> providing thread-safe access to the token revocation list
559    pub fn revocation_list(&self) -> Arc<RiJWTRevocationList> {
560        self.revocation_list.clone()
561    }
562
563    /// Check if a login attempt from the given identifier (IP address or username) should be allowed.
564    ///
565    /// This method implements rate limiting for login attempts. If the rate limit is exceeded,
566    /// the method returns an error with the remaining lockout time in seconds.
567    ///
568    /// # Parameters
569    ///
570    /// - `identifier`: The identifier to check (typically IP address or username)
571    ///
572    /// # Returns
573    ///
574    /// - `Ok(())` if the attempt is allowed
575    /// - `Err(lockout_remaining_secs)` if the identifier is locked out
576    pub async fn check_rate_limit(&self, identifier: &str) -> Result<(), u64> {
577        self.rate_limiter.check_and_record(identifier).await
578    }
579
580    /// Reset the rate limit for a given identifier (e.g., after successful login).
581    ///
582    /// # Parameters
583    ///
584    /// - `identifier`: The identifier to reset
585    pub async fn reset_rate_limit(&self, identifier: &str) {
586        self.rate_limiter.reset(identifier).await;
587    }
588
589    /// Log an audit event for security tracking.
590    ///
591    /// # Parameters
592    ///
593    /// - `event`: The audit event to log
594    pub async fn log_audit_event(&self, event: RiAuditEvent) {
595        self.audit_logger.log(event).await;
596    }
597
598    /// Log a login attempt event.
599    ///
600    /// # Parameters
601    ///
602    /// - `username`: The username that attempted to log in
603    /// - `ip_address`: The IP address of the login attempt
604    /// - `success`: Whether the login was successful
605    /// - `reason`: Optional reason for failure or success
606    pub async fn log_login_attempt(&self, username: &str, ip_address: Option<&str>, success: bool, reason: Option<&str>) {
607        let mut event = RiAuditEvent::new("LOGIN_ATTEMPT", if success { "login_success" } else { "login_failure" });
608        event = event.with_user(username);
609        if let Some(ip) = ip_address {
610            event = event.with_ip(ip);
611        }
612        if let Some(r) = reason {
613            event = event.with_details(r);
614        }
615        event = event.with_success(success);
616        self.audit_logger.log(event).await;
617    }
618
619    /// Get recent audit events.
620    ///
621    /// # Returns
622    ///
623    /// A vector of recent audit events
624    pub async fn get_audit_events(&self) -> Vec<RiAuditEvent> {
625        self.audit_logger.get_events().await
626    }
627
628    /// Returns a reference to the JWT manager.
629    /// 
630    /// # Returns
631    /// 
632    /// An Arc<RiJWTManager> providing thread-safe access to the JWT manager
633    pub fn jwt_manager(&self) -> Arc<RiJWTManager> {
634        self.jwt_manager.clone()
635    }
636
637    /// Returns a reference to the session manager.
638    /// 
639    /// # Returns
640    /// 
641    /// An Arc<RwLock<RiSessionManager>> providing thread-safe access to the session manager
642    pub fn session_manager(&self) -> Arc<RwLock<RiSessionManager>> {
643        self.session_manager.clone()
644    }
645
646    /// Returns a reference to the permission manager.
647    /// 
648    /// # Returns
649    /// 
650    /// An Arc<RwLock<RiPermissionManager>> providing thread-safe access to the permission manager
651    pub fn permission_manager(&self) -> Arc<RwLock<RiPermissionManager>> {
652        self.permission_manager.clone()
653    }
654
655    /// Returns a reference to the OAuth manager.
656    /// 
657    /// # Returns
658    /// 
659    /// An Arc<RwLock<RiOAuthManager>> providing thread-safe access to the OAuth manager
660    pub fn oauth_manager(&self) -> Arc<RwLock<RiOAuthManager>> {
661        self.oauth_manager.clone()
662    }
663}
664
665#[cfg(feature = "pyo3")]
666#[pyo3::prelude::pymethods]
667impl RiAuthConfig {
668    /// Creates a new authentication configuration with the specified parameters.
669    ///
670    /// All parameters have sensible defaults, making it easy to create a basic configuration.
671    /// The JWT secret is automatically loaded from the `Ri_JWT_SECRET` environment variable
672    /// if not provided.
673    ///
674    /// # Parameters
675    ///
676    /// - `enabled`: Whether authentication is enabled (default: true)
677    /// - `jwt_secret`: Secret key for JWT tokens (default: loaded from Ri_JWT_SECRET env var)
678    /// - `jwt_expiry_secs`: JWT token expiry time in seconds (default: 3600)
679    /// - `session_timeout_secs`: Session timeout in seconds (default: 86400)
680    /// - `oauth_providers`: List of OAuth providers to enable (default: empty)
681    /// - `enable_api_keys`: Whether API key authentication is enabled (default: true)
682    /// - `enable_session_auth`: Whether session authentication is enabled (default: true)
683    /// - `oauth_cache_backend_type`: Cache backend type - "Memory" or "Redis" (default: "Memory")
684    /// - `oauth_cache_redis_url`: Redis URL for OAuth cache (default: "redis://127.0.0.1:6379")
685    ///
686    /// # Returns
687    ///
688    /// A new `RiAuthConfig` instance
689    ///
690    /// # Example
691    ///
692    /// ```python
693    /// from ri import RiAuthConfig
694    ///
695    /// # Create with defaults
696    /// config = RiAuthConfig()
697    ///
698    /// # Create with custom settings
699    /// config = RiAuthConfig(
700    ///     enabled=True,
701    ///     jwt_secret="my-secret-key",
702    ///     jwt_expiry_secs=7200,
703    ///     oauth_providers=["google", "github"]
704    /// )
705    /// ```
706    #[new]
707    #[pyo3(signature = (
708        enabled = true,
709        jwt_secret = "",
710        jwt_expiry_secs = 3600,
711        session_timeout_secs = 86400,
712        oauth_providers = vec![],
713        enable_api_keys = true,
714        enable_session_auth = true,
715        oauth_cache_backend_type = None,
716        oauth_cache_redis_url = "redis://127.0.0.1:6379",
717        rate_limit_max_login_attempts = 5,
718        rate_limit_lockout_secs = 300,
719        rate_limit_window_secs = 900
720    ))]
721    fn py_new(
722        enabled: bool,
723        jwt_secret: &str,
724        jwt_expiry_secs: u64,
725        session_timeout_secs: u64,
726        oauth_providers: Vec<String>,
727        enable_api_keys: bool,
728        enable_session_auth: bool,
729        oauth_cache_backend_type: Option<String>,
730        oauth_cache_redis_url: &str,
731        rate_limit_max_login_attempts: u32,
732        rate_limit_lockout_secs: u64,
733        rate_limit_window_secs: u64,
734    ) -> Self {
735        let secret = if jwt_secret.is_empty() {
736            load_jwt_secret_from_env()
737        } else {
738            jwt_secret.to_string()
739        };
740        
741        #[cfg(feature = "cache")]
742        {
743            let backend_type = match oauth_cache_backend_type.as_deref() {
744                Some("Redis") => crate::cache::RiCacheBackendType::Redis,
745                _ => crate::cache::RiCacheBackendType::Memory,
746            };
747            
748            Self {
749                enabled,
750                jwt_secret: secret,
751                jwt_expiry_secs,
752                session_timeout_secs,
753                oauth_providers,
754                enable_api_keys,
755                enable_session_auth,
756                oauth_cache_backend_type: backend_type,
757                oauth_cache_redis_url: oauth_cache_redis_url.to_string(),
758                rate_limit_max_login_attempts,
759                rate_limit_lockout_secs,
760                rate_limit_window_secs,
761            }
762        }
763        
764        #[cfg(not(feature = "cache"))]
765        {
766            let _ = oauth_cache_backend_type;
767            let _ = oauth_cache_redis_url;
768            
769            Self {
770                enabled,
771                jwt_secret: secret,
772                jwt_expiry_secs,
773                session_timeout_secs,
774                oauth_providers,
775                enable_api_keys,
776                enable_session_auth,
777                rate_limit_max_login_attempts,
778                rate_limit_lockout_secs,
779                rate_limit_window_secs,
780            }
781        }
782    }
783
784    /// Creates a new authentication configuration with default values.
785    ///
786    /// This is a convenience method that creates a configuration with all default settings.
787    /// The JWT secret is loaded from the `Ri_JWT_SECRET` environment variable.
788    ///
789    /// # Returns
790    ///
791    /// A new `RiAuthConfig` instance with default values
792    ///
793    /// # Example
794    ///
795    /// ```python
796    /// from ri import RiAuthConfig
797    ///
798    /// config = RiAuthConfig.default()
799    /// ```
800    #[staticmethod]
801    fn default() -> Self {
802        <Self as Default>::default()
803    }
804
805    /// Creates a new authentication configuration from environment variables.
806    ///
807    /// This method loads the JWT secret from the `Ri_JWT_SECRET` environment variable
808    /// and uses default values for all other settings.
809    ///
810    /// # Returns
811    ///
812    /// A new `RiAuthConfig` instance with values from environment
813    ///
814    /// # Example
815    ///
816    /// ```python
817    /// import os
818    /// from ri import RiAuthConfig
819    ///
820    /// os.environ["Ri_JWT_SECRET"] = "my-secret-key"
821    /// config = RiAuthConfig.from_env()
822    /// ```
823    #[staticmethod]
824    fn from_env() -> Self {
825        Self {
826            jwt_secret: load_jwt_secret_from_env(),
827            ..Self::default()
828        }
829    }
830
831    /// Returns whether authentication is enabled.
832    #[getter]
833    fn get_enabled(&self) -> bool {
834        self.enabled
835    }
836
837    /// Returns the JWT secret key.
838    #[getter]
839    fn get_jwt_secret(&self) -> String {
840        self.jwt_secret.clone()
841    }
842
843    /// Returns the JWT token expiry time in seconds.
844    #[getter]
845    fn get_jwt_expiry_secs(&self) -> u64 {
846        self.jwt_expiry_secs
847    }
848
849    /// Returns the session timeout in seconds.
850    #[getter]
851    fn get_session_timeout_secs(&self) -> u64 {
852        self.session_timeout_secs
853    }
854
855    /// Returns the list of OAuth providers.
856    #[getter]
857    fn get_oauth_providers(&self) -> Vec<String> {
858        self.oauth_providers.clone()
859    }
860
861    /// Returns whether API key authentication is enabled.
862    #[getter]
863    fn get_enable_api_keys(&self) -> bool {
864        self.enable_api_keys
865    }
866
867    /// Returns whether session authentication is enabled.
868    #[getter]
869    fn get_enable_session_auth(&self) -> bool {
870        self.enable_session_auth
871    }
872}
873
874#[cfg(feature = "pyo3")]
875/// Python bindings for the Ri Authentication Module.
876///
877/// This module provides Python interface to Ri authentication functionality,
878/// enabling Python applications to leverage Ri's authentication capabilities.
879///
880/// ## Supported Operations
881///
882/// - JWT token generation and validation
883/// - Session management for stateful authentication
884/// - Permission and role management for RBAC
885/// - OAuth provider integration
886///
887/// ## Python Usage Example
888///
889/// ```python
890/// from ri import RiAuthConfig, RiJWTManager
891///
892/// # Create auth configuration
893/// config = RiAuthConfig(
894///     enabled=True,
895///     jwt_secret="secure-secret-key",
896///     jwt_expiry_secs=3600,
897///     session_timeout_secs=86400,
898///     oauth_providers=["google", "github"],
899///     enable_api_keys=True,
900///     enable_session_auth=True,
901/// )
902///
903/// # Create auth module
904/// auth_module = RiAuthModule(config)
905///
906/// # Get JWT manager and generate token
907/// jwt_manager = auth_module.jwt_manager()
908/// token = jwt_manager.generate_token("user123", ["user"], ["read:data"])
909/// ```
910#[pyo3::prelude::pymethods]
911impl RiAuthModule {
912    #[new]
913    fn py_new(config: RiAuthConfig) -> PyResult<Self> {
914        // Python interpreters do not guarantee an ambient Tokio reactor; use
915        // the process-wide runtime shared by all PyO3 bindings instead of
916        // `Handle::current()`, which panics when no reactor is running.
917        crate::py_runtime::py_runtime().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}