systemprompt_models/auth/
claims.rs1use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10use std::str::FromStr;
11use systemprompt_identifiers::{ClientId, SessionId, UserId};
12
13use super::{
14 JwtAudience, Permission, RateLimitTier, TokenType, UserType, parse_permissions,
15 permissions_to_string,
16};
17use systemprompt_identifiers::Actor;
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct ActClaim {
28 pub iss: String,
29 pub sub: String,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub act: Box<Option<Self>>,
32}
33
34pub const MAX_ACT_CHAIN_DEPTH: usize = 16;
38
39impl ActClaim {
40 #[must_use]
49 pub fn flatten_to_chain(&self) -> Vec<Actor> {
50 let mut chain = Vec::new();
51 let mut cursor = Some(self);
52 while let Some(node) = cursor {
53 if chain.len() >= MAX_ACT_CHAIN_DEPTH {
54 break;
55 }
56 chain.push(Actor::user(UserId::new(node.sub.clone())));
57 cursor = node.act.as_ref().as_ref();
58 }
59 chain
60 }
61
62 #[must_use]
66 pub fn depth(&self) -> usize {
67 let mut depth = 0usize;
68 let mut cursor = Some(self);
69 while let Some(node) = cursor {
70 depth += 1;
71 if depth > MAX_ACT_CHAIN_DEPTH {
72 return depth;
73 }
74 cursor = node.act.as_ref().as_ref();
75 }
76 depth
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct JwtClaims {
88 pub sub: String,
89 pub iat: i64,
90 pub exp: i64,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub nbf: Option<i64>,
93 pub iss: String,
94 #[serde(
95 serialize_with = "serialize_audiences",
96 deserialize_with = "deserialize_audiences"
97 )]
98 pub aud: Vec<JwtAudience>,
99 pub jti: String,
100
101 #[serde(
105 serialize_with = "serialize_scope",
106 deserialize_with = "deserialize_scope"
107 )]
108 pub scope: Vec<Permission>,
109
110 pub username: String,
111 pub email: String,
112 pub user_type: UserType,
113
114 #[serde(default, skip_serializing_if = "Vec::is_empty")]
121 pub roles: Vec<String>,
122
123 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
129 pub attributes: BTreeMap<String, serde_json::Value>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub client_id: Option<ClientId>,
133 pub token_type: TokenType,
134 pub auth_time: i64,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub session_id: Option<SessionId>,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
142 pub rate_limit_tier: Option<RateLimitTier>,
143
144 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub plugin_id: Option<String>,
150
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub act: Option<ActClaim>,
153}
154
155fn serialize_audiences<S>(auds: &[JwtAudience], s: S) -> Result<S::Ok, S::Error>
156where
157 S: serde::Serializer,
158{
159 use serde::ser::SerializeSeq;
160 let mut seq = s.serialize_seq(Some(auds.len()))?;
161 for aud in auds {
162 seq.serialize_element(aud.as_str())?;
163 }
164 seq.end()
165}
166
167fn deserialize_audiences<'de, D>(d: D) -> Result<Vec<JwtAudience>, D::Error>
168where
169 D: serde::Deserializer<'de>,
170{
171 let strings: Vec<String> = Vec::deserialize(d)?;
172 strings
173 .iter()
174 .map(|s| JwtAudience::from_str(s).map_err(serde::de::Error::custom))
175 .collect()
176}
177
178fn serialize_scope<S>(permissions: &[Permission], s: S) -> Result<S::Ok, S::Error>
179where
180 S: serde::Serializer,
181{
182 s.serialize_str(&permissions_to_string(permissions))
183}
184
185fn deserialize_scope<'de, D>(d: D) -> Result<Vec<Permission>, D::Error>
186where
187 D: serde::Deserializer<'de>,
188{
189 let scope_string: String = String::deserialize(d)?;
190 parse_permissions(&scope_string).map_err(serde::de::Error::custom)
191}
192
193impl JwtClaims {
194 pub fn has_permission(&self, permission: Permission) -> bool {
195 self.scope.contains(&permission)
196 }
197
198 pub fn permissions(&self) -> &[Permission] {
199 &self.scope
200 }
201
202 pub fn get_permissions(&self) -> Vec<Permission> {
203 self.scope.clone()
204 }
205
206 pub fn get_scopes(&self) -> Vec<String> {
207 self.scope.iter().map(ToString::to_string).collect()
208 }
209
210 pub fn is_admin(&self) -> bool {
211 self.has_permission(Permission::Admin)
212 }
213
214 pub fn is_registered_user(&self) -> bool {
215 self.has_permission(Permission::User)
216 }
217
218 pub fn is_anonymous(&self) -> bool {
219 self.has_permission(Permission::Anonymous)
220 }
221
222 pub fn has_audience(&self, aud: &JwtAudience) -> bool {
223 self.aud.contains(aud)
224 }
225
226 pub fn has_role(&self, role: &str) -> bool {
227 self.roles.iter().any(|r| r == role)
228 }
229
230 pub fn roles(&self) -> &[String] {
231 &self.roles
232 }
233}