1#[cfg(feature = "jwt")]
6use jsonwebtoken::{
7 decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation,
8};
9
10use async_trait::async_trait;
11use chrono::{DateTime, Duration, Utc};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15use crate::{
16 config::JwtConfig,
17 traits::{AuthProvider, Authenticatable, AuthenticationResult, UserContext},
18 AuthError, AuthResult,
19};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct JwtToken {
24 pub token: String,
26
27 pub expires_at: DateTime<Utc>,
29
30 pub refresh_token: Option<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct JwtClaims {
37 pub sub: String,
39
40 pub username: String,
42
43 pub roles: Vec<String>,
45
46 pub permissions: Vec<String>,
48
49 pub iat: i64,
51
52 pub exp: i64,
54
55 pub nbf: i64,
57
58 pub iss: String,
60
61 pub aud: Option<String>,
63
64 pub jti: String,
66
67 pub token_type: String,
69
70 pub user_data: HashMap<String, serde_json::Value>,
72}
73
74pub struct JwtProvider<User> {
76 config: JwtConfig,
78
79 #[cfg(feature = "jwt")]
81 encoding_key: EncodingKey,
82
83 #[cfg(feature = "jwt")]
85 decoding_key: DecodingKey,
86
87 #[cfg(feature = "jwt")]
89 header: Header,
90
91 #[cfg(feature = "jwt")]
93 validation: Validation,
94
95 _marker: std::marker::PhantomData<User>,
97}
98
99impl<User> JwtProvider<User> {
100 #[cfg(feature = "jwt")]
102 pub fn new(config: JwtConfig) -> AuthResult<Self> {
103 let algorithm = Self::parse_algorithm(&config.algorithm)?;
105
106 let (encoding_key, decoding_key) = Self::create_keys(&config.secret, &algorithm)?;
108
109 let header = Header::new(algorithm);
111
112 let mut validation = Validation::new(algorithm);
114 validation.set_issuer(&[&config.issuer]);
115 if let Some(ref audience) = config.audience {
116 validation.set_audience(&[audience]);
117 }
118
119 Ok(Self {
120 config,
121 encoding_key,
122 decoding_key,
123 header,
124 validation,
125 _marker: std::marker::PhantomData,
126 })
127 }
128
129 #[cfg(not(feature = "jwt"))]
131 pub fn new(_config: JwtConfig) -> AuthResult<Self> {
132 Err(AuthError::generic_error(
133 "JWT support not enabled. Enable the 'jwt' feature",
134 ))
135 }
136
137 #[cfg(feature = "jwt")]
139 fn parse_algorithm(algorithm: &str) -> AuthResult<Algorithm> {
140 match algorithm {
141 "HS256" => Ok(Algorithm::HS256),
142 "HS384" => Ok(Algorithm::HS384),
143 "HS512" => Ok(Algorithm::HS512),
144 "RS256" => Ok(Algorithm::RS256),
145 "RS384" => Ok(Algorithm::RS384),
146 "RS512" => Ok(Algorithm::RS512),
147 _ => Err(AuthError::configuration_error(format!(
148 "Unsupported JWT algorithm: {}",
149 algorithm
150 ))),
151 }
152 }
153
154 #[cfg(feature = "jwt")]
156 fn create_keys(secret: &str, algorithm: &Algorithm) -> AuthResult<(EncodingKey, DecodingKey)> {
157 match algorithm {
158 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
159 let encoding_key = EncodingKey::from_secret(secret.as_bytes());
161 let decoding_key = DecodingKey::from_secret(secret.as_bytes());
162 Ok((encoding_key, decoding_key))
163 }
164 Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => {
165 Err(AuthError::configuration_error(
167 "RSA algorithms require private/public key files. Use HS256/HS384/HS512 for shared secret authentication"
168 ))
169 }
170 _ => Err(AuthError::configuration_error("Unsupported algorithm")),
171 }
172 }
173
174 #[cfg(feature = "jwt")]
176 pub fn generate_token(&self, user: &User, token_type: &str) -> AuthResult<JwtToken>
177 where
178 User: Authenticatable,
179 User::Id: std::fmt::Display,
180 {
181 let now = Utc::now();
182 let expiry_duration = match token_type {
183 "access" => Duration::seconds(self.config.access_token_expiry as i64),
184 "refresh" => Duration::seconds(self.config.refresh_token_expiry as i64),
185 _ => return Err(AuthError::token_error("Invalid token type")),
186 };
187
188 let expires_at = now + expiry_duration;
189
190 let claims = JwtClaims {
191 sub: user.id().to_string(),
192 username: user.username().to_string(),
193 roles: user.roles(),
194 permissions: user.permissions(),
195 iat: now.timestamp(),
196 exp: expires_at.timestamp(),
197 nbf: now.timestamp(),
198 iss: self.config.issuer.clone(),
199 aud: self.config.audience.clone(),
200 jti: uuid::Uuid::new_v4().to_string(),
201 token_type: token_type.to_string(),
202 user_data: user.additional_data(),
203 };
204
205 let token = encode(&self.header, &claims, &self.encoding_key)
206 .map_err(|e| AuthError::token_error(format!("Failed to generate JWT token: {}", e)))?;
207
208 Ok(JwtToken {
209 token,
210 expires_at,
211 refresh_token: None,
212 })
213 }
214
215 #[cfg(feature = "jwt")]
217 pub fn generate_token_pair(&self, user: &User) -> AuthResult<(JwtToken, JwtToken)>
218 where
219 User: Authenticatable,
220 User::Id: std::fmt::Display,
221 {
222 let access_token = self.generate_token(user, "access")?;
223 let refresh_token = self.generate_token(user, "refresh")?;
224
225 Ok((access_token, refresh_token))
226 }
227
228 #[cfg(feature = "jwt")]
230 pub fn decode_token(&self, token: &str) -> AuthResult<TokenData<JwtClaims>> {
231 decode::<JwtClaims>(token, &self.decoding_key, &self.validation)
232 .map_err(|e| AuthError::token_error(format!("Invalid JWT token: {}", e)))
233 }
234
235 #[cfg(feature = "jwt")]
237 pub fn validate_token_claims(&self, token: &JwtToken) -> AuthResult<JwtClaims> {
238 let token_data = self.decode_token(&token.token)?;
239
240 let now = Utc::now().timestamp();
242 if token_data.claims.exp < now {
243 return Err(AuthError::token_error("Token has expired"));
244 }
245
246 if token_data.claims.nbf > now {
248 return Err(AuthError::token_error("Token not yet valid"));
249 }
250
251 Ok(token_data.claims)
252 }
253
254 pub fn claims_to_user_context(&self, claims: &JwtClaims) -> UserContext {
256 let mut context = UserContext::new(
257 claims.sub.clone(),
258 claims.username.clone(),
259 "jwt".to_string(),
260 );
261
262 context.roles = claims.roles.clone();
263 context.permissions = claims.permissions.clone();
264 context.authenticated_at = DateTime::from_timestamp(claims.iat, 0).unwrap_or(Utc::now());
265 context.expires_at = Some(DateTime::from_timestamp(claims.exp, 0).unwrap_or(Utc::now()));
266 context.additional_data = claims.user_data.clone();
267
268 context
269 }
270
271 #[cfg(not(feature = "jwt"))]
273 pub fn generate_token(&self, _user: &User, _token_type: &str) -> AuthResult<JwtToken>
274 where
275 User: Authenticatable,
276 User::Id: std::fmt::Display,
277 {
278 Err(AuthError::generic_error("JWT support not enabled"))
279 }
280
281 #[cfg(not(feature = "jwt"))]
282 pub fn generate_token_pair(&self, _user: &User) -> AuthResult<(JwtToken, JwtToken)>
283 where
284 User: Authenticatable,
285 User::Id: std::fmt::Display,
286 {
287 Err(AuthError::generic_error("JWT support not enabled"))
288 }
289
290 #[cfg(not(feature = "jwt"))]
291 pub fn validate_token_claims(&self, _token: &JwtToken) -> AuthResult<JwtClaims> {
292 Err(AuthError::generic_error("JWT support not enabled"))
293 }
294}
295
296#[derive(Debug, Clone)]
298pub struct JwtCredentials {
299 pub username: String,
301 pub password: String,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct JwtUser {
308 pub id: String,
309 pub username: String,
310 pub email: String,
311 pub password_hash: String,
312 pub roles: Vec<String>,
313 pub permissions: Vec<String>,
314 pub is_active: bool,
315 pub is_locked: bool,
316}
317
318#[async_trait]
319impl Authenticatable for JwtUser {
320 type Id = String;
321 type Credentials = JwtCredentials;
322
323 fn id(&self) -> &Self::Id {
324 &self.id
325 }
326
327 fn username(&self) -> &str {
328 &self.username
329 }
330
331 fn is_active(&self) -> bool {
332 self.is_active
333 }
334
335 fn is_locked(&self) -> bool {
336 self.is_locked
337 }
338
339 fn roles(&self) -> Vec<String> {
340 self.roles.clone()
341 }
342
343 fn permissions(&self) -> Vec<String> {
344 self.permissions.clone()
345 }
346
347 async fn verify_credentials(&self, credentials: &Self::Credentials) -> AuthResult<bool> {
348 Ok(credentials.username == self.username && !credentials.password.is_empty())
351 }
352
353 fn additional_data(&self) -> HashMap<String, serde_json::Value> {
354 let mut data = HashMap::new();
355 data.insert(
356 "email".to_string(),
357 serde_json::Value::String(self.email.clone()),
358 );
359 data
360 }
361}
362
363#[async_trait]
365impl<User> AuthProvider<User> for JwtProvider<User>
366where
367 User: Authenticatable + Send + Sync + 'static,
368 User::Credentials: Send + Sync,
369{
370 type Token = JwtToken;
371 type Credentials = User::Credentials;
372
373 async fn authenticate(
374 &self,
375 _credentials: &Self::Credentials,
376 ) -> AuthResult<AuthenticationResult<User, Self::Token>> {
377 Err(AuthError::authentication_failed(
384 "JWT authentication requires user lookup implementation. This provider handles token generation/validation but needs integration with user storage."
385 ))
386 }
387
388 async fn validate_token(&self, token: &Self::Token) -> AuthResult<User> {
389 let _claims = self.validate_token_claims(token)?;
391
392 Err(AuthError::token_error(
399 "Token validation requires user storage integration. Claims are valid but user lookup is not implemented."
400 ))
401 }
402
403 #[cfg(feature = "jwt")]
404 async fn refresh_token(&self, token: &Self::Token) -> AuthResult<Self::Token> {
405 if !self.config.allow_refresh {
406 return Err(AuthError::token_error("Token refresh not allowed"));
407 }
408
409 let claims = self.validate_token_claims(token)?;
411
412 if claims.token_type != "refresh" {
413 return Err(AuthError::token_error("Invalid token type for refresh"));
414 }
415
416 Err(AuthError::token_error(
422 "Token refresh requires user storage integration",
423 ))
424 }
425
426 #[cfg(not(feature = "jwt"))]
427 async fn refresh_token(&self, _token: &Self::Token) -> AuthResult<Self::Token> {
428 Err(AuthError::generic_error("JWT support not enabled"))
429 }
430
431 async fn revoke_token(&self, _token: &Self::Token) -> AuthResult<()> {
432 tracing::info!("Token revocation requested (not implemented - requires blacklist)");
437 Ok(())
438 }
439
440 fn provider_name(&self) -> &str {
441 "jwt"
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 fn create_test_config() -> JwtConfig {
450 JwtConfig {
451 secret: "test-secret-key-that-is-long-enough-for-validation".to_string(),
452 algorithm: "HS256".to_string(),
453 access_token_expiry: 900, refresh_token_expiry: 604800, issuer: "test".to_string(),
456 audience: Some("test-app".to_string()),
457 allow_refresh: true,
458 }
459 }
460
461 fn create_test_user() -> JwtUser {
462 JwtUser {
463 id: "123".to_string(),
464 username: "testuser".to_string(),
465 email: "test@example.com".to_string(),
466 password_hash: "hashed_password".to_string(),
467 roles: vec!["user".to_string()],
468 permissions: vec!["read".to_string()],
469 is_active: true,
470 is_locked: false,
471 }
472 }
473
474 #[cfg(feature = "jwt")]
475 #[tokio::test]
476 async fn test_jwt_provider_creation() {
477 let config = create_test_config();
478 let provider = JwtProvider::<JwtUser>::new(config);
479 assert!(provider.is_ok());
480 }
481
482 #[cfg(feature = "jwt")]
483 #[tokio::test]
484 async fn test_token_generation() {
485 let config = create_test_config();
486 let provider = JwtProvider::<JwtUser>::new(config).unwrap();
487 let user = create_test_user();
488
489 let token = provider.generate_token(&user, "access");
490 assert!(token.is_ok());
491
492 let token = token.unwrap();
493 assert!(!token.token.is_empty());
494 assert!(token.expires_at > Utc::now());
495 }
496
497 #[cfg(feature = "jwt")]
498 #[tokio::test]
499 async fn test_token_validation() {
500 let config = create_test_config();
501 let provider = JwtProvider::<JwtUser>::new(config).unwrap();
502 let user = create_test_user();
503
504 let token = provider.generate_token(&user, "access").unwrap();
505 let claims = provider.validate_token_claims(&token);
506 assert!(claims.is_ok());
507
508 let claims = claims.unwrap();
509 assert_eq!(claims.sub, "123");
510 assert_eq!(claims.username, "testuser");
511 assert_eq!(claims.roles, vec!["user"]);
512 assert_eq!(claims.token_type, "access");
513 }
514
515 #[cfg(feature = "jwt")]
516 #[tokio::test]
517 async fn test_token_pair_generation() {
518 let config = create_test_config();
519 let provider = JwtProvider::<JwtUser>::new(config).unwrap();
520 let user = create_test_user();
521
522 let result = provider.generate_token_pair(&user);
523 assert!(result.is_ok());
524
525 let (access_token, refresh_token) = result.unwrap();
526 assert!(!access_token.token.is_empty());
527 assert!(!refresh_token.token.is_empty());
528 assert_ne!(access_token.token, refresh_token.token);
529 }
530
531 #[cfg(feature = "jwt")]
532 #[tokio::test]
533 async fn test_claims_to_user_context() {
534 let config = create_test_config();
535 let provider = JwtProvider::<JwtUser>::new(config).unwrap();
536 let user = create_test_user();
537
538 let token = provider.generate_token(&user, "access").unwrap();
539 let claims = provider.validate_token_claims(&token).unwrap();
540 let context = provider.claims_to_user_context(&claims);
541
542 assert_eq!(context.user_id, "123");
543 assert_eq!(context.username, "testuser");
544 assert_eq!(context.auth_provider, "jwt");
545 assert_eq!(context.roles, vec!["user"]);
546 assert!(context.has_role("user"));
547 assert!(!context.has_role("admin"));
548 }
549
550 #[tokio::test]
551 async fn test_jwt_user_trait_implementation() {
552 let user = create_test_user();
553 let credentials = JwtCredentials {
554 username: "testuser".to_string(),
555 password: "password123".to_string(),
556 };
557
558 assert_eq!(user.id(), "123");
559 assert_eq!(user.username(), "testuser");
560 assert!(user.is_active());
561 assert!(!user.is_locked());
562 assert_eq!(user.roles(), vec!["user"]);
563 assert_eq!(user.permissions(), vec!["read"]);
564
565 let verification_result = user.verify_credentials(&credentials).await;
566 assert!(verification_result.is_ok());
567 assert!(verification_result.unwrap());
568 }
569
570 #[tokio::test]
571 async fn test_invalid_algorithm() {
572 let mut config = create_test_config();
573 config.algorithm = "INVALID".to_string();
574
575 #[cfg(feature = "jwt")]
576 {
577 let provider = JwtProvider::<JwtUser>::new(config);
578 assert!(provider.is_err());
579 }
580 }
581
582 #[tokio::test]
583 async fn test_provider_name() {
584 let config = create_test_config();
585
586 #[cfg(feature = "jwt")]
587 {
588 let provider = JwtProvider::<JwtUser>::new(config).unwrap();
589 assert_eq!(provider.provider_name(), "jwt");
590 }
591 }
592}