use axum::{ body::Body, extract::{Path, State}, http::{header, Request, StatusCode}, middleware::Next, response::{IntoResponse, Response}, Json, }; use serde::{Deserialize, Serialize}; use serde_json::json; use std::collections::{HashMap, HashSet}; use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Permission { Read, Write, Delete, Admin, ManageUsers, ManageBots, ViewAnalytics, ManageSettings, ExecuteTasks, ViewLogs, ManageSecrets, AccessApi, ManageFiles, SendMessages, ViewConversations, ManageWebhooks, ManageIntegrations, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Role { Anonymous, User, Moderator, Admin, SuperAdmin, Service, Bot, BotOwner, BotOperator, BotViewer, } impl Role { pub fn permissions(&self) -> HashSet { match self { Self::Anonymous => HashSet::new(), Self::User => { let mut perms = HashSet::new(); perms.insert(Permission::Read); perms.insert(Permission::AccessApi); perms } Self::Moderator => { let mut perms = Self::User.permissions(); perms.insert(Permission::Write); perms.insert(Permission::ViewLogs); perms.insert(Permission::ViewAnalytics); perms.insert(Permission::ViewConversations); perms } Self::Admin => { let mut perms = Self::Moderator.permissions(); perms.insert(Permission::Delete); perms.insert(Permission::ManageUsers); perms.insert(Permission::ManageBots); perms.insert(Permission::ManageSettings); perms.insert(Permission::ExecuteTasks); perms.insert(Permission::ManageFiles); perms.insert(Permission::ManageWebhooks); perms } Self::SuperAdmin => { let mut perms = Self::Admin.permissions(); perms.insert(Permission::Admin); perms.insert(Permission::ManageSecrets); perms.insert(Permission::ManageIntegrations); perms } Self::Service => { let mut perms = HashSet::new(); perms.insert(Permission::Read); perms.insert(Permission::Write); perms.insert(Permission::AccessApi); perms.insert(Permission::ExecuteTasks); perms.insert(Permission::SendMessages); perms } Self::Bot => { let mut perms = HashSet::new(); perms.insert(Permission::Read); perms.insert(Permission::Write); perms.insert(Permission::AccessApi); perms.insert(Permission::SendMessages); perms } Self::BotOwner => { let mut perms = HashSet::new(); perms.insert(Permission::Read); perms.insert(Permission::Write); perms.insert(Permission::Delete); perms.insert(Permission::AccessApi); perms.insert(Permission::ManageBots); perms.insert(Permission::ManageSettings); perms.insert(Permission::ViewAnalytics); perms.insert(Permission::ViewLogs); perms.insert(Permission::ManageFiles); perms.insert(Permission::SendMessages); perms.insert(Permission::ViewConversations); perms.insert(Permission::ManageWebhooks); perms } Self::BotOperator => { let mut perms = HashSet::new(); perms.insert(Permission::Read); perms.insert(Permission::Write); perms.insert(Permission::AccessApi); perms.insert(Permission::ViewAnalytics); perms.insert(Permission::ViewLogs); perms.insert(Permission::SendMessages); perms.insert(Permission::ViewConversations); perms } Self::BotViewer => { let mut perms = HashSet::new(); perms.insert(Permission::Read); perms.insert(Permission::AccessApi); perms.insert(Permission::ViewAnalytics); perms.insert(Permission::ViewConversations); perms } } } pub fn has_permission(&self, permission: &Permission) -> bool { self.permissions().contains(permission) } pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "anonymous" => Self::Anonymous, "user" => Self::User, "moderator" | "mod" => Self::Moderator, "admin" => Self::Admin, "superadmin" | "super_admin" | "super" => Self::SuperAdmin, "service" | "svc" => Self::Service, "bot" => Self::Bot, "bot_owner" | "botowner" | "owner" => Self::BotOwner, "bot_operator" | "botoperator" | "operator" => Self::BotOperator, "bot_viewer" | "botviewer" | "viewer" => Self::BotViewer, _ => Self::Anonymous, } } pub fn hierarchy_level(&self) -> u8 { match self { Self::Anonymous => 0, Self::User => 1, Self::BotViewer => 2, Self::BotOperator => 3, Self::BotOwner => 4, Self::Bot => 4, Self::Moderator => 5, Self::Service => 6, Self::Admin => 7, Self::SuperAdmin => 8, } } pub fn is_at_least(&self, other: &Role) -> bool { self.hierarchy_level() >= other.hierarchy_level() } } impl Default for Role { fn default() -> Self { Self::Anonymous } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct BotAccess { pub bot_id: Uuid, pub role: Role, pub granted_at: Option, pub granted_by: Option, pub expires_at: Option, } impl BotAccess { pub fn new(bot_id: Uuid, role: Role) -> Self { Self { bot_id, role, granted_at: Some(chrono::Utc::now().timestamp()), granted_by: None, expires_at: None, } } pub fn owner(bot_id: Uuid) -> Self { Self::new(bot_id, Role::BotOwner) } pub fn operator(bot_id: Uuid) -> Self { Self::new(bot_id, Role::BotOperator) } pub fn viewer(bot_id: Uuid) -> Self { Self::new(bot_id, Role::BotViewer) } pub fn with_expiry(mut self, expires_at: i64) -> Self { self.expires_at = Some(expires_at); self } pub fn with_grantor(mut self, granted_by: Uuid) -> Self { self.granted_by = Some(granted_by); self } pub fn is_expired(&self) -> bool { if let Some(expires) = self.expires_at { chrono::Utc::now().timestamp() > expires } else { false } } pub fn is_valid(&self) -> bool { !self.is_expired() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthenticatedUser { pub user_id: Uuid, pub username: String, pub email: Option, pub roles: Vec, pub bot_access: HashMap, pub current_bot_id: Option, pub session_id: Option, pub organization_id: Option, pub metadata: HashMap, } impl Default for AuthenticatedUser { fn default() -> Self { Self::anonymous() } } impl AuthenticatedUser { pub fn new(user_id: Uuid, username: String) -> Self { Self { user_id, username, email: None, roles: vec![Role::User], bot_access: HashMap::new(), current_bot_id: None, session_id: None, organization_id: None, metadata: HashMap::new(), } } pub fn anonymous() -> Self { Self { user_id: Uuid::nil(), username: "anonymous".to_string(), email: None, roles: vec![Role::Anonymous], bot_access: HashMap::new(), current_bot_id: None, session_id: None, organization_id: None, metadata: HashMap::new(), } } pub fn service(name: &str) -> Self { Self { user_id: Uuid::nil(), username: format!("service:{}", name), email: None, roles: vec![Role::Service], bot_access: HashMap::new(), current_bot_id: None, session_id: None, organization_id: None, metadata: HashMap::new(), } } pub fn bot_user(bot_id: Uuid, bot_name: &str) -> Self { Self { user_id: bot_id, username: format!("bot:{}", bot_name), email: None, roles: vec![Role::Bot], bot_access: HashMap::new(), current_bot_id: Some(bot_id), session_id: None, organization_id: None, metadata: HashMap::new(), } } pub fn with_email(mut self, email: impl Into) -> Self { self.email = Some(email.into()); self } pub fn with_role(mut self, role: Role) -> Self { if !self.roles.contains(&role) { self.roles.push(role); } self } pub fn with_roles(mut self, roles: Vec) -> Self { self.roles = roles; self } pub fn with_bot_access(mut self, access: BotAccess) -> Self { self.bot_access.insert(access.bot_id, access); self } pub fn with_current_bot(mut self, bot_id: Uuid) -> Self { self.current_bot_id = Some(bot_id); self } pub fn with_session(mut self, session_id: impl Into) -> Self { self.session_id = Some(session_id.into()); self } pub fn with_organization(mut self, org_id: Uuid) -> Self { self.organization_id = Some(org_id); self } pub fn with_metadata(mut self, key: impl Into, value: impl Into) -> Self { self.metadata.insert(key.into(), value.into()); self } pub fn has_permission(&self, permission: &Permission) -> bool { self.roles.iter().any(|r| r.has_permission(permission)) } pub fn has_any_permission(&self, permissions: &[Permission]) -> bool { permissions.iter().any(|p| self.has_permission(p)) } pub fn has_all_permissions(&self, permissions: &[Permission]) -> bool { permissions.iter().all(|p| self.has_permission(p)) } pub fn has_role(&self, role: &Role) -> bool { self.roles.contains(role) } pub fn has_any_role(&self, roles: &[Role]) -> bool { roles.iter().any(|r| self.roles.contains(r)) } pub fn highest_role(&self) -> &Role { self.roles .iter() .max_by_key(|r| r.hierarchy_level()) .unwrap_or(&Role::Anonymous) } pub fn is_admin(&self) -> bool { self.has_role(&Role::Admin) || self.has_role(&Role::SuperAdmin) } pub fn is_super_admin(&self) -> bool { self.has_role(&Role::SuperAdmin) } pub fn is_authenticated(&self) -> bool { !self.has_role(&Role::Anonymous) && self.user_id != Uuid::nil() } pub fn is_service(&self) -> bool { self.has_role(&Role::Service) } pub fn is_bot(&self) -> bool { self.has_role(&Role::Bot) } pub fn get_bot_access(&self, bot_id: &Uuid) -> Option<&BotAccess> { self.bot_access.get(bot_id).filter(|a| a.is_valid()) } pub fn get_bot_role(&self, bot_id: &Uuid) -> Option<&Role> { self.get_bot_access(bot_id).map(|a| &a.role) } pub fn has_bot_permission(&self, bot_id: &Uuid, permission: &Permission) -> bool { if self.is_admin() { return true; } if let Some(access) = self.get_bot_access(bot_id) { access.role.has_permission(permission) } else { false } } pub fn can_access_bot(&self, bot_id: &Uuid) -> bool { if self.is_admin() || self.is_service() { return true; } if self.current_bot_id.as_ref() == Some(bot_id) && self.is_bot() { return true; } self.get_bot_access(bot_id).is_some() } pub fn can_manage_bot(&self, bot_id: &Uuid) -> bool { if self.is_admin() { return true; } if let Some(access) = self.get_bot_access(bot_id) { access.role == Role::BotOwner } else { false } } pub fn can_operate_bot(&self, bot_id: &Uuid) -> bool { if self.is_admin() { return true; } if let Some(access) = self.get_bot_access(bot_id) { access.role.is_at_least(&Role::BotOperator) } else { false } } pub fn can_view_bot(&self, bot_id: &Uuid) -> bool { if self.is_admin() || self.is_service() { return true; } if let Some(access) = self.get_bot_access(bot_id) { access.role.is_at_least(&Role::BotViewer) } else { false } } pub fn can_access_organization(&self, org_id: &Uuid) -> bool { if self.is_admin() { return true; } self.organization_id .as_ref() .map(|id| id == org_id) .unwrap_or(false) } pub fn accessible_bot_ids(&self) -> Vec { self.bot_access .iter() .filter(|(_, access)| access.is_valid()) .map(|(id, _)| *id) .collect() } pub fn owned_bot_ids(&self) -> Vec { self.bot_access .iter() .filter(|(_, access)| access.is_valid() && access.role == Role::BotOwner) .map(|(id, _)| *id) .collect() } } #[derive(Debug, Clone)] pub struct AuthConfig { pub require_auth: bool, pub jwt_secret: Option, pub api_key_header: String, pub bearer_prefix: String, pub session_cookie_name: String, pub allow_anonymous_paths: Vec, pub public_paths: Vec, pub bot_id_header: String, pub org_id_header: String, } impl Default for AuthConfig { fn default() -> Self { Self { require_auth: true, jwt_secret: None, api_key_header: "X-API-Key".to_string(), bearer_prefix: "Bearer ".to_string(), session_cookie_name: "session_id".to_string(), allow_anonymous_paths: vec![ "/health".to_string(), "/healthz".to_string(), "/api/health".to_string(), "/api/v1/health".to_string(), "/.well-known".to_string(), "/metrics".to_string(), ], public_paths: vec![ "/".to_string(), "/static".to_string(), "/favicon.ico".to_string(), "/robots.txt".to_string(), ], bot_id_header: "X-Bot-ID".to_string(), org_id_header: "X-Organization-ID".to_string(), } } } impl AuthConfig { pub fn new() -> Self { Self::default() } pub fn from_env() -> Self { let mut config = Self::default(); if let Ok(secret) = std::env::var("JWT_SECRET") { config.jwt_secret = Some(secret); } if let Ok(require) = std::env::var("REQUIRE_AUTH") { config.require_auth = require == "true" || require == "1"; } if let Ok(paths) = std::env::var("ANONYMOUS_PATHS") { config.allow_anonymous_paths = paths .split(',') .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .collect(); } config } pub fn with_jwt_secret(mut self, secret: impl Into) -> Self { self.jwt_secret = Some(secret.into()); self } pub fn with_require_auth(mut self, require: bool) -> Self { self.require_auth = require; self } pub fn add_anonymous_path(mut self, path: impl Into) -> Self { self.allow_anonymous_paths.push(path.into()); self } pub fn add_public_path(mut self, path: impl Into) -> Self { self.public_paths.push(path.into()); self } pub fn is_public_path(&self, path: &str) -> bool { for public_path in &self.public_paths { if path == public_path || path.starts_with(&format!("{}/", public_path)) { return true; } } false } pub fn is_anonymous_allowed(&self, path: &str) -> bool { for allowed_path in &self.allow_anonymous_paths { if path == allowed_path || path.starts_with(&format!("{}/", allowed_path)) { return true; } } false } } #[derive(Debug)] pub enum AuthError { MissingToken, InvalidToken, ExpiredToken, InsufficientPermissions, InvalidApiKey, SessionExpired, UserNotFound, AccountDisabled, RateLimited, BotAccessDenied, BotNotFound, OrganizationAccessDenied, InternalError(String), } impl AuthError { pub fn status_code(&self) -> StatusCode { match self { Self::MissingToken => StatusCode::UNAUTHORIZED, Self::InvalidToken => StatusCode::UNAUTHORIZED, Self::ExpiredToken => StatusCode::UNAUTHORIZED, Self::InsufficientPermissions => StatusCode::FORBIDDEN, Self::InvalidApiKey => StatusCode::UNAUTHORIZED, Self::SessionExpired => StatusCode::UNAUTHORIZED, Self::UserNotFound => StatusCode::UNAUTHORIZED, Self::AccountDisabled => StatusCode::FORBIDDEN, Self::RateLimited => StatusCode::TOO_MANY_REQUESTS, Self::BotAccessDenied => StatusCode::FORBIDDEN, Self::BotNotFound => StatusCode::NOT_FOUND, Self::OrganizationAccessDenied => StatusCode::FORBIDDEN, Self::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } pub fn error_code(&self) -> &'static str { match self { Self::MissingToken => "missing_token", Self::InvalidToken => "invalid_token", Self::ExpiredToken => "expired_token", Self::InsufficientPermissions => "insufficient_permissions", Self::InvalidApiKey => "invalid_api_key", Self::SessionExpired => "session_expired", Self::UserNotFound => "user_not_found", Self::AccountDisabled => "account_disabled", Self::RateLimited => "rate_limited", Self::BotAccessDenied => "bot_access_denied", Self::BotNotFound => "bot_not_found", Self::OrganizationAccessDenied => "organization_access_denied", Self::InternalError(_) => "internal_error", } } pub fn message(&self) -> String { match self { Self::MissingToken => "Authentication token is required".to_string(), Self::InvalidToken => "Invalid authentication token".to_string(), Self::ExpiredToken => "Authentication token has expired".to_string(), Self::InsufficientPermissions => { "You don't have permission to access this resource".to_string() } Self::InvalidApiKey => "Invalid API key".to_string(), Self::SessionExpired => "Your session has expired".to_string(), Self::UserNotFound => "User not found".to_string(), Self::AccountDisabled => "Your account has been disabled".to_string(), Self::RateLimited => "Too many requests, please try again later".to_string(), Self::BotAccessDenied => "You don't have access to this bot".to_string(), Self::BotNotFound => "Bot not found".to_string(), Self::OrganizationAccessDenied => { "You don't have access to this organization".to_string() } Self::InternalError(_) => "An internal error occurred".to_string(), } } } impl IntoResponse for AuthError { fn into_response(self) -> Response { let status = self.status_code(); let body = Json(json!({ "error": self.error_code(), "message": self.message() })); (status, body).into_response() } } pub fn extract_user_from_request( request: &Request, config: &AuthConfig, ) -> Result { if let Some(api_key) = request .headers() .get(&config.api_key_header) .and_then(|v| v.to_str().ok()) { let mut user = validate_api_key_sync(api_key)?; if let Some(bot_id) = extract_bot_id_from_request(request, config) { user = user.with_current_bot(bot_id); } return Ok(user); } if let Some(auth_header) = request .headers() .get(header::AUTHORIZATION) .and_then(|v| v.to_str().ok()) { if let Some(token) = auth_header.strip_prefix(&config.bearer_prefix) { let mut user = validate_bearer_token_sync(token)?; if let Some(bot_id) = extract_bot_id_from_request(request, config) { user = user.with_current_bot(bot_id); } return Ok(user); } } if let Some(session_id) = extract_session_from_cookies(request, &config.session_cookie_name) { let mut user = validate_session_sync(&session_id)?; if let Some(bot_id) = extract_bot_id_from_request(request, config) { user = user.with_current_bot(bot_id); } return Ok(user); } if let Some(user_id) = request .headers() .get("X-User-ID") .and_then(|v| v.to_str().ok()) .and_then(|s| Uuid::parse_str(s).ok()) { let mut user = AuthenticatedUser::new(user_id, "header-user".to_string()); if let Some(bot_id) = extract_bot_id_from_request(request, config) { user = user.with_current_bot(bot_id); } return Ok(user); } Err(AuthError::MissingToken) } fn extract_bot_id_from_request(request: &Request, config: &AuthConfig) -> Option { request .headers() .get(&config.bot_id_header) .and_then(|v| v.to_str().ok()) .and_then(|s| Uuid::parse_str(s).ok()) } fn extract_session_from_cookies(request: &Request, cookie_name: &str) -> Option { request .headers() .get(header::COOKIE) .and_then(|v| v.to_str().ok()) .and_then(|cookies| { cookies.split(';').find_map(|cookie| { let mut parts = cookie.trim().splitn(2, '='); let name = parts.next()?; let value = parts.next()?; if name == cookie_name { Some(value.to_string()) } else { None } }) }) } fn validate_api_key_sync(api_key: &str) -> Result { if api_key.is_empty() { return Err(AuthError::InvalidApiKey); } if api_key.len() < 16 { return Err(AuthError::InvalidApiKey); } Ok(AuthenticatedUser::service("api-client").with_metadata("api_key_prefix", &api_key[..8])) } fn validate_bearer_token_sync(token: &str) -> Result { if token.is_empty() { return Err(AuthError::InvalidToken); } let parts: Vec<&str> = token.split('.').collect(); if parts.len() != 3 { return Err(AuthError::InvalidToken); } Ok(AuthenticatedUser::new( Uuid::new_v4(), "jwt-user".to_string(), )) } fn validate_session_sync(session_id: &str) -> Result { if session_id.is_empty() { return Err(AuthError::SessionExpired); } if Uuid::parse_str(session_id).is_err() && session_id.len() < 32 { return Err(AuthError::InvalidToken); } Ok( AuthenticatedUser::new(Uuid::new_v4(), "session-user".to_string()) .with_session(session_id), ) } pub async fn auth_middleware( State(config): State>, mut request: Request, next: Next, ) -> Result { let path = request.uri().path().to_string(); if config.is_public_path(&path) || config.is_anonymous_allowed(&path) { request .extensions_mut() .insert(AuthenticatedUser::anonymous()); return Ok(next.run(request).await); } match extract_user_from_request(&request, &config) { Ok(user) => { request.extensions_mut().insert(user); Ok(next.run(request).await) } Err(e) => { if !config.require_auth { request .extensions_mut() .insert(AuthenticatedUser::anonymous()); return Ok(next.run(request).await); } Err(e) } } } pub async fn require_auth_middleware( mut request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.is_authenticated() { return Err(AuthError::MissingToken); } request.extensions_mut().insert(user); Ok(next.run(request).await) } pub fn require_permission( permission: Permission, ) -> impl Fn(Request) -> Result, AuthError> + Clone { move |request: Request| { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.has_permission(&permission) { return Err(AuthError::InsufficientPermissions); } Ok(request) } } pub fn require_role( role: Role, ) -> impl Fn(Request) -> Result, AuthError> + Clone { move |request: Request| { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.has_role(&role) { return Err(AuthError::InsufficientPermissions); } Ok(request) } } pub fn require_admin() -> impl Fn(Request) -> Result, AuthError> + Clone { move |request: Request| { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.is_admin() { return Err(AuthError::InsufficientPermissions); } Ok(request) } } pub fn require_bot_access( bot_id: Uuid, ) -> impl Fn(Request) -> Result, AuthError> + Clone { move |request: Request| { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.can_access_bot(&bot_id) { return Err(AuthError::BotAccessDenied); } Ok(request) } } pub fn require_bot_permission( bot_id: Uuid, permission: Permission, ) -> impl Fn(Request) -> Result, AuthError> + Clone { move |request: Request| { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.has_bot_permission(&bot_id, &permission) { return Err(AuthError::InsufficientPermissions); } Ok(request) } } pub async fn require_permission_middleware( permission: Permission, request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.has_permission(&permission) { return Err(AuthError::InsufficientPermissions); } Ok(next.run(request).await) } pub async fn require_role_middleware( role: Role, request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.has_role(&role) { return Err(AuthError::InsufficientPermissions); } Ok(next.run(request).await) } pub async fn admin_only_middleware( request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.is_admin() { return Err(AuthError::InsufficientPermissions); } Ok(next.run(request).await) } pub async fn bot_scope_middleware( Path(bot_id): Path, mut request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.can_access_bot(&bot_id) { return Err(AuthError::BotAccessDenied); } let user = user.with_current_bot(bot_id); request.extensions_mut().insert(user); Ok(next.run(request).await) } pub async fn bot_owner_middleware( Path(bot_id): Path, request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.can_manage_bot(&bot_id) { return Err(AuthError::InsufficientPermissions); } Ok(next.run(request).await) } pub async fn bot_operator_middleware( Path(bot_id): Path, request: Request, next: Next, ) -> Result { let user = request .extensions() .get::() .cloned() .unwrap_or_else(AuthenticatedUser::anonymous); if !user.can_operate_bot(&bot_id) { return Err(AuthError::InsufficientPermissions); } Ok(next.run(request).await) } #[cfg(test)] mod tests { use super::*; #[test] fn test_role_permissions() { assert!(!Role::Anonymous.has_permission(&Permission::Read)); assert!(Role::User.has_permission(&Permission::Read)); assert!(Role::User.has_permission(&Permission::AccessApi)); assert!(!Role::User.has_permission(&Permission::Write)); assert!(Role::Admin.has_permission(&Permission::ManageUsers)); assert!(Role::Admin.has_permission(&Permission::Delete)); assert!(Role::SuperAdmin.has_permission(&Permission::ManageSecrets)); } #[test] fn test_role_from_str() { assert_eq!(Role::from_str("admin"), Role::Admin); assert_eq!(Role::from_str("ADMIN"), Role::Admin); assert_eq!(Role::from_str("user"), Role::User); assert_eq!(Role::from_str("superadmin"), Role::SuperAdmin); assert_eq!(Role::from_str("bot_owner"), Role::BotOwner); assert_eq!(Role::from_str("unknown"), Role::Anonymous); } #[test] fn test_role_hierarchy() { assert!(Role::SuperAdmin.is_at_least(&Role::Admin)); assert!(Role::Admin.is_at_least(&Role::Moderator)); assert!(Role::BotOwner.is_at_least(&Role::BotOperator)); assert!(Role::BotOperator.is_at_least(&Role::BotViewer)); assert!(!Role::User.is_at_least(&Role::Admin)); } #[test] fn test_authenticated_user_builder() { let user = AuthenticatedUser::new(Uuid::new_v4(), "testuser".to_string()) .with_email("test@example.com") .with_role(Role::Admin) .with_metadata("key", "value"); assert_eq!(user.email, Some("test@example.com".to_string())); assert!(user.has_role(&Role::Admin)); assert_eq!(user.metadata.get("key"), Some(&"value".to_string())); } #[test] fn test_user_permissions() { let admin = AuthenticatedUser::new(Uuid::new_v4(), "admin".to_string()) .with_role(Role::Admin); assert!(admin.has_permission(&Permission::ManageUsers)); assert!(admin.has_permission(&Permission::Delete)); assert!(admin.is_admin()); let user = AuthenticatedUser::new(Uuid::new_v4(), "user".to_string()); assert!(user.has_permission(&Permission::Read)); assert!(!user.has_permission(&Permission::ManageUsers)); assert!(!user.is_admin()); } #[test] fn test_anonymous_user() { let anon = AuthenticatedUser::anonymous(); assert!(!anon.is_authenticated()); assert!(anon.has_role(&Role::Anonymous)); assert!(!anon.has_permission(&Permission::Read)); } #[test] fn test_service_user() { let service = AuthenticatedUser::service("scheduler"); assert!(service.has_role(&Role::Service)); assert!(service.has_permission(&Permission::ExecuteTasks)); } #[test] fn test_bot_user() { let bot_id = Uuid::new_v4(); let bot = AuthenticatedUser::bot_user(bot_id, "test-bot"); assert!(bot.is_bot()); assert!(bot.has_permission(&Permission::SendMessages)); assert_eq!(bot.current_bot_id, Some(bot_id)); } #[test] fn test_auth_config_paths() { let config = AuthConfig::default(); assert!(config.is_anonymous_allowed("/health")); assert!(config.is_anonymous_allowed("/api/health")); assert!(!config.is_anonymous_allowed("/api/users")); assert!(config.is_public_path("/static")); assert!(config.is_public_path("/static/css/style.css")); assert!(!config.is_public_path("/api/private")); } #[test] fn test_auth_error_responses() { assert_eq!(AuthError::MissingToken.status_code(), StatusCode::UNAUTHORIZED); assert_eq!(AuthError::InsufficientPermissions.status_code(), StatusCode::FORBIDDEN); assert_eq!(AuthError::RateLimited.status_code(), StatusCode::TOO_MANY_REQUESTS); assert_eq!(AuthError::BotAccessDenied.status_code(), StatusCode::FORBIDDEN); } #[test] fn test_bot_access() { let bot_id = Uuid::new_v4(); let other_bot_id = Uuid::new_v4(); let user = AuthenticatedUser::new(Uuid::new_v4(), "user".to_string()) .with_bot_access(BotAccess::viewer(bot_id)); assert!(user.can_access_bot(&bot_id)); assert!(user.can_view_bot(&bot_id)); assert!(!user.can_operate_bot(&bot_id)); assert!(!user.can_manage_bot(&bot_id)); assert!(!user.can_access_bot(&other_bot_id)); let admin = AuthenticatedUser::new(Uuid::new_v4(), "admin".to_string()) .with_role(Role::Admin); assert!(admin.can_access_bot(&bot_id)); assert!(admin.can_access_bot(&other_bot_id)); } #[test] fn test_bot_owner_access() { let bot_id = Uuid::new_v4(); let owner = AuthenticatedUser::new(Uuid::new_v4(), "owner".to_string()) .with_bot_access(BotAccess::owner(bot_id)); assert!(owner.can_access_bot(&bot_id)); assert!(owner.can_view_bot(&bot_id)); assert!(owner.can_operate_bot(&bot_id)); assert!(owner.can_manage_bot(&bot_id)); } #[test] fn test_bot_operator_access() { let bot_id = Uuid::new_v4(); let operator = AuthenticatedUser::new(Uuid::new_v4(), "operator".to_string()) .with_bot_access(BotAccess::operator(bot_id)); assert!(operator.can_access_bot(&bot_id)); assert!(operator.can_view_bot(&bot_id)); assert!(operator.can_operate_bot(&bot_id)); assert!(!operator.can_manage_bot(&bot_id)); } #[test] fn test_bot_permission_check() { let bot_id = Uuid::new_v4(); let operator = AuthenticatedUser::new(Uuid::new_v4(), "operator".to_string()) .with_bot_access(BotAccess::operator(bot_id)); assert!(operator.has_bot_permission(&bot_id, &Permission::SendMessages)); assert!(operator.has_bot_permission(&bot_id, &Permission::ViewAnalytics)); assert!(!operator.has_bot_permission(&bot_id, &Permission::ManageBots)); } #[test] fn test_bot_access_expiry() { let bot_id = Uuid::new_v4(); let past_time = chrono::Utc::now().timestamp() - 3600; let expired_access = BotAccess::viewer(bot_id).with_expiry(past_time); assert!(expired_access.is_expired()); assert!(!expired_access.is_valid()); let future_time = chrono::Utc::now().timestamp() + 3600; let valid_access = BotAccess::viewer(bot_id).with_expiry(future_time); assert!(!valid_access.is_expired()); assert!(valid_access.is_valid()); } #[test] fn test_accessible_bot_ids() { let bot1 = Uuid::new_v4(); let bot2 = Uuid::new_v4(); let user = AuthenticatedUser::new(Uuid::new_v4(), "user".to_string()) .with_bot_access(BotAccess::owner(bot1)) .with_bot_access(BotAccess::viewer(bot2)); let accessible = user.accessible_bot_ids(); assert_eq!(accessible.len(), 2); assert!(accessible.contains(&bot1)); assert!(accessible.contains(&bot2)); let owned = user.owned_bot_ids(); assert_eq!(owned.len(), 1); assert!(owned.contains(&bot1)); } #[test] fn test_organization_access() { let org_id = Uuid::new_v4(); let other_org_id = Uuid::new_v4(); let user = AuthenticatedUser::new(Uuid::new_v4(), "user".to_string()) .with_organization(org_id); assert!(user.can_access_organization(&org_id)); assert!(!user.can_access_organization(&other_org_id)); } #[test] fn test_has_any_permission() { let user = AuthenticatedUser::new(Uuid::new_v4(), "user".to_string()); assert!(user.has_any_permission(&[Permission::Read, Permission::Write])); assert!(!user.has_any_permission(&[Permission::Delete, Permission::Admin])); } #[test] fn test_has_all_permissions() { let admin = AuthenticatedUser::new(Uuid::new_v4(), "admin".to_string()) .with_role(Role::Admin); assert!(admin.has_all_permissions(&[Permission::Read, Permission::Write, Permission::Delete])); assert!(!admin.has_all_permissions(&[Permission::ManageSecrets])); } #[test] fn test_highest_role() { let user = AuthenticatedUser::new(Uuid::new_v4(), "user".to_string()) .with_role(Role::Admin) .with_role(Role::Moderator); assert_eq!(user.highest_role(), &Role::Admin); } }