Skip to main content

ruest/security/
jwt.rs

1use std::sync::Arc;
2
3use chrono::{Duration, Utc};
4use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
5use crate::di::Container;
6
7use super::claims::RuestClaims;
8use super::config::SecurityConfig;
9use super::SecurityError;
10
11/// Service JWT : signature et vérification des tokens (enregistrable en DI).
12#[derive(Clone)]
13pub struct JwtService {
14    encoding: EncodingKey,
15    decoding: DecodingKey,
16    validation: Validation,
17    expires_in_secs: u64,
18    issuer: Option<String>,
19}
20
21impl JwtService {
22    pub fn new(config: &SecurityConfig) -> Result<Self, SecurityError> {
23        if config.jwt_secret.len() < 16 {
24            return Err(SecurityError::Config(
25                "jwt_secret must be at least 16 characters".into(),
26            ));
27        }
28
29        let mut validation = Validation::default();
30        validation.validate_exp = true;
31        if let Some(ref iss) = config.jwt_issuer {
32            validation.set_issuer(&[iss.as_str()]);
33        }
34
35        Ok(Self {
36            encoding: EncodingKey::from_secret(config.jwt_secret.as_bytes()),
37            decoding: DecodingKey::from_secret(config.jwt_secret.as_bytes()),
38            validation,
39            expires_in_secs: config.jwt_expires_in_secs,
40            issuer: config.jwt_issuer.clone(),
41        })
42    }
43
44    /// Enregistre le service comme singleton (pattern `#[service]` / module).
45    pub fn register_provider(container: &Container, config: SecurityConfig) -> Result<(), SecurityError> {
46        let service = Arc::new(Self::new(&config)?);
47        container.register_singleton(service);
48        Ok(())
49    }
50
51    /// Enregistrement pratique pour le développement (`SecurityConfig::dev()`).
52    pub fn register_dev_provider(container: &Container) {
53        let config = SecurityConfig::dev();
54        let service = Arc::new(Self::new(&config).expect("dev JWT config"));
55        container.register_singleton(service);
56    }
57
58    pub fn sign_subject(&self, sub: impl Into<String>, roles: Vec<String>) -> Result<String, SecurityError> {
59        let now = Utc::now();
60        let exp = now + Duration::seconds(self.expires_in_secs as i64);
61        let claims = RuestClaims::new(
62            sub,
63            roles,
64            self.issuer.clone(),
65            exp.timestamp(),
66            now.timestamp(),
67        );
68        self.sign(&claims)
69    }
70
71    pub fn sign(&self, claims: &RuestClaims) -> Result<String, SecurityError> {
72        encode(&Header::default(), claims, &self.encoding).map_err(SecurityError::from)
73    }
74
75    pub fn verify(&self, token: &str) -> Result<RuestClaims, SecurityError> {
76        let data = decode::<RuestClaims>(token, &self.decoding, &self.validation)
77            .map_err(|_| SecurityError::InvalidToken)?;
78        Ok(data.claims)
79    }
80}