Skip to main content

systemprompt_models/auth/
types.rs

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