systemprompt_security/jwt/
decode.rs1use std::collections::BTreeMap;
18use systemprompt_identifiers::{Actor, ClientId, SessionId, UserId};
19use systemprompt_models::auth::{Permission, UserType};
20
21use super::validate::{ValidationPolicy, decode_rs256_claims};
22use crate::error::{AuthError, AuthResult};
23
24#[derive(Debug, Clone)]
25pub struct JwtUserContext {
26 pub user_id: UserId,
27 pub session_id: SessionId,
28 pub role: Permission,
29 pub user_type: UserType,
30 pub client_id: Option<ClientId>,
31 pub act_chain: Vec<Actor>,
32 pub attributes: BTreeMap<String, serde_json::Value>,
33 pub jti: String,
34 pub exp: i64,
35}
36
37pub fn extract_user_context(token: &str) -> AuthResult<JwtUserContext> {
38 let claims = decode_rs256_claims(token, &ValidationPolicy::session_context())?;
39
40 let session_id = claims.session_id.ok_or(AuthError::MissingSessionId)?;
41 let role = *claims.scope.first().ok_or(AuthError::MissingScope)?;
42 let derived_type = UserType::from_permissions(&claims.scope);
43 if derived_type != claims.user_type {
44 return Err(AuthError::UserTypeMismatch {
45 claimed: claims.user_type,
46 derived: derived_type,
47 });
48 }
49 let act_chain = claims
50 .act
51 .as_ref()
52 .map(systemprompt_models::auth::ActClaim::flatten_to_chain)
53 .unwrap_or_default();
54
55 Ok(JwtUserContext {
56 user_id: UserId::new(claims.sub),
57 session_id,
58 role,
59 user_type: derived_type,
60 client_id: claims.client_id,
61 act_chain,
62 attributes: claims.attributes,
63 jti: claims.jti,
64 exp: claims.exp,
65 })
66}