Skip to main content

systemprompt_models/auth/
types.rs

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