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 std::env;
124use std::sync::Arc;
125use tokio::sync::RwLock;
126#[cfg(feature = "pyo3")]
127use tokio::runtime::Handle;
128
129const DEFAULT_JWT_SECRET_ENV: &str = "Ri_JWT_SECRET";
130const FALLBACK_SECRET_LENGTH: usize = 64;
131
132fn load_jwt_secret_from_env() -> String {
133 env::var(DEFAULT_JWT_SECRET_ENV).unwrap_or_else(|_| {
134 let mut secret = vec![0u8; FALLBACK_SECRET_LENGTH];
135 rand::thread_rng().fill_bytes(&mut secret);
136 hex::encode(secret)
137 })
138}
139
140fn load_oauth_env_var(provider_name: &str, suffix: &str) -> Result<String, RiError> {
141 let env_var = format!("Ri_OAUTH_{}_{}", provider_name.to_uppercase(), suffix);
142 env::var(&env_var).map_err(|_| {
143 RiError::Config(format!(
144 "OAuth {} is not set for provider '{}'. Please set the environment variable {}",
145 suffix.to_lowercase(),
146 provider_name,
147 env_var
148 ))
149 })
150}
151
152fn get_oauth_url(provider_name: &str, endpoint: &str) -> String {
153 match load_oauth_env_var(provider_name, endpoint) {
154 Ok(url) if !url.is_empty() => url,
155 _ => format!("https://{}.com/oauth/{}", provider_name, endpoint)
156 }
157}
158
159#[cfg(feature = "pyo3")]
160use pyo3::PyResult;
161
162/// Configuration for the authentication module.
163///
164/// This struct defines the configuration options for authentication behavior, including
165/// JWT settings, session settings, OAuth providers, and enabled authentication methods.
166///
167/// ## Security
168///
169/// The JWT secret is loaded from the `Ri_JWT_SECRET` environment variable. If not set,
170/// a cryptographically secure random secret is generated automatically.
171///
172/// **Important**: For production environments, always set the `Ri_JWT_SECRET` environment
173/// variable to a strong, unique value. Do not rely on auto-generated secrets in production.
174#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
175#[derive(Debug, Clone)]
176#[derive(Deserialize)]
177pub struct RiAuthConfig {
178 /// Whether authentication is enabled
179 pub enabled: bool,
180 /// Secret key for JWT token generation and validation
181 pub jwt_secret: String,
182 /// JWT token expiry time in seconds
183 pub jwt_expiry_secs: u64,
184 /// Session timeout in seconds
185 pub session_timeout_secs: u64,
186 /// List of OAuth providers to enable
187 pub oauth_providers: Vec<String>,
188 /// Whether API key authentication is enabled
189 pub enable_api_keys: bool,
190 /// Whether session authentication is enabled
191 pub enable_session_auth: bool,
192 /// OAuth token cache backend type (Memory or Redis)
193 #[cfg(feature = "cache")]
194 pub oauth_cache_backend_type: crate::cache::RiCacheBackendType,
195 /// Redis URL for OAuth token cache (used when backend is Redis)
196 #[cfg(feature = "cache")]
197 pub oauth_cache_redis_url: String,
198}
199
200impl Default for RiAuthConfig {
201 fn default() -> Self {
202 Self {
203 enabled: true,
204 jwt_secret: load_jwt_secret_from_env(),
205 jwt_expiry_secs: 3600,
206 session_timeout_secs: 86400,
207 oauth_providers: vec![],
208 enable_api_keys: true,
209 enable_session_auth: true,
210 #[cfg(feature = "cache")]
211 oauth_cache_backend_type: crate::cache::RiCacheBackendType::Memory,
212 #[cfg(feature = "cache")]
213 oauth_cache_redis_url: "redis://127.0.0.1:6379".to_string(),
214 }
215 }
216}
217
218/// Main authentication module for Ri.
219///
220/// This module provides comprehensive authentication and authorization functionality,
221/// including JWT management, session management, permission management, and OAuth integration.
222#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
223pub struct RiAuthModule {
224 /// Authentication configuration
225 config: RiAuthConfig,
226 /// JWT manager for stateless authentication
227 jwt_manager: Arc<RiJWTManager>,
228 /// Session manager for stateful authentication, protected by a RwLock for thread-safe access
229 session_manager: Arc<RwLock<RiSessionManager>>,
230 /// Permission manager for role-based access control, protected by a RwLock for thread-safe access
231 permission_manager: Arc<RwLock<RiPermissionManager>>,
232 /// OAuth manager for OAuth provider integration, protected by a RwLock for thread-safe access
233 oauth_manager: Arc<RwLock<RiOAuthManager>>,
234 /// JWT token revocation list for token invalidation
235 revocation_list: Arc<RiJWTRevocationList>,
236}
237
238impl RiAuthModule {
239 /// Creates a new authentication module with the given configuration.
240 ///
241 /// **Performance Note**: This method creates a permission manager using the synchronous
242 /// `new()` method which uses `blocking_write` during initialization. For async contexts,
243 /// consider using `new_async()` to avoid blocking the runtime.
244 ///
245 /// # Parameters
246 ///
247 /// - `config`: The authentication configuration to use
248 ///
249 /// # Returns
250 ///
251 /// A `RiResult` containing the new `RiAuthModule` instance
252 ///
253 /// # Errors
254 ///
255 /// Returns an error if Redis cache creation fails when Redis backend is configured
256 pub async fn new(config: RiAuthConfig) -> crate::core::error::RiResult<Self> {
257 let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
258 let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
259 let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new()));
260
261 #[cfg(feature = "cache")]
262 let cache: Arc<dyn crate::cache::RiCache> = match config.oauth_cache_backend_type {
263 crate::cache::RiCacheBackendType::Memory => {
264 Arc::new(crate::cache::RiMemoryCache::new())
265 }
266 crate::cache::RiCacheBackendType::Redis => {
267 let cache = crate::cache::RiRedisCache::new(&config.oauth_cache_redis_url).await
268 .map_err(|e| crate::core::error::RiError::RedisError(format!("Failed to create Redis cache for OAuth: {}", e)))?;
269 Arc::new(cache)
270 }
271 _ => Arc::new(crate::cache::RiMemoryCache::new()),
272 };
273
274 #[cfg(not(feature = "cache"))]
275 let cache = Arc::new(crate::cache::RiMemoryCache::new());
276
277 let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
278 let revocation_list = Arc::new(RiJWTRevocationList::new());
279
280 Ok(Self {
281 config,
282 jwt_manager,
283 session_manager,
284 permission_manager,
285 oauth_manager,
286 revocation_list,
287 })
288 }
289
290 /// Creates a new authentication module with the given configuration (synchronous version).
291 ///
292 /// This is a synchronous wrapper for use in the builder pattern.
293 ///
294 /// # Parameters
295 ///
296 /// - `config`: The authentication configuration to use
297 ///
298 /// # Returns
299 ///
300 /// A new `RiAuthModule` instance
301 pub fn with_config(config: RiAuthConfig) -> Self {
302 let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
303 let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
304 let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new()));
305 let cache = Arc::new(crate::cache::RiMemoryCache::new());
306 let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
307 let revocation_list = Arc::new(RiJWTRevocationList::new());
308
309 Self {
310 config,
311 jwt_manager,
312 session_manager,
313 permission_manager,
314 oauth_manager,
315 revocation_list,
316 }
317 }
318
319 /// Creates a new authentication module with the given configuration asynchronously.
320 ///
321 /// This method is preferred for async contexts as it avoids blocking the runtime
322 /// during permission manager initialization by using the async `new_async()` method.
323 ///
324 /// # Parameters
325 ///
326 /// - `config`: The authentication configuration to use
327 ///
328 /// # Returns
329 ///
330 /// A `RiResult` containing the new `RiAuthModule` instance
331 ///
332 /// # Errors
333 ///
334 /// Returns an error if Redis cache creation fails when Redis backend is configured
335 pub async fn new_async(config: RiAuthConfig) -> crate::core::error::RiResult<Self> {
336 let jwt_manager = Arc::new(RiJWTManager::create(config.jwt_secret.clone(), config.jwt_expiry_secs));
337 let session_manager = Arc::new(RwLock::new(RiSessionManager::new(config.session_timeout_secs)));
338 let permission_manager = Arc::new(RwLock::new(RiPermissionManager::new_async().await));
339
340 #[cfg(feature = "cache")]
341 let cache: Arc<dyn crate::cache::RiCache> = match config.oauth_cache_backend_type {
342 crate::cache::RiCacheBackendType::Memory => {
343 Arc::new(crate::cache::RiMemoryCache::new())
344 }
345 crate::cache::RiCacheBackendType::Redis => {
346 let cache = crate::cache::RiRedisCache::new(&config.oauth_cache_redis_url).await
347 .map_err(|e| crate::core::error::RiError::RedisError(format!("Failed to create Redis cache: {}", e)))?;
348 Arc::new(cache)
349 }
350 _ => Arc::new(crate::cache::RiMemoryCache::new()),
351 };
352
353 #[cfg(not(feature = "cache"))]
354 let cache = Arc::new(crate::cache::RiMemoryCache::new());
355
356 let oauth_manager = Arc::new(RwLock::new(RiOAuthManager::new(cache)));
357 let revocation_list = Arc::new(RiJWTRevocationList::new());
358
359 Ok(Self {
360 config,
361 jwt_manager,
362 session_manager,
363 permission_manager,
364 oauth_manager,
365 revocation_list,
366 })
367 }
368
369 /// Returns a reference to the JWT revocation list.
370 ///
371 /// # Returns
372 ///
373 /// An Arc<RiJWTRevocationList> providing thread-safe access to the token revocation list
374 pub fn revocation_list(&self) -> Arc<RiJWTRevocationList> {
375 self.revocation_list.clone()
376 }
377
378 /// Returns a reference to the JWT manager.
379 ///
380 /// # Returns
381 ///
382 /// An Arc<RiJWTManager> providing thread-safe access to the JWT manager
383 pub fn jwt_manager(&self) -> Arc<RiJWTManager> {
384 self.jwt_manager.clone()
385 }
386
387 /// Returns a reference to the session manager.
388 ///
389 /// # Returns
390 ///
391 /// An Arc<RwLock<RiSessionManager>> providing thread-safe access to the session manager
392 pub fn session_manager(&self) -> Arc<RwLock<RiSessionManager>> {
393 self.session_manager.clone()
394 }
395
396 /// Returns a reference to the permission manager.
397 ///
398 /// # Returns
399 ///
400 /// An Arc<RwLock<RiPermissionManager>> providing thread-safe access to the permission manager
401 pub fn permission_manager(&self) -> Arc<RwLock<RiPermissionManager>> {
402 self.permission_manager.clone()
403 }
404
405 /// Returns a reference to the OAuth manager.
406 ///
407 /// # Returns
408 ///
409 /// An Arc<RwLock<RiOAuthManager>> providing thread-safe access to the OAuth manager
410 pub fn oauth_manager(&self) -> Arc<RwLock<RiOAuthManager>> {
411 self.oauth_manager.clone()
412 }
413}
414
415#[cfg(feature = "pyo3")]
416#[pyo3::prelude::pymethods]
417impl RiAuthConfig {
418 /// Creates a new authentication configuration with the specified parameters.
419 ///
420 /// All parameters have sensible defaults, making it easy to create a basic configuration.
421 /// The JWT secret is automatically loaded from the `Ri_JWT_SECRET` environment variable
422 /// if not provided.
423 ///
424 /// # Parameters
425 ///
426 /// - `enabled`: Whether authentication is enabled (default: true)
427 /// - `jwt_secret`: Secret key for JWT tokens (default: loaded from Ri_JWT_SECRET env var)
428 /// - `jwt_expiry_secs`: JWT token expiry time in seconds (default: 3600)
429 /// - `session_timeout_secs`: Session timeout in seconds (default: 86400)
430 /// - `oauth_providers`: List of OAuth providers to enable (default: empty)
431 /// - `enable_api_keys`: Whether API key authentication is enabled (default: true)
432 /// - `enable_session_auth`: Whether session authentication is enabled (default: true)
433 /// - `oauth_cache_backend_type`: Cache backend type - "Memory" or "Redis" (default: "Memory")
434 /// - `oauth_cache_redis_url`: Redis URL for OAuth cache (default: "redis://127.0.0.1:6379")
435 ///
436 /// # Returns
437 ///
438 /// A new `RiAuthConfig` instance
439 ///
440 /// # Example
441 ///
442 /// ```python
443 /// from ri import RiAuthConfig
444 ///
445 /// # Create with defaults
446 /// config = RiAuthConfig()
447 ///
448 /// # Create with custom settings
449 /// config = RiAuthConfig(
450 /// enabled=True,
451 /// jwt_secret="my-secret-key",
452 /// jwt_expiry_secs=7200,
453 /// oauth_providers=["google", "github"]
454 /// )
455 /// ```
456 #[new]
457 #[pyo3(signature = (
458 enabled = true,
459 jwt_secret = "",
460 jwt_expiry_secs = 3600,
461 session_timeout_secs = 86400,
462 oauth_providers = vec![],
463 enable_api_keys = true,
464 enable_session_auth = true,
465 oauth_cache_backend_type = None,
466 oauth_cache_redis_url = "redis://127.0.0.1:6379"
467 ))]
468 fn py_new(
469 enabled: bool,
470 jwt_secret: &str,
471 jwt_expiry_secs: u64,
472 session_timeout_secs: u64,
473 oauth_providers: Vec<String>,
474 enable_api_keys: bool,
475 enable_session_auth: bool,
476 oauth_cache_backend_type: Option<String>,
477 oauth_cache_redis_url: &str,
478 ) -> Self {
479 let secret = if jwt_secret.is_empty() {
480 load_jwt_secret_from_env()
481 } else {
482 jwt_secret.to_string()
483 };
484
485 #[cfg(feature = "cache")]
486 {
487 let backend_type = match oauth_cache_backend_type.as_deref() {
488 Some("Redis") => crate::cache::RiCacheBackendType::Redis,
489 _ => crate::cache::RiCacheBackendType::Memory,
490 };
491
492 Self {
493 enabled,
494 jwt_secret: secret,
495 jwt_expiry_secs,
496 session_timeout_secs,
497 oauth_providers,
498 enable_api_keys,
499 enable_session_auth,
500 oauth_cache_backend_type: backend_type,
501 oauth_cache_redis_url: oauth_cache_redis_url.to_string(),
502 }
503 }
504
505 #[cfg(not(feature = "cache"))]
506 {
507 let _ = oauth_cache_backend_type;
508 let _ = oauth_cache_redis_url;
509
510 Self {
511 enabled,
512 jwt_secret: secret,
513 jwt_expiry_secs,
514 session_timeout_secs,
515 oauth_providers,
516 enable_api_keys,
517 enable_session_auth,
518 }
519 }
520 }
521
522 /// Creates a new authentication configuration with default values.
523 ///
524 /// This is a convenience method that creates a configuration with all default settings.
525 /// The JWT secret is loaded from the `Ri_JWT_SECRET` environment variable.
526 ///
527 /// # Returns
528 ///
529 /// A new `RiAuthConfig` instance with default values
530 ///
531 /// # Example
532 ///
533 /// ```python
534 /// from ri import RiAuthConfig
535 ///
536 /// config = RiAuthConfig.default()
537 /// ```
538 #[staticmethod]
539 fn default() -> Self {
540 <Self as Default>::default()
541 }
542
543 /// Creates a new authentication configuration from environment variables.
544 ///
545 /// This method loads the JWT secret from the `Ri_JWT_SECRET` environment variable
546 /// and uses default values for all other settings.
547 ///
548 /// # Returns
549 ///
550 /// A new `RiAuthConfig` instance with values from environment
551 ///
552 /// # Example
553 ///
554 /// ```python
555 /// import os
556 /// from ri import RiAuthConfig
557 ///
558 /// os.environ["Ri_JWT_SECRET"] = "my-secret-key"
559 /// config = RiAuthConfig.from_env()
560 /// ```
561 #[staticmethod]
562 fn from_env() -> Self {
563 Self {
564 jwt_secret: load_jwt_secret_from_env(),
565 ..Self::default()
566 }
567 }
568
569 /// Returns whether authentication is enabled.
570 #[getter]
571 fn get_enabled(&self) -> bool {
572 self.enabled
573 }
574
575 /// Returns the JWT secret key.
576 #[getter]
577 fn get_jwt_secret(&self) -> String {
578 self.jwt_secret.clone()
579 }
580
581 /// Returns the JWT token expiry time in seconds.
582 #[getter]
583 fn get_jwt_expiry_secs(&self) -> u64 {
584 self.jwt_expiry_secs
585 }
586
587 /// Returns the session timeout in seconds.
588 #[getter]
589 fn get_session_timeout_secs(&self) -> u64 {
590 self.session_timeout_secs
591 }
592
593 /// Returns the list of OAuth providers.
594 #[getter]
595 fn get_oauth_providers(&self) -> Vec<String> {
596 self.oauth_providers.clone()
597 }
598
599 /// Returns whether API key authentication is enabled.
600 #[getter]
601 fn get_enable_api_keys(&self) -> bool {
602 self.enable_api_keys
603 }
604
605 /// Returns whether session authentication is enabled.
606 #[getter]
607 fn get_enable_session_auth(&self) -> bool {
608 self.enable_session_auth
609 }
610}
611
612#[cfg(feature = "pyo3")]
613/// Python bindings for the Ri Authentication Module.
614///
615/// This module provides Python interface to Ri authentication functionality,
616/// enabling Python applications to leverage Ri's authentication capabilities.
617///
618/// ## Supported Operations
619///
620/// - JWT token generation and validation
621/// - Session management for stateful authentication
622/// - Permission and role management for RBAC
623/// - OAuth provider integration
624///
625/// ## Python Usage Example
626///
627/// ```python
628/// from ri import RiAuthConfig, RiJWTManager
629///
630/// # Create auth configuration
631/// config = RiAuthConfig(
632/// enabled=True,
633/// jwt_secret="secure-secret-key",
634/// jwt_expiry_secs=3600,
635/// session_timeout_secs=86400,
636/// oauth_providers=["google", "github"],
637/// enable_api_keys=True,
638/// enable_session_auth=True,
639/// )
640///
641/// # Create auth module
642/// auth_module = RiAuthModule(config)
643///
644/// # Get JWT manager and generate token
645/// jwt_manager = auth_module.jwt_manager()
646/// token = jwt_manager.generate_token("user123", ["user"], ["read:data"])
647/// ```
648#[pyo3::prelude::pymethods]
649impl RiAuthModule {
650 #[new]
651 fn py_new(config: RiAuthConfig) -> PyResult<Self> {
652 let rt = Handle::current();
653 rt.block_on(async {
654 Self::new(config).await
655 .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
656 })
657 }
658
659 #[getter]
660 fn get_config(&self) -> RiAuthConfig {
661 self.config.clone()
662 }
663
664 #[getter]
665 fn get_jwt_expiry_secs(&self) -> u64 {
666 self.jwt_manager.get_token_expiry()
667 }
668
669 #[getter]
670 fn get_session_timeout_secs(&self) -> u64 {
671 self.config.session_timeout_secs
672 }
673
674 #[getter]
675 fn is_enabled(&self) -> bool {
676 self.config.enabled
677 }
678
679 #[getter]
680 fn is_api_keys_enabled(&self) -> bool {
681 self.config.enable_api_keys
682 }
683
684 #[getter]
685 fn is_session_auth_enabled(&self) -> bool {
686 self.config.enable_session_auth
687 }
688
689 #[getter]
690 fn get_oauth_providers(&self) -> Vec<String> {
691 self.config.oauth_providers.clone()
692 }
693
694 fn validate_jwt_token(&self, token: &str) -> bool {
695 self.jwt_manager.validate_token(token).is_ok()
696 }
697
698 fn generate_test_token(&self, subject: &str, roles: Vec<String>, permissions: Vec<String>) -> PyResult<String> {
699 self.jwt_manager.generate_token(subject, roles, permissions)
700 .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
701 }
702}
703
704impl crate::core::ServiceModule for RiAuthModule {
705 fn name(&self) -> &str {
706 "Ri.Auth"
707 }
708
709 fn is_critical(&self) -> bool {
710 false
711 }
712
713 fn priority(&self) -> i32 {
714 20
715 }
716
717 fn dependencies(&self) -> Vec<&str> {
718 vec![]
719 }
720
721 fn init(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
722 Ok(())
723 }
724
725 fn start(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
726 Ok(())
727 }
728
729 fn shutdown(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
730 Ok(())
731 }
732}
733
734#[async_trait::async_trait]
735impl crate::core::RiModule for RiAuthModule {
736 /// Returns the name of the authentication module.
737 ///
738 /// # Returns
739 ///
740 /// The module name as a string
741 fn name(&self) -> &str {
742 "Ri.Auth"
743 }
744
745 /// Indicates whether the authentication module is critical.
746 ///
747 /// The authentication module is non-critical, meaning that if it fails to initialize or operate,
748 /// it should not break the entire application. This allows the core functionality to continue
749 /// even if authentication features are unavailable.
750 ///
751 /// # Returns
752 ///
753 /// `false` since authentication is non-critical
754 fn is_critical(&self) -> bool {
755 false // Auth failures should not break the application
756 }
757
758 /// Initializes the authentication module asynchronously.
759 ///
760 /// This method performs the following steps:
761 /// 1. Loads configuration from the service context
762 /// 2. Updates the module configuration if provided
763 /// 3. Reinitializes the JWT manager with the new configuration
764 /// 4. Initializes OAuth providers if configured
765 ///
766 /// # Parameters
767 ///
768 /// - `ctx`: The service context containing configuration
769 ///
770 /// # Returns
771 ///
772 /// A `RiResult<()>` indicating success or failure
773 async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
774 log::info!("Initializing Ri Auth Module");
775
776 // Load configuration
777 let binding = ctx.config();
778 let cfg = binding.config();
779
780 // Update configuration if provided
781 if let Some(auth_config) = cfg.get("auth") {
782 self.config = serde_yaml::from_str(auth_config)
783 .unwrap_or_else(|_| RiAuthConfig::default());
784 }
785
786 // Initialize JWT manager with new config
787 self.jwt_manager = Arc::new(RiJWTManager::create(self.config.jwt_secret.clone(), self.config.jwt_expiry_secs));
788
789 // Initialize OAuth providers if configured
790 if !self.config.oauth_providers.is_empty() {
791 for provider_name in &self.config.oauth_providers {
792 let client_id = load_oauth_env_var(provider_name, "CLIENT_ID")?;
793 let client_secret = load_oauth_env_var(provider_name, "CLIENT_SECRET")?;
794
795 let provider_config = crate::auth::oauth::RiOAuthProvider {
796 id: provider_name.clone(),
797 name: provider_name.clone(),
798 client_id,
799 client_secret,
800 auth_url: get_oauth_url(provider_name, "authorize"),
801 token_url: get_oauth_url(provider_name, "token"),
802 user_info_url: get_oauth_url(provider_name, "userinfo"),
803 scopes: vec!["openid".to_string(), "profile".to_string(), "email".to_string()],
804 enabled: true,
805 redirect_uri: None,
806 };
807
808 let oauth_mgr = self.oauth_manager.write().await;
809 oauth_mgr.register_provider(provider_config).await?;
810 log::info!("OAuth provider registered: {provider_name}");
811 }
812 }
813
814 log::info!("Ri Auth Module initialized successfully");
815 Ok(())
816 }
817
818 /// Performs asynchronous cleanup after the application has shut down.
819 ///
820 /// This method cleans up all sessions managed by the session manager.
821 ///
822 /// # Parameters
823 ///
824 /// - `_ctx`: The service context (not used in this implementation)
825 ///
826 /// # Returns
827 ///
828 /// A `RiResult<()>` indicating success or failure
829 async fn after_shutdown(&mut self, _ctx: &mut RiServiceContext) -> RiResult<()> {
830 log::info!("Cleaning up Ri Auth Module");
831
832 // Cleanup sessions
833 let session_mgr = self.session_manager.write().await;
834 session_mgr.cleanup_all().await?;
835
836 log::info!("Ri Auth Module cleanup completed");
837 Ok(())
838 }
839}