systemprompt_security/jwt/
validate.rs1use jsonwebtoken::{Algorithm, Validation, decode, decode_header};
14use systemprompt_models::auth::{JwtAudience, JwtClaims};
15
16use crate::error::{AuthError, AuthResult};
17use crate::keys::authority;
18
19pub const JWT_LEEWAY_SECONDS: u64 = 30;
23
24#[derive(Debug, Clone, Default)]
27pub struct ValidationPolicy<'a> {
28 pub validate_exp: bool,
29 pub validate_nbf: bool,
30 pub leeway_seconds: u64,
31 pub issuer: Option<&'a str>,
32 pub audiences: &'a [JwtAudience],
33}
34
35impl<'a> ValidationPolicy<'a> {
36 #[must_use]
41 pub const fn session_context() -> Self {
42 Self {
43 validate_exp: true,
44 validate_nbf: true,
45 leeway_seconds: JWT_LEEWAY_SECONDS,
46 issuer: None,
47 audiences: &[],
48 }
49 }
50
51 #[must_use]
54 pub const fn issuer_scoped(issuer: &'a str, audiences: &'a [JwtAudience]) -> Self {
55 Self {
56 validate_exp: true,
57 validate_nbf: true,
58 leeway_seconds: JWT_LEEWAY_SECONDS,
59 issuer: Some(issuer),
60 audiences,
61 }
62 }
63}
64
65pub fn decode_rs256_claims(token: &str, policy: &ValidationPolicy<'_>) -> AuthResult<JwtClaims> {
66 let header = decode_header(token).map_err(AuthError::InvalidToken)?;
67 if header.alg != Algorithm::RS256 {
68 return Err(AuthError::UnsupportedAlgorithm {
69 got: format!("{:?}", header.alg),
70 });
71 }
72 let kid = header.kid.as_deref().ok_or(AuthError::MissingKid)?;
73 let key = authority::decoding_key_for_kid(kid)
74 .map_err(|e| AuthError::KeyLookup(e.to_string()))?
75 .ok_or_else(|| AuthError::UnknownKid(kid.to_owned()))?;
76
77 let mut validation = Validation::new(Algorithm::RS256);
78 validation.validate_exp = policy.validate_exp;
79 validation.validate_nbf = policy.validate_nbf;
80 validation.leeway = policy.leeway_seconds;
81 if let Some(issuer) = policy.issuer {
82 validation.set_issuer(&[issuer]);
83 }
84 if policy.audiences.is_empty() {
85 validation.validate_aud = false;
86 } else {
87 let audience_strs: Vec<&str> = policy.audiences.iter().map(JwtAudience::as_str).collect();
88 validation.set_audience(&audience_strs);
89 }
90
91 decode::<JwtClaims>(token, key, &validation)
92 .map(|data| data.claims)
93 .map_err(AuthError::InvalidToken)
94}