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