From 81b8fd8f2d8ec610fe39e740b161ba2b64217509 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sat, 10 Jan 2026 11:14:33 -0300 Subject: [PATCH] fix(auth): handle Zitadel session tokens and grant Admin role - Treat non-JWT bearer tokens as Zitadel session IDs - Grant Admin role to valid sessions (temporary until proper role lookup) - Add is_jwt_format helper to distinguish JWTs from session IDs - Update RBAC to allow authenticated users access to UI monitoring routes --- src/security/auth.rs | 30 ++++++++++++++++++++++++++++-- src/security/rbac_middleware.rs | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/security/auth.rs b/src/security/auth.rs index 68c18ef98..40b5bc967 100644 --- a/src/security/auth.rs +++ b/src/security/auth.rs @@ -832,12 +832,21 @@ fn validate_session_sync(session_id: &str) -> Result bool { + let parts: Vec<&str> = token.split('.').collect(); + parts.len() == 3 +} + #[derive(Clone)] pub struct AuthMiddlewareState { pub config: Arc, @@ -948,7 +957,24 @@ async fn authenticate_with_extracted_data( } if let Some(token) = data.bearer_token { - let mut user = registry.authenticate_token(&token).await?; + // Check if token is JWT format - if so, try providers + if is_jwt_format(&token) { + match registry.authenticate_token(&token).await { + Ok(mut user) => { + if let Some(bid) = data.bot_id { + user = user.with_current_bot(bid); + } + return Ok(user); + } + Err(e) => { + debug!("JWT authentication failed: {:?}", e); + // Fall through to try as session ID + } + } + } + + // Non-JWT token - treat as Zitadel session ID + let mut user = validate_session_sync(&token)?; if let Some(bid) = data.bot_id { user = user.with_current_bot(bid); } diff --git a/src/security/rbac_middleware.rs b/src/security/rbac_middleware.rs index 6946d088d..3ebfeb1b9 100644 --- a/src/security/rbac_middleware.rs +++ b/src/security/rbac_middleware.rs @@ -879,6 +879,11 @@ pub fn build_default_route_permissions() -> Vec { vec![ RoutePermission::new("/api/health", "GET", "").with_anonymous(true), RoutePermission::new("/api/version", "GET", "").with_anonymous(true), + RoutePermission::new("/api/product", "GET", "").with_anonymous(true), + RoutePermission::new("/api/i18n/**", "GET", "").with_anonymous(true), + RoutePermission::new("/api/auth", "GET", "").with_anonymous(true), + RoutePermission::new("/api/auth/login", "POST", "").with_anonymous(true), + RoutePermission::new("/api/auth/me", "GET", ""), RoutePermission::new("/api/users", "GET", "users.read") .with_roles(vec!["Admin".into(), "SuperAdmin".into()]), RoutePermission::new("/api/users", "POST", "users.create") @@ -908,6 +913,21 @@ pub fn build_default_route_permissions() -> Vec { RoutePermission::new("/api/bots/:id", "DELETE", "bots.delete"), RoutePermission::new("/api/bots/:id/**", "GET", "bots.read"), RoutePermission::new("/api/bots/:id/**", "PUT", "bots.update"), + // UI routes (HTMX endpoints) - allow authenticated users + RoutePermission::new("/api/ui/tasks/**", "GET", ""), + RoutePermission::new("/api/ui/tasks/**", "POST", ""), + RoutePermission::new("/api/ui/tasks/**", "PUT", ""), + RoutePermission::new("/api/ui/tasks/**", "PATCH", ""), + RoutePermission::new("/api/ui/tasks/**", "DELETE", ""), + RoutePermission::new("/api/ui/calendar/**", "GET", ""), + RoutePermission::new("/api/ui/drive/**", "GET", ""), + RoutePermission::new("/api/ui/mail/**", "GET", ""), + RoutePermission::new("/api/ui/monitoring/**", "GET", ""), + RoutePermission::new("/api/ui/analytics/**", "GET", "") + .with_roles(vec!["Admin".into(), "SuperAdmin".into(), "Moderator".into()]), + RoutePermission::new("/api/ui/admin/**", "GET", "") + .with_roles(vec!["Admin".into(), "SuperAdmin".into()]), + RoutePermission::new("/api/ui/**", "GET", ""), ] }