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