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}