systemprompt_security/auth/
validation.rs1use axum::http::HeaderMap;
7use systemprompt_identifiers::{Actor, ContextId, SessionId, UserId};
8use systemprompt_models::auth::{JwtAudience, MAX_ACT_CHAIN_DEPTH, Permission, UserType};
9use systemprompt_models::execution::context::RequestContext;
10
11use crate::error::{AuthError, AuthResult};
12use crate::extraction::{HeaderExtractor, TokenExtractor};
13use crate::jwt::{ValidationPolicy, decode_rs256_claims};
14use crate::session::ValidatedSessionClaims;
15
16#[derive(Debug)]
17pub struct AuthValidationService {
18 issuer: String,
19 audiences: Vec<JwtAudience>,
20}
21
22impl AuthValidationService {
23 #[must_use]
24 pub const fn new(issuer: String, audiences: Vec<JwtAudience>) -> Self {
25 Self { issuer, audiences }
26 }
27
28 pub fn validate_request(&self, headers: &HeaderMap) -> AuthResult<RequestContext> {
29 let token = TokenExtractor::extract_from_authorization(headers)
30 .map_err(|_e| AuthError::MissingAuthorization)?;
31 let claims = self.validate_token(&token)?;
32 Ok(Self::create_context_from_claims(&claims, &token, headers))
33 }
34
35 fn validate_token(&self, token: &str) -> AuthResult<ValidatedSessionClaims> {
36 let policy = ValidationPolicy::issuer_scoped(&self.issuer, &self.audiences);
37 let claims = decode_rs256_claims(token, &policy)?;
38
39 if let Some(ref act) = claims.act {
40 let depth = act.depth();
41 if depth > MAX_ACT_CHAIN_DEPTH {
42 return Err(AuthError::ActChainTooDeep {
43 depth,
44 max: MAX_ACT_CHAIN_DEPTH,
45 });
46 }
47 }
48
49 let user_type = if claims.scope.contains(&Permission::Admin) {
50 UserType::Admin
51 } else {
52 claims.user_type
53 };
54
55 Ok(ValidatedSessionClaims {
56 user_id: UserId::new(claims.sub),
57 session_id: claims
58 .session_id
59 .map(SessionId::new)
60 .ok_or(AuthError::MissingSessionId)?,
61 user_type,
62 jti: claims.jti,
63 exp: claims.exp,
64 })
65 }
66
67 fn create_context_from_claims(
68 claims: &ValidatedSessionClaims,
69 token: &str,
70 headers: &HeaderMap,
71 ) -> RequestContext {
72 let session_id = claims.session_id.clone();
73 let user_id = claims.user_id.clone();
74
75 RequestContext::new(
76 session_id,
77 HeaderExtractor::extract_trace_id(headers),
78 HeaderExtractor::extract_context_id(headers).unwrap_or_else(ContextId::generate),
79 HeaderExtractor::extract_agent_name(headers),
80 )
81 .with_actor(Actor::user(user_id))
82 .with_auth_token(token)
83 .with_user_type(claims.user_type)
84 .with_jti(claims.jti.clone())
85 .with_token_exp(claims.exp)
86 }
87}