Skip to main content

systemprompt_models/auth/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use systemprompt_identifiers::ClientId;
4use uuid::Uuid;
5
6use super::enums::UserType;
7use super::permission::Permission;
8
9pub const BEARER_PREFIX: &str = "Bearer ";
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct AuthenticatedUser {
13    pub id: Uuid,
14    pub username: String,
15    pub email: String,
16    pub permissions: Vec<Permission>,
17    #[serde(default)]
18    pub roles: Vec<String>,
19    /// Opaque ABAC attribute bag forwarded into `JwtClaims.attributes` and
20    /// onward to `AuthzRequest.attributes`. Tenant-defined, namespaced
21    /// keys; core never interprets values.
22    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
23    pub attributes: BTreeMap<String, serde_json::Value>,
24}
25
26impl AuthenticatedUser {
27    pub const fn new(
28        id: Uuid,
29        username: String,
30        email: String,
31        permissions: Vec<Permission>,
32    ) -> Self {
33        Self {
34            id,
35            username,
36            email,
37            permissions,
38            roles: Vec::new(),
39            attributes: BTreeMap::new(),
40        }
41    }
42
43    pub const fn new_with_roles(
44        id: Uuid,
45        username: String,
46        email: String,
47        permissions: Vec<Permission>,
48        roles: Vec<String>,
49    ) -> Self {
50        Self {
51            id,
52            username,
53            email,
54            permissions,
55            roles,
56            attributes: BTreeMap::new(),
57        }
58    }
59
60    #[must_use]
61    pub fn with_attributes(mut self, attributes: BTreeMap<String, serde_json::Value>) -> Self {
62        self.attributes = attributes;
63        self
64    }
65
66    #[must_use]
67    pub const fn attributes(&self) -> &BTreeMap<String, serde_json::Value> {
68        &self.attributes
69    }
70
71    pub fn has_permission(&self, permission: Permission) -> bool {
72        self.permissions.contains(&permission)
73            || self.permissions.iter().any(|p| p.implies(&permission))
74    }
75
76    pub fn is_admin(&self) -> bool {
77        self.has_permission(Permission::Admin)
78    }
79
80    pub fn permissions(&self) -> &[Permission] {
81        &self.permissions
82    }
83
84    pub fn has_role(&self, role: &str) -> bool {
85        self.roles.iter().any(|r| r == role)
86    }
87
88    pub fn roles(&self) -> &[String] {
89        &self.roles
90    }
91
92    pub fn user_type(&self) -> UserType {
93        UserType::from_permissions(&self.permissions)
94    }
95}
96
97#[derive(Debug, thiserror::Error)]
98pub enum AuthError {
99    #[error("Invalid token format")]
100    InvalidTokenFormat,
101
102    #[error("Token expired")]
103    TokenExpired,
104
105    #[error("Token signature invalid")]
106    InvalidSignature,
107
108    #[error("User not found")]
109    UserNotFound,
110
111    #[error("Insufficient permissions")]
112    InsufficientPermissions,
113
114    #[error("Authentication failed: {message}")]
115    AuthenticationFailed { message: String },
116
117    #[error("Invalid OAuth request: {reason}")]
118    InvalidRequest { reason: String },
119
120    #[error("CSRF token (state) is required")]
121    MissingState,
122
123    #[error("Redirect URI is required and must be registered")]
124    InvalidRedirectUri,
125
126    #[error("PKCE code_challenge is required")]
127    MissingCodeChallenge,
128
129    #[error("PKCE method '{method}' not allowed (must be S256)")]
130    WeakPkceMethod { method: String },
131
132    #[error("Client ID {client_id} not found")]
133    ClientNotFound { client_id: ClientId },
134
135    #[error("Scope '{scope}' is invalid")]
136    InvalidScope { scope: String },
137
138    #[error("Token revocation requires authenticated user")]
139    UnauthenticatedRevocation,
140
141    #[error("WebAuthn RP ID could not be determined")]
142    InvalidRpId,
143
144    #[error("Client registration validation failed: {reason}")]
145    RegistrationFailed { reason: String },
146
147    #[error("Internal error: {0}")]
148    Internal(String),
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum PkceMethod {
153    S256,
154}
155
156impl std::str::FromStr for PkceMethod {
157    type Err = AuthError;
158
159    fn from_str(s: &str) -> Result<Self, Self::Err> {
160        match s {
161            "S256" => Ok(Self::S256),
162            "plain" => Err(AuthError::WeakPkceMethod {
163                method: s.to_owned(),
164            }),
165            _ => Err(AuthError::InvalidRequest {
166                reason: format!("Unknown PKCE method: {s}"),
167            }),
168        }
169    }
170}
171
172impl std::fmt::Display for PkceMethod {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        match self {
175            Self::S256 => write!(f, "S256"),
176        }
177    }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum ResponseType {
182    Code,
183    Token,
184}
185
186impl std::str::FromStr for ResponseType {
187    type Err = AuthError;
188
189    fn from_str(s: &str) -> Result<Self, Self::Err> {
190        match s {
191            "code" => Ok(Self::Code),
192            "token" => Ok(Self::Token),
193            _ => Err(AuthError::InvalidRequest {
194                reason: format!("Unknown response type: {s}"),
195            }),
196        }
197    }
198}
199
200impl std::fmt::Display for ResponseType {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        match self {
203            Self::Code => write!(f, "code"),
204            Self::Token => write!(f, "token"),
205        }
206    }
207}