Skip to main content

toolcraft_jwt/
verify.rs

1use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};
2use serde::Deserialize;
3
4use crate::{AccessTokenVerifier, Claims, Result, error::Error};
5
6#[derive(Debug, Deserialize)]
7pub struct VerifyJwtCfg {
8    pub public_key_pem: String,
9    pub issuer: String,
10    pub audience: String,
11}
12
13/// Minimal verifier for asymmetric Ed25519 JWT.
14///
15/// Uses public key for signature verification with fixed issuer/audience checks.
16pub struct VerifyJwt {
17    decoding_key: DecodingKey,
18    validation: Validation,
19}
20
21impl VerifyJwt {
22    /// Create an Ed25519 verifier with public key PEM and fixed issuer/audience.
23    pub fn new(cfg: VerifyJwtCfg) -> Result<Self> {
24        let VerifyJwtCfg {
25            public_key_pem,
26            issuer,
27            audience,
28        } = cfg;
29
30        let decoding_key = DecodingKey::from_ed_pem(public_key_pem.as_bytes())?;
31        let mut validation = Validation::new(Algorithm::EdDSA);
32        if issuer.is_empty() {
33            return Err(Error::ErrorMessage("issuer must not be empty".into()));
34        }
35        if audience.is_empty() {
36            return Err(Error::ErrorMessage("audience must not be empty".into()));
37        }
38        validation.set_issuer(&[issuer]);
39        validation.set_audience(&[audience]);
40        validation.validate_aud = true;
41        Ok(Self {
42            decoding_key,
43            validation,
44        })
45    }
46
47    /// Validate token signature and standard claims based on default validation.
48    pub fn validate_token(&self, token: &str) -> Result<Claims> {
49        decode::<Claims>(token, &self.decoding_key, &self.validation)
50            .map(|data| data.claims)
51            .map_err(|e| Error::AuthError(e.to_string().into()))
52    }
53}
54
55impl AccessTokenVerifier for VerifyJwt {
56    fn validate_access_token(&self, token: &str) -> Result<Claims> {
57        self.validate_token(token)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use jsonwebtoken::{EncodingKey, Header, encode};
64
65    use super::*;
66
67    const PRIVATE_KEY_PEM: &str = "-----BEGIN PRIVATE KEY-----
68MC4CAQAwBQYDK2VwBCIEIGrD/e7uKYqSY4twDEsRfMMuLSrODf14dpTiTK6K1YI0
69-----END PRIVATE KEY-----";
70    const PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----
71MCowBQYDK2VwAyEA2+Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8=
72-----END PUBLIC KEY-----";
73
74    #[test]
75    fn test_verify_with_public_key_only() {
76        let claims = Claims::new(
77            "test_issuer".to_string(),
78            "test_audience".to_string(),
79            "test_sub".to_string(),
80            (chrono::Utc::now().timestamp() as usize) + 3600,
81            chrono::Utc::now().timestamp() as usize,
82        );
83        let token = encode(
84            &Header::new(Algorithm::EdDSA),
85            &claims,
86            &EncodingKey::from_ed_pem(PRIVATE_KEY_PEM.as_bytes()).unwrap(),
87        )
88        .unwrap();
89
90        let verifier = VerifyJwt::new(VerifyJwtCfg {
91            public_key_pem: PUBLIC_KEY_PEM.to_string(),
92            issuer: "test_issuer".to_string(),
93            audience: "test_audience".to_string(),
94        })
95        .unwrap();
96        let decoded = verifier.validate_token(&token).unwrap();
97        assert_eq!(decoded.sub, "test_sub");
98    }
99}