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}