use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use systemprompt_identifiers::ClientId;
use uuid::Uuid;
use super::enums::UserType;
use super::permission::Permission;
pub const BEARER_PREFIX: &str = "Bearer ";
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthenticatedUser {
pub id: Uuid,
pub username: String,
pub email: String,
pub permissions: Vec<Permission>,
#[serde(default)]
pub roles: Vec<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub attributes: BTreeMap<String, serde_json::Value>,
}
impl AuthenticatedUser {
pub const fn new(
id: Uuid,
username: String,
email: String,
permissions: Vec<Permission>,
) -> Self {
Self {
id,
username,
email,
permissions,
roles: Vec::new(),
attributes: BTreeMap::new(),
}
}
pub const fn new_with_roles(
id: Uuid,
username: String,
email: String,
permissions: Vec<Permission>,
roles: Vec<String>,
) -> Self {
Self {
id,
username,
email,
permissions,
roles,
attributes: BTreeMap::new(),
}
}
#[must_use]
pub fn with_attributes(mut self, attributes: BTreeMap<String, serde_json::Value>) -> Self {
self.attributes = attributes;
self
}
#[must_use]
pub const fn attributes(&self) -> &BTreeMap<String, serde_json::Value> {
&self.attributes
}
pub fn has_permission(&self, permission: Permission) -> bool {
self.permissions.contains(&permission)
|| self.permissions.iter().any(|p| p.implies(&permission))
}
pub fn is_admin(&self) -> bool {
self.has_permission(Permission::Admin)
}
pub fn permissions(&self) -> &[Permission] {
&self.permissions
}
pub fn has_role(&self, role: &str) -> bool {
self.roles.iter().any(|r| r == role)
}
pub fn roles(&self) -> &[String] {
&self.roles
}
pub fn user_type(&self) -> UserType {
UserType::from_permissions(&self.permissions)
}
}
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("Invalid token format")]
InvalidTokenFormat,
#[error("Token expired")]
TokenExpired,
#[error("Token signature invalid")]
InvalidSignature,
#[error("User not found")]
UserNotFound,
#[error("Insufficient permissions")]
InsufficientPermissions,
#[error("Authentication failed: {message}")]
AuthenticationFailed { message: String },
#[error("Invalid OAuth request: {reason}")]
InvalidRequest { reason: String },
#[error("CSRF token (state) is required")]
MissingState,
#[error("Redirect URI is required and must be registered")]
InvalidRedirectUri,
#[error("PKCE code_challenge is required")]
MissingCodeChallenge,
#[error("PKCE method '{method}' not allowed (must be S256)")]
WeakPkceMethod { method: String },
#[error("Client ID {client_id} not found")]
ClientNotFound { client_id: ClientId },
#[error("Scope '{scope}' is invalid")]
InvalidScope { scope: String },
#[error("Token revocation requires authenticated user")]
UnauthenticatedRevocation,
#[error("WebAuthn RP ID could not be determined")]
InvalidRpId,
#[error("Client registration validation failed: {reason}")]
RegistrationFailed { reason: String },
#[error("Internal error: {0}")]
Internal(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PkceMethod {
S256,
}
impl std::str::FromStr for PkceMethod {
type Err = AuthError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"S256" => Ok(Self::S256),
"plain" => Err(AuthError::WeakPkceMethod {
method: s.to_owned(),
}),
_ => Err(AuthError::InvalidRequest {
reason: format!("Unknown PKCE method: {s}"),
}),
}
}
}
impl std::fmt::Display for PkceMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::S256 => write!(f, "S256"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResponseType {
Code,
Token,
}
impl std::str::FromStr for ResponseType {
type Err = AuthError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"code" => Ok(Self::Code),
"token" => Ok(Self::Token),
_ => Err(AuthError::InvalidRequest {
reason: format!("Unknown response type: {s}"),
}),
}
}
}
impl std::fmt::Display for ResponseType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Code => write!(f, "code"),
Self::Token => write!(f, "token"),
}
}
}