systemprompt_security/jwt/
validate.rs1use jsonwebtoken::{Algorithm, Validation, decode, decode_header};
21use systemprompt_models::auth::{JwtAudience, JwtClaims};
22
23use crate::error::{AuthError, AuthResult};
24use crate::keys::authority;
25
26pub const JWT_LEEWAY_SECONDS: u64 = 30;
27
28#[derive(Debug, Clone)]
29pub struct ValidationPolicy<'a> {
30 validate_exp: bool,
31 validate_nbf: bool,
32 leeway_seconds: u64,
33 issuer: Option<&'a str>,
34 audiences: &'a [JwtAudience],
35}
36
37impl<'a> ValidationPolicy<'a> {
38 #[must_use]
39 pub const fn session_context() -> Self {
40 Self {
41 validate_exp: true,
42 validate_nbf: true,
43 leeway_seconds: JWT_LEEWAY_SECONDS,
44 issuer: None,
45 audiences: JwtAudience::FIRST_PARTY,
46 }
47 }
48
49 #[must_use]
50 pub const fn issuer_scoped(issuer: &'a str, audiences: &'a [JwtAudience]) -> Self {
51 Self {
52 validate_exp: true,
53 validate_nbf: true,
54 leeway_seconds: JWT_LEEWAY_SECONDS,
55 issuer: Some(issuer),
56 audiences,
57 }
58 }
59}
60
61pub fn decode_rs256_claims(token: &str, policy: &ValidationPolicy<'_>) -> AuthResult<JwtClaims> {
62 if policy.audiences.is_empty() {
63 return Err(AuthError::EmptyAudiencePolicy);
64 }
65
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 let audience_strs: Vec<&str> = policy.audiences.iter().map(JwtAudience::as_str).collect();
85 validation.set_audience(&audience_strs);
86
87 decode::<JwtClaims>(token, key, &validation)
88 .map(|data| data.claims)
89 .map_err(AuthError::InvalidToken)
90}