armature_auth/
lib.rs

1//! Authentication and authorization for Armature.
2//!
3//! This crate provides comprehensive authentication and authorization
4//! capabilities including JWT, OAuth2, SAML, password hashing, and guards.
5//!
6//! ## Features
7//!
8//! - 🔐 **JWT Authentication** - Token-based auth with `armature-jwt`
9//! - 🌐 **OAuth2** - Google, Auth0, Microsoft, AWS Cognito, Okta
10//! - 🔑 **SAML** - Enterprise SAML 2.0 authentication (requires `saml` feature)
11//! - 🔒 **Password Hashing** - Secure bcrypt-based hashing
12//! - 🛡️ **Guards** - Route protection with auth and role guards
13//! - 👤 **User Context** - Request-scoped user information
14//!
15//! ## Cargo Features
16//!
17//! - `default` - Core authentication (JWT, OAuth2, password hashing)
18//! - `saml` - SAML 2.0 support (requires openssl/xmlsec1 system libraries)
19//!
20//! ### SAML System Requirements
21//!
22//! The `saml` feature requires system libraries for XML signature verification:
23//! - **Ubuntu/Debian**: `apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl`
24//! - **macOS**: `brew install libxmlsec1`
25//! - **Windows**: Not recommended (complex setup)
26//!
27//! ## Quick Start - Password Authentication
28//!
29//! ```
30//! use armature_auth::{AuthService, PasswordHasher, PasswordVerifier};
31//!
32//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! let auth_service = AuthService::new();
34//!
35//! // Hash a password
36//! let hash = auth_service.hash_password("secret123")?;
37//!
38//! // Verify password
39//! let is_valid = auth_service.verify_password("secret123", &hash)?;
40//! assert!(is_valid);
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! ## JWT Authentication
46//!
47//! ```no_run
48//! use armature_auth::AuthService;
49//! use armature_jwt::{JwtConfig, JwtManager};
50//!
51//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
52//! // Create JWT manager
53//! let jwt_config = JwtConfig::new("your-secret-key".to_string());
54//! let jwt_manager = JwtManager::new(jwt_config)?;
55//!
56//! // Create auth service with JWT
57//! let auth_service = AuthService::with_jwt(jwt_manager);
58//!
59//! // JWT manager is now available
60//! assert!(auth_service.jwt_manager().is_some());
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! ## OAuth2 - Google Example
66//!
67//! ```no_run
68//! use armature_auth::{GoogleProvider, providers::GoogleConfig, OAuth2Provider};
69//!
70//! # #[tokio::main]
71//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
72//! let config = GoogleConfig::new(
73//!     "client-id".to_string(),
74//!     "client-secret".to_string(),
75//!     "https://myapp.com/callback".to_string(),
76//! );
77//!
78//! let provider = GoogleProvider::new(config)?;
79//!
80//! // Generate authorization URL
81//! let (auth_url, state) = provider.authorization_url()?;
82//! println!("Redirect user to: {}", auth_url);
83//! println!("State: {}", state.secret());
84//!
85//! // After callback, exchange code for token
86//! let token = provider.exchange_code("auth-code".to_string()).await?;
87//! println!("Access token: {}", token.access_token);
88//! # Ok(())
89//! # }
90//! ```
91//!
92//! ## Route Guards
93//!
94//! ```ignore
95//! use armature_auth::{AuthGuard, RoleGuard};
96//! use armature_core::{Controller, Get};
97//!
98//! #[controller("/admin")]
99//! struct AdminController;
100//!
101//! impl AdminController {
102//!     // Require authentication
103//!     #[get("/dashboard")]
104//!     #[guard(AuthGuard)]
105//!     async fn dashboard(&self) -> Result<HttpResponse, Error> {
106//!         Ok(HttpResponse::ok())
107//!     }
108//!
109//!     // Require specific role
110//!     #[get("/users")]
111//!     #[guard(RoleGuard::new("admin"))]
112//!     async fn users(&self) -> Result<HttpResponse, Error> {
113//!         Ok(HttpResponse::ok())
114//!     }
115//! }
116//! ```
117//!
118//! ## SAML Authentication
119//!
120//! ```ignore
121//! use armature_auth::{SamlServiceProvider, SamlConfig, IdpMetadata};
122//!
123//! # #[tokio::main]
124//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
125//! let config = SamlConfig {
126//!     entity_id: "https://myapp.com".to_string(),
127//!     acs_url: "https://myapp.com/callback".to_string(),
128//!     sls_url: None,
129//!     idp_metadata: IdpMetadata::Url("https://idp.example.com/metadata".to_string()),
130//!     sp_certificate: None,
131//!     sp_private_key: None,
132//!     contact_person: None,
133//!     allow_unsigned_assertions: false,
134//!     required_attributes: vec![],
135//! };
136//!
137//! let provider = SamlServiceProvider::new(config);
138//!
139//! // Generate SAML auth request
140//! let auth_request = provider.create_auth_request()?;
141//! println!("SAML Request: {}", auth_request.saml_request);
142//! # Ok(())
143//! # }
144//! ```
145
146pub mod api_key;
147pub mod error;
148pub mod guard;
149pub mod oauth2;
150pub mod password;
151pub mod passwordless;
152pub mod providers;
153#[cfg(feature = "saml")]
154pub mod saml;
155pub mod strategy;
156#[cfg(feature = "two-factor")]
157pub mod two_factor;
158pub mod user;
159
160pub use api_key::{ApiKey, ApiKeyError, ApiKeyManager, ApiKeyStore};
161pub use error::{AuthError, Result};
162pub use guard::{AuthGuard, Guard, RoleGuard};
163pub use oauth2::{OAuth2Provider, OAuth2Token, OAuth2UserInfo};
164pub use password::{PasswordHasher, PasswordVerifier};
165pub use passwordless::{MagicLinkToken, PasswordlessError, WebAuthnManager};
166#[cfg(feature = "saml")]
167pub use saml::{
168    ContactInfo, IdpMetadata, SamlAssertion, SamlAuthRequest, SamlConfig, SamlProvider,
169    SamlServiceProvider,
170};
171pub use strategy::{AuthStrategy, JwtStrategy, LocalStrategy};
172#[cfg(feature = "two-factor")]
173pub use two_factor::{BackupCodes, TotpSecret, TwoFactorError};
174pub use user::{AuthUser, UserContext};
175
176// Re-export providers
177pub use providers::{
178    Auth0Provider, AwsCognitoProvider, DiscordProvider, DiscordUser, GitHubProvider, GitHubUser,
179    GitLabProvider, GitLabUser, GoogleProvider, LinkedInProvider, LinkedInUser,
180    MicrosoftEntraProvider, OktaProvider,
181};
182
183use armature_jwt::JwtManager;
184use armature_log::{debug, trace};
185use std::sync::Arc;
186
187/// Authentication service for managing user authentication.
188///
189/// Provides password hashing, verification, and optional JWT token management.
190///
191/// # Examples
192///
193/// Basic password authentication:
194///
195/// ```
196/// use armature_auth::{AuthService, PasswordVerifier};
197///
198/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
199/// let service = AuthService::new();
200///
201/// // Hash password
202/// let hash = service.hash_password("mypassword")?;
203///
204/// // Verify password
205/// assert!(service.verify_password("mypassword", &hash)?);
206/// assert!(!service.verify_password("wrongpassword", &hash)?);
207/// # Ok(())
208/// # }
209/// ```
210#[derive(Clone)]
211pub struct AuthService {
212    jwt_manager: Option<Arc<JwtManager>>,
213    password_hasher: PasswordHasher,
214}
215
216impl AuthService {
217    /// Create a new authentication service
218    pub fn new() -> Self {
219        debug!("Creating new AuthService");
220        Self {
221            jwt_manager: None,
222            password_hasher: PasswordHasher::default(),
223        }
224    }
225
226    /// Create with JWT manager
227    pub fn with_jwt(jwt_manager: JwtManager) -> Self {
228        debug!("Creating AuthService with JWT manager");
229        Self {
230            jwt_manager: Some(Arc::new(jwt_manager)),
231            password_hasher: PasswordHasher::default(),
232        }
233    }
234
235    /// Set password hasher
236    pub fn with_password_hasher(mut self, hasher: PasswordHasher) -> Self {
237        debug!("Setting password hasher");
238        self.password_hasher = hasher;
239        self
240    }
241
242    /// Hash a password
243    pub fn hash_password(&self, password: &str) -> Result<String> {
244        trace!("Hashing password");
245        self.password_hasher.hash(password)
246    }
247
248    /// Verify a password
249    pub fn verify_password(&self, password: &str, hash: &str) -> Result<bool> {
250        trace!("Verifying password");
251        self.password_hasher.verify(password, hash)
252    }
253
254    /// Get JWT manager
255    pub fn jwt_manager(&self) -> Option<&JwtManager> {
256        self.jwt_manager.as_deref()
257    }
258
259    /// Validate authentication
260    pub fn validate<T: AuthUser>(&self, user: &T) -> Result<()> {
261        debug!("Validating user authentication");
262        if !user.is_active() {
263            debug!("User is inactive");
264            return Err(AuthError::InactiveUser);
265        }
266
267        trace!("User validation successful");
268        Ok(())
269    }
270}
271
272impl Default for AuthService {
273    fn default() -> Self {
274        Self::new()
275    }
276}
277
278/// Prelude for common imports.
279///
280/// ```
281/// use armature_auth::prelude::*;
282/// ```
283pub mod prelude {
284    pub use crate::AuthService;
285    pub use crate::api_key::{ApiKey, ApiKeyManager, ApiKeyStore};
286    pub use crate::error::{AuthError, Result};
287    pub use crate::guard::{AuthGuard, Guard, RoleGuard};
288    pub use crate::oauth2::{OAuth2Provider, OAuth2Token, OAuth2UserInfo};
289    pub use crate::password::{PasswordHasher, PasswordVerifier};
290    pub use crate::strategy::{AuthStrategy, JwtStrategy, LocalStrategy};
291    pub use crate::user::{AuthUser, UserContext};
292
293    // OAuth2 providers
294    pub use crate::providers::{
295        Auth0Provider, AwsCognitoProvider, DiscordProvider, GitHubProvider, GitLabProvider,
296        GoogleProvider, LinkedInProvider, MicrosoftEntraProvider, OktaProvider,
297    };
298
299    #[cfg(feature = "saml")]
300    pub use crate::saml::{SamlConfig, SamlProvider, SamlServiceProvider};
301
302    #[cfg(feature = "two-factor")]
303    pub use crate::two_factor::{BackupCodes, TotpSecret, TwoFactorError};
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_password_hashing() {
312        let service = AuthService::new();
313
314        let password = "test-password-123";
315        let hash = service.hash_password(password).unwrap();
316
317        assert!(service.verify_password(password, &hash).unwrap());
318        assert!(!service.verify_password("wrong-password", &hash).unwrap());
319    }
320}