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