armature_jwt/
lib.rs

1//! JWT authentication and authorization for Armature
2//!
3//! This crate provides JSON Web Token (JWT) support for the Armature framework,
4//! including token generation, verification, and management.
5//!
6//! # Examples
7//!
8//! ## Basic Usage
9//!
10//! ```
11//! use armature_jwt::{JwtConfig, JwtManager, StandardClaims};
12//!
13//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
14//! // Create JWT configuration
15//! let config = JwtConfig::new("your-secret-key".to_string());
16//! let manager = JwtManager::new(config)?;
17//!
18//! // Create claims
19//! let claims = StandardClaims::new()
20//!     .with_subject("user123".to_string())
21//!     .with_expiration(3600); // 1 hour
22//!
23//! // Sign a token
24//! let token = manager.sign(&claims)?;
25//!
26//! // Verify and decode
27//! let decoded: StandardClaims = manager.verify(&token)?;
28//! assert_eq!(decoded.sub, Some("user123".to_string()));
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! ## Custom Claims
34//!
35//! ```
36//! use armature_jwt::{Claims, JwtConfig, JwtManager};
37//! use serde::{Deserialize, Serialize};
38//!
39//! #[derive(Debug, Serialize, Deserialize)]
40//! struct UserClaims {
41//!     email: String,
42//!     role: String,
43//! }
44//!
45//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
46//! let config = JwtConfig::new("secret".to_string());
47//! let manager = JwtManager::new(config)?;
48//!
49//! let claims = Claims::new(UserClaims {
50//!     email: "user@example.com".to_string(),
51//!     role: "admin".to_string(),
52//! })
53//! .with_subject("123".to_string())
54//! .with_expiration(7200);
55//!
56//! let token = manager.sign(&claims)?;
57//! let decoded: Claims<UserClaims> = manager.verify(&token)?;
58//! assert_eq!(decoded.custom.email, "user@example.com");
59//! # Ok(())
60//! # }
61//! ```
62
63pub mod claims;
64pub mod config;
65pub mod error;
66pub mod service;
67pub mod token;
68
69pub use claims::{Claims, StandardClaims};
70pub use config::JwtConfig;
71pub use error::{JwtError, Result};
72pub use service::JwtService;
73pub use token::{Token, TokenPair};
74
75// Re-export jsonwebtoken types
76pub use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
77
78use armature_log::{debug, info, trace};
79
80/// JWT service for token management
81#[derive(Clone)]
82pub struct JwtManager {
83    service: JwtService,
84}
85
86impl JwtManager {
87    /// Create a new JWT manager
88    ///
89    /// # Example
90    ///
91    /// ```
92    /// use armature_jwt::{JwtConfig, JwtManager};
93    ///
94    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
95    /// let config = JwtConfig::new("my-secret".to_string());
96    /// let manager = JwtManager::new(config)?;
97    /// # Ok(())
98    /// # }
99    /// ```
100    pub fn new(config: JwtConfig) -> Result<Self> {
101        info!("Initializing JWT manager");
102        debug!("JWT algorithm: {:?}", config.algorithm);
103        let service = JwtService::new(config)?;
104        Ok(Self { service })
105    }
106
107    /// Sign a token with claims
108    ///
109    /// # Example
110    ///
111    /// ```
112    /// use armature_jwt::{JwtConfig, JwtManager, StandardClaims};
113    ///
114    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
115    /// let manager = JwtManager::new(JwtConfig::new("secret".to_string()))?;
116    /// let claims = StandardClaims::new().with_subject("user".to_string());
117    /// let token = manager.sign(&claims)?;
118    /// assert!(!token.is_empty());
119    /// # Ok(())
120    /// # }
121    /// ```
122    pub fn sign<T: serde::Serialize>(&self, claims: &T) -> Result<String> {
123        trace!("Signing JWT token");
124        self.service.sign(claims)
125    }
126
127    /// Verify and decode a token
128    ///
129    /// # Example
130    ///
131    /// ```
132    /// use armature_jwt::{JwtConfig, JwtManager, StandardClaims};
133    ///
134    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
135    /// let manager = JwtManager::new(JwtConfig::new("secret".to_string()))?;
136    /// let claims = StandardClaims::new()
137    ///     .with_subject("user".to_string())
138    ///     .with_expiration(3600); // Add expiration
139    /// let token = manager.sign(&claims)?;
140    /// let decoded: StandardClaims = manager.verify(&token)?;
141    /// assert_eq!(decoded.sub, Some("user".to_string()));
142    /// # Ok(())
143    /// # }
144    /// ```
145    pub fn verify<T: serde::de::DeserializeOwned>(&self, token: &str) -> Result<T> {
146        trace!("Verifying JWT token");
147        match self.service.verify(token) {
148            Ok(claims) => {
149                trace!("JWT token verified successfully");
150                Ok(claims)
151            }
152            Err(e) => {
153                debug!("JWT verification failed: {}", e);
154                Err(e)
155            }
156        }
157    }
158
159    /// Generate a token pair (access + refresh)
160    pub fn generate_token_pair<T: serde::Serialize + Clone>(
161        &self,
162        claims: &T,
163    ) -> Result<TokenPair> {
164        debug!("Generating JWT token pair");
165        self.service.generate_token_pair(claims)
166    }
167
168    /// Refresh an access token using a refresh token
169    #[allow(dead_code)]
170    pub fn refresh_token<T: serde::de::DeserializeOwned + serde::Serialize + Clone>(
171        &self,
172        refresh_token: &str,
173    ) -> Result<TokenPair> {
174        self.service.refresh_token::<T>(refresh_token)
175    }
176
177    /// Decode a token without verification (useful for inspecting expired tokens)
178    pub fn decode_unverified<T: serde::de::DeserializeOwned>(&self, token: &str) -> Result<T> {
179        self.service.decode_unverified(token)
180    }
181
182    /// Get the configuration
183    pub fn config(&self) -> &JwtConfig {
184        self.service.config()
185    }
186}
187
188impl Default for JwtManager {
189    fn default() -> Self {
190        Self::new(JwtConfig::default()).expect("Failed to create default JwtManager")
191    }
192}
193
194/// Prelude for common imports.
195///
196/// ```
197/// use armature_jwt::prelude::*;
198/// ```
199pub mod prelude {
200    pub use crate::JwtManager;
201    pub use crate::claims::{Claims, StandardClaims};
202    pub use crate::config::JwtConfig;
203    pub use crate::error::{JwtError, Result};
204    pub use crate::service::JwtService;
205    pub use crate::token::{Token, TokenPair};
206    pub use jsonwebtoken::Algorithm;
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use serde::{Deserialize, Serialize};
213
214    #[derive(Debug, Serialize, Deserialize, PartialEq)]
215    struct TestClaims {
216        sub: String,
217        name: String,
218        exp: i64,
219    }
220
221    #[test]
222    fn test_sign_and_verify() {
223        let config = JwtConfig::new("test-secret".to_string());
224        let manager = JwtManager::new(config).unwrap();
225
226        let exp = (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp();
227
228        let claims = TestClaims {
229            sub: "123".to_string(),
230            name: "John Doe".to_string(),
231            exp,
232        };
233
234        let token = manager.sign(&claims).unwrap();
235        let decoded: TestClaims = manager.verify(&token).unwrap();
236
237        assert_eq!(decoded.sub, claims.sub);
238        assert_eq!(decoded.name, claims.name);
239    }
240
241    #[test]
242    fn test_invalid_token() {
243        let config = JwtConfig::new("test-secret".to_string());
244        let manager = JwtManager::new(config).unwrap();
245
246        let result: Result<TestClaims> = manager.verify("invalid.token.here");
247        assert!(result.is_err());
248    }
249}