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