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}
35
36pub fn extract_user_context(token: &str, issuer: &str) -> AuthResult<JwtUserContext> {
37 let claims = decode_session_claims(token, &ValidationPolicy::session_context(issuer))?;
38
39 let session_id = claims.session_id.ok_or(AuthError::MissingSessionId)?;
40 let role = *claims.scope.first().ok_or(AuthError::MissingScope)?;
41 let act_chain = claims
42 .act
43 .as_ref()
44 .map(systemprompt_models::auth::ActClaim::flatten_to_chain)
45 .unwrap_or_default();
46
47 Ok(JwtUserContext {
48 user_id: UserId::try_new(claims.sub).map_err(AuthError::InvalidSubject)?,
49 session_id,
50 role,
51 user_type: claims.user_type,
52 client_id: claims.client_id,
53 act_chain,
54 attributes: claims.attributes,
55 jti: (!claims.jti.is_empty()).then(|| AccessTokenId::new(claims.jti)),
56 exp: claims.exp,
57 })
58}