Skip to main content

systemprompt_models/auth/
enums.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5use super::permission::Permission;
6use crate::errors::ParseEnumError;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema)]
9#[serde(rename_all = "lowercase")]
10pub enum JwtAudience {
11    Web,
12    Api,
13    A2a,
14    Mcp,
15    Internal,
16    Bridge,
17    Hook,
18    #[serde(untagged)]
19    Resource(String),
20}
21
22impl JwtAudience {
23    pub fn as_str(&self) -> &str {
24        match self {
25            Self::Web => "web",
26            Self::Api => "api",
27            Self::A2a => "a2a",
28            Self::Mcp => "mcp",
29            Self::Internal => "internal",
30            Self::Bridge => "bridge",
31            Self::Hook => "hook",
32            Self::Resource(s) => s.as_str(),
33        }
34    }
35
36    pub fn standard() -> Vec<Self> {
37        vec![Self::Web, Self::Api, Self::A2a, Self::Mcp]
38    }
39
40    pub fn service() -> Vec<Self> {
41        vec![Self::Api, Self::Mcp, Self::A2a, Self::Internal]
42    }
43}
44
45impl fmt::Display for JwtAudience {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(f, "{}", self.as_str())
48    }
49}
50
51impl FromStr for JwtAudience {
52    type Err = ParseEnumError;
53    fn from_str(s: &str) -> Result<Self, Self::Err> {
54        match s {
55            "web" => Ok(Self::Web),
56            "api" => Ok(Self::Api),
57            "a2a" => Ok(Self::A2a),
58            "mcp" => Ok(Self::Mcp),
59            "internal" => Ok(Self::Internal),
60            "bridge" => Ok(Self::Bridge),
61            "hook" => Ok(Self::Hook),
62            _ => Ok(Self::Resource(s.to_owned())),
63        }
64    }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "lowercase")]
69pub enum UserType {
70    Admin,
71    User,
72    A2a,
73    Mcp,
74    Service,
75    Anon,
76    Unknown,
77}
78
79impl UserType {
80    /// Derives the caller type from a permission set, the single source of
81    /// truth for the permission → type mapping. The precedence is
82    /// privilege-descending (`Admin` wins over `User`, etc.); the hook scopes
83    /// resolve to `Service` so a hook principal is never silently downgraded
84    /// to `Anon`.
85    pub fn from_permissions(permissions: &[Permission]) -> Self {
86        let has = |p: Permission| permissions.contains(&p);
87        if has(Permission::Admin) {
88            Self::Admin
89        } else if has(Permission::User) {
90            Self::User
91        } else if has(Permission::A2a) {
92            Self::A2a
93        } else if has(Permission::Mcp) {
94            Self::Mcp
95        } else if has(Permission::Service)
96            || has(Permission::HookGovern)
97            || has(Permission::HookTrack)
98        {
99            Self::Service
100        } else {
101            Self::Anon
102        }
103    }
104
105    pub const fn as_str(&self) -> &'static str {
106        match self {
107            Self::Admin => "admin",
108            Self::User => "user",
109            Self::A2a => "a2a",
110            Self::Mcp => "mcp",
111            Self::Service => "service",
112            Self::Anon => "anon",
113            Self::Unknown => "unknown",
114        }
115    }
116
117    pub const fn rate_tier(&self) -> RateLimitTier {
118        match self {
119            Self::Admin => RateLimitTier::Admin,
120            Self::User => RateLimitTier::User,
121            Self::A2a => RateLimitTier::A2a,
122            Self::Mcp => RateLimitTier::Mcp,
123            Self::Service => RateLimitTier::Service,
124            Self::Anon | Self::Unknown => RateLimitTier::Anon,
125        }
126    }
127
128    // Human types (Admin/User) are authoritative on the users row, not the JWT:
129    // an Admin-claimed token whose user row is no longer in the admin role gets
130    // downgraded here. Machine types (Service/A2a/Mcp/Anon) are not reflected in
131    // users.roles — they are minted by the OAuth layer and trusted as claimed.
132    #[must_use]
133    pub const fn reconcile_with(self, user_is_admin: bool) -> Self {
134        match self {
135            Self::Admin if !user_is_admin => Self::User,
136            other => other,
137        }
138    }
139}
140
141impl fmt::Display for UserType {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "{}", self.as_str())
144    }
145}
146
147impl FromStr for UserType {
148    type Err = ParseEnumError;
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        match s {
151            "admin" => Ok(Self::Admin),
152            "user" => Ok(Self::User),
153            "a2a" => Ok(Self::A2a),
154            "mcp" => Ok(Self::Mcp),
155            "service" => Ok(Self::Service),
156            "anon" => Ok(Self::Anon),
157            "unknown" => Ok(Self::Unknown),
158            _ => Err(ParseEnumError::new("user_type", s)),
159        }
160    }
161}
162
163#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
164pub enum TokenType {
165    #[default]
166    Bearer,
167}
168
169impl TokenType {
170    pub const fn as_str(self) -> &'static str {
171        match self {
172            Self::Bearer => "Bearer",
173        }
174    }
175}
176
177impl fmt::Display for TokenType {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        write!(f, "Bearer")
180    }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
184#[serde(rename_all = "lowercase")]
185pub enum RateLimitTier {
186    Admin,
187    User,
188    A2a,
189    Mcp,
190    Service,
191    Anon,
192}
193
194impl RateLimitTier {
195    pub const fn as_str(&self) -> &'static str {
196        match self {
197            Self::Admin => "admin",
198            Self::User => "user",
199            Self::A2a => "a2a",
200            Self::Mcp => "mcp",
201            Self::Service => "service",
202            Self::Anon => "anon",
203        }
204    }
205}
206
207impl fmt::Display for RateLimitTier {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        write!(f, "{}", self.as_str())
210    }
211}
212
213impl FromStr for RateLimitTier {
214    type Err = ParseEnumError;
215    fn from_str(s: &str) -> Result<Self, Self::Err> {
216        match s {
217            "admin" => Ok(Self::Admin),
218            "user" => Ok(Self::User),
219            "a2a" => Ok(Self::A2a),
220            "mcp" => Ok(Self::Mcp),
221            "service" => Ok(Self::Service),
222            "anon" => Ok(Self::Anon),
223            _ => Err(ParseEnumError::new("rate_limit_tier", s)),
224        }
225    }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
229#[serde(rename_all = "lowercase")]
230pub enum UserRole {
231    Admin,
232    User,
233    Anonymous,
234}
235
236impl UserRole {
237    pub const fn as_str(&self) -> &'static str {
238        match self {
239            Self::Admin => "admin",
240            Self::User => "user",
241            Self::Anonymous => "anonymous",
242        }
243    }
244}
245
246impl fmt::Display for UserRole {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        write!(f, "{}", self.as_str())
249    }
250}
251
252impl FromStr for UserRole {
253    type Err = ParseEnumError;
254    fn from_str(s: &str) -> Result<Self, Self::Err> {
255        match s {
256            "admin" => Ok(Self::Admin),
257            "user" => Ok(Self::User),
258            "anonymous" => Ok(Self::Anonymous),
259            _ => Err(ParseEnumError::new("user_role", s)),
260        }
261    }
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
265#[serde(rename_all = "lowercase")]
266pub enum UserStatus {
267    Active,
268    Inactive,
269    Suspended,
270    Pending,
271    Deleted,
272    Temporary,
273}
274
275impl UserStatus {
276    pub const fn as_str(&self) -> &'static str {
277        match self {
278            Self::Active => "active",
279            Self::Inactive => "inactive",
280            Self::Suspended => "suspended",
281            Self::Pending => "pending",
282            Self::Deleted => "deleted",
283            Self::Temporary => "temporary",
284        }
285    }
286
287    pub const fn is_active(&self) -> bool {
288        matches!(self, Self::Active)
289    }
290}
291
292impl fmt::Display for UserStatus {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        write!(f, "{}", self.as_str())
295    }
296}
297
298impl FromStr for UserStatus {
299    type Err = ParseEnumError;
300    fn from_str(s: &str) -> Result<Self, Self::Err> {
301        match s {
302            "active" => Ok(Self::Active),
303            "inactive" => Ok(Self::Inactive),
304            "suspended" => Ok(Self::Suspended),
305            "pending" => Ok(Self::Pending),
306            "deleted" => Ok(Self::Deleted),
307            "temporary" => Ok(Self::Temporary),
308            _ => Err(ParseEnumError::new("user_status", s)),
309        }
310    }
311}