elif_auth/providers/
jwt.rs

1//! JWT (JSON Web Token) authentication provider
2//!
3//! Provides JWT token generation, validation, and refresh capabilities
4
5#[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/// JWT token structure  
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct JwtToken {
24    /// The raw JWT token string
25    pub token: String,
26
27    /// Token expiration time
28    pub expires_at: DateTime<Utc>,
29
30    /// Optional refresh token
31    pub refresh_token: Option<String>,
32}
33
34/// JWT claims structure
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct JwtClaims {
37    /// Subject (user ID)
38    pub sub: String,
39
40    /// Username/email
41    pub username: String,
42
43    /// User roles
44    pub roles: Vec<String>,
45
46    /// User permissions  
47    pub permissions: Vec<String>,
48
49    /// Issued at timestamp
50    pub iat: i64,
51
52    /// Expiration timestamp
53    pub exp: i64,
54
55    /// Not before timestamp
56    pub nbf: i64,
57
58    /// Issuer
59    pub iss: String,
60
61    /// Audience
62    pub aud: Option<String>,
63
64    /// JWT ID
65    pub jti: String,
66
67    /// Token type (access/refresh)
68    pub token_type: String,
69
70    /// Additional user data
71    pub user_data: HashMap<String, serde_json::Value>,
72}
73
74/// JWT authentication provider
75pub struct JwtProvider<User> {
76    /// JWT configuration
77    config: JwtConfig,
78
79    /// Encoding key for signing tokens
80    #[cfg(feature = "jwt")]
81    encoding_key: EncodingKey,
82
83    /// Decoding key for verifying tokens
84    #[cfg(feature = "jwt")]
85    decoding_key: DecodingKey,
86
87    /// JWT header configuration
88    #[cfg(feature = "jwt")]
89    header: Header,
90
91    /// JWT validation configuration  
92    #[cfg(feature = "jwt")]
93    validation: Validation,
94
95    /// User type marker
96    _marker: std::marker::PhantomData<User>,
97}
98
99impl<User> JwtProvider<User> {
100    /// Create a new JWT provider
101    #[cfg(feature = "jwt")]
102    pub fn new(config: JwtConfig) -> AuthResult<Self> {
103        // Parse algorithm
104        let algorithm = Self::parse_algorithm(&config.algorithm)?;
105
106        // Create keys based on algorithm
107        let (encoding_key, decoding_key) = Self::create_keys(&config.secret, &algorithm)?;
108
109        // Create header
110        let header = Header::new(algorithm);
111
112        // Create validation config
113        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    /// Create a new JWT provider (fallback when jwt feature is disabled)
130    #[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    /// Parse algorithm string to jsonwebtoken Algorithm
138    #[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    /// Create encoding and decoding keys
155    #[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                // HMAC algorithms use shared secret
160                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                // RSA algorithms need key files (for now, return an error with instructions)
166                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    /// Generate JWT token for user
175    #[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    /// Generate token pair (access + refresh)
216    #[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    /// Validate and decode JWT token
229    #[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    /// Validate token and extract claims
236    #[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        // Check if token has expired
241        let now = Utc::now().timestamp();
242        if token_data.claims.exp < now {
243            return Err(AuthError::token_error("Token has expired"));
244        }
245
246        // Check not before
247        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    /// Create user context from JWT claims
255    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    /// Fallback methods when jwt feature is disabled
272    #[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/// Simple user credentials for JWT authentication
297#[derive(Debug, Clone)]
298pub struct JwtCredentials {
299    /// Username or email
300    pub username: String,
301    /// Password
302    pub password: String,
303}
304
305/// Example user implementation (for testing/demonstration)
306#[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        // In a real implementation, this would verify the password hash
349        // For demo purposes, just check if credentials match expected format
350        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// Implement AuthProvider trait for JWT provider
364#[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        // Note: In a real implementation, you would:
378        // 1. Look up the user by credentials
379        // 2. Verify the credentials (password, etc.)
380        // 3. Generate tokens for the authenticated user
381
382        // For now, return an error indicating this needs user lookup implementation
383        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        // Validate the token and extract claims
390        let _claims = self.validate_token_claims(token)?;
391
392        // Note: In a real implementation, you would:
393        // 1. Use the claims to look up the user from storage
394        // 2. Ensure the user still exists and is active
395        // 3. Return the user object
396
397        // For now, return an error indicating this needs user storage integration
398        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        // Validate the refresh token
410        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        // Note: In a real implementation, you would:
417        // 1. Look up the user using the claims
418        // 2. Generate a new access token
419        // 3. Optionally rotate the refresh token
420
421        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        // JWT tokens are stateless, so revocation would require:
433        // 1. A token blacklist/revocation storage
434        // 2. Middleware to check revoked tokens
435        // For now, we'll just log and return success
436        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,     // 15 minutes
454            refresh_token_expiry: 604800, // 7 days
455            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}