systemprompt_models/auth/
types.rs1use 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 #[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}