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}