Skip to main content

systemprompt_security/session/
generator.rs

1//! Session id and JWT minting for authenticated sessions.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use chrono::{Duration, Utc};
7use jsonwebtoken::{Algorithm, Header, encode};
8use std::collections::BTreeMap;
9use systemprompt_identifiers::{SessionId, SessionToken, UserId};
10use systemprompt_models::auth::{
11    JwtAudience, JwtClaims, Permission, RateLimitTier, TokenType, UserType,
12};
13
14use crate::error::{JwtError, JwtResult};
15use crate::keys::authority;
16
17#[derive(Debug)]
18pub struct SessionParams<'a> {
19    pub user_id: &'a UserId,
20    pub session_id: &'a SessionId,
21    pub email: &'a str,
22    pub duration: Duration,
23    pub user_type: UserType,
24    pub permissions: Vec<Permission>,
25    pub roles: Vec<String>,
26    // JSON: ABAC attribute bag — JWT claim values are policy-defined and schema-less.
27    pub attributes: BTreeMap<String, serde_json::Value>,
28    pub rate_limit_tier: RateLimitTier,
29}
30
31#[derive(Debug)]
32pub struct SessionGenerator {
33    issuer: String,
34}
35
36impl SessionGenerator {
37    pub fn new(issuer: impl Into<String>) -> Self {
38        Self {
39            issuer: issuer.into(),
40        }
41    }
42
43    pub fn generate(&self, params: &SessionParams<'_>) -> JwtResult<SessionToken> {
44        let now = Utc::now();
45        let expiry = now + params.duration;
46
47        let claims = JwtClaims {
48            sub: params.user_id.to_string(),
49            iat: now.timestamp(),
50            exp: expiry.timestamp(),
51            nbf: Some(now.timestamp()),
52            iss: self.issuer.clone(),
53            aud: JwtAudience::standard(),
54            jti: uuid::Uuid::new_v4().to_string(),
55            scope: params.permissions.clone(),
56            username: params.email.to_owned(),
57            email: params.email.to_owned(),
58            user_type: params.user_type,
59            roles: params.roles.clone(),
60            attributes: params.attributes.clone(),
61            client_id: None,
62            token_type: TokenType::Bearer,
63            auth_time: now.timestamp(),
64            session_id: Some(params.session_id.clone()),
65            rate_limit_tier: Some(params.rate_limit_tier),
66            plugin_id: None,
67            act: None,
68        };
69
70        let kid = authority::active_kid().map_err(JwtError::Signing)?;
71        let mut header = Header::new(Algorithm::RS256);
72        header.kid = Some(kid.to_owned());
73        let key = authority::encoding_key().map_err(JwtError::Signing)?;
74        let token = encode(&header, &claims, key).map_err(JwtError::from)?;
75
76        Ok(SessionToken::new(token))
77    }
78}