systemprompt_models/auth/
claims.rs1use serde::{Deserialize, Serialize};
16use std::collections::BTreeMap;
17use std::str::FromStr;
18use systemprompt_identifiers::{ClientId, PluginId, SessionId, UserId};
19
20use super::{
21 JwtAudience, Permission, RateLimitTier, TokenType, UserType, parse_permissions,
22 permissions_to_string,
23};
24use systemprompt_identifiers::Actor;
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct ActClaim {
35 pub iss: String,
36 pub sub: String,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub act: Box<Option<Self>>,
39}
40
41pub const MAX_ACT_CHAIN_DEPTH: usize = 16;
42
43impl ActClaim {
44 #[must_use]
45 pub fn flatten_to_chain(&self) -> Vec<Actor> {
46 let mut chain = Vec::new();
47 let mut cursor = Some(self);
48 while let Some(node) = cursor {
49 if chain.len() >= MAX_ACT_CHAIN_DEPTH {
50 break;
51 }
52 chain.push(Actor::user(UserId::new(node.sub.clone())));
53 cursor = node.act.as_ref().as_ref();
54 }
55 chain
56 }
57
58 #[must_use]
59 pub fn depth(&self) -> usize {
60 let mut depth = 0usize;
61 let mut cursor = Some(self);
62 while let Some(node) = cursor {
63 depth += 1;
64 if depth > MAX_ACT_CHAIN_DEPTH {
65 return depth;
66 }
67 cursor = node.act.as_ref().as_ref();
68 }
69 depth
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct JwtClaims {
77 #[serde(flatten)]
78 pub registered: RegisteredClaims,
79 #[serde(flatten)]
80 pub custom: CustomClaims,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct RegisteredClaims {
86 pub sub: String,
87 pub iat: i64,
88 pub exp: i64,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub nbf: Option<i64>,
91 pub iss: String,
92 #[serde(
93 serialize_with = "serialize_audiences",
94 deserialize_with = "deserialize_audiences"
95 )]
96 pub aud: Vec<JwtAudience>,
97 pub jti: String,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct CustomClaims {
108 #[serde(
109 serialize_with = "serialize_scope",
110 deserialize_with = "deserialize_scope"
111 )]
112 pub scope: Vec<Permission>,
113
114 pub username: String,
115 pub email: String,
116 pub user_type: UserType,
117
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
119 pub roles: Vec<String>,
120
121 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
122 pub attributes: BTreeMap<String, serde_json::Value>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub client_id: Option<ClientId>,
127 pub token_type: TokenType,
128 pub auth_time: i64,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub session_id: Option<SessionId>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub rate_limit_tier: Option<RateLimitTier>,
134
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub plugin_id: Option<PluginId>,
137
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub act: Option<ActClaim>,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub purpose: Option<String>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub interaction: Option<TokenInteraction>,
144 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
145 pub browser_session: bool,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(rename_all = "snake_case")]
151pub enum TokenInteraction {
152 Interactive,
153 NonInteractive,
154}
155
156fn serialize_audiences<S>(auds: &[JwtAudience], s: S) -> Result<S::Ok, S::Error>
157where
158 S: serde::Serializer,
159{
160 use serde::ser::SerializeSeq;
161 let mut seq = s.serialize_seq(Some(auds.len()))?;
162 for aud in auds {
163 seq.serialize_element(aud.as_str())?;
164 }
165 seq.end()
166}
167
168fn deserialize_audiences<'de, D>(d: D) -> Result<Vec<JwtAudience>, D::Error>
169where
170 D: serde::Deserializer<'de>,
171{
172 let strings: Vec<String> = Vec::deserialize(d)?;
173 strings
174 .iter()
175 .map(|s| JwtAudience::from_str(s).map_err(serde::de::Error::custom))
176 .collect()
177}
178
179fn serialize_scope<S>(permissions: &[Permission], s: S) -> Result<S::Ok, S::Error>
180where
181 S: serde::Serializer,
182{
183 s.serialize_str(&permissions_to_string(permissions))
184}
185
186fn deserialize_scope<'de, D>(d: D) -> Result<Vec<Permission>, D::Error>
187where
188 D: serde::Deserializer<'de>,
189{
190 let scope_string: String = String::deserialize(d)?;
191 parse_permissions(&scope_string).map_err(serde::de::Error::custom)
192}
193
194impl JwtClaims {
195 pub fn has_permission(&self, permission: Permission) -> bool {
196 self.custom.scope.contains(&permission)
197 }
198
199 pub fn permissions(&self) -> &[Permission] {
200 &self.custom.scope
201 }
202
203 pub fn get_permissions(&self) -> Vec<Permission> {
204 self.custom.scope.clone()
205 }
206
207 pub fn get_scopes(&self) -> Vec<String> {
208 self.custom.scope.iter().map(ToString::to_string).collect()
209 }
210
211 pub fn is_admin(&self) -> bool {
212 self.has_permission(Permission::Admin)
213 }
214
215 pub fn is_registered_user(&self) -> bool {
216 self.has_permission(Permission::User)
217 }
218
219 pub fn is_anonymous(&self) -> bool {
220 self.has_permission(Permission::Anonymous)
221 }
222
223 pub fn has_audience(&self, aud: &JwtAudience) -> bool {
224 self.registered.aud.contains(aud)
225 }
226
227 pub fn has_role(&self, role: &str) -> bool {
228 self.custom.roles.iter().any(|r| r == role)
229 }
230
231 pub fn roles(&self) -> &[String] {
232 &self.custom.roles
233 }
234}