From 2daee68d20e4f1be71e0476c2608d75f9912a365 Mon Sep 17 00:00:00 2001 From: BillionClaw Date: Tue, 17 Mar 2026 06:14:02 +0800 Subject: [PATCH] fix(auth): resolve CSP blocking and session refresh loop Fix two issues causing login loop after successful admin setup: 1. CSP blocking inline styles: The frontend JavaScript dynamically sets inline styles (e.g., element.style.display = 'none') for UI state management. The CSP header only allowed 'self' for style-src, blocking these dynamic styles. Added 'unsafe-inline' to style-src directive. 2. Session refresh 401 errors: The cookie Secure flag defaulted to true when OXICLOUD_BASE_URL was not set, causing cookies to not be sent over HTTP in Docker deployments. Changed the default to false when the base URL is not explicitly set to HTTPS, with clear logging to guide users to set OXICLOUD_COOKIE_SECURE=true for production. Fixes #203 --- src/interfaces/api/cookie_auth.rs | 35 +++++++++++++++++++------------ src/main.rs | 9 +++++--- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/interfaces/api/cookie_auth.rs b/src/interfaces/api/cookie_auth.rs index c52cfaca..f50353fe 100755 --- a/src/interfaces/api/cookie_auth.rs +++ b/src/interfaces/api/cookie_auth.rs @@ -30,8 +30,10 @@ pub const CSRF_HEADER: &str = "x-csrf-token"; /// Resolution order: /// 1. `OXICLOUD_COOKIE_SECURE=true|false` — explicit override. /// 2. `OXICLOUD_BASE_URL` starts with `https` → `true`. -/// 3. **Default: `true`** (safe-by-default). Set `OXICLOUD_COOKIE_SECURE=false` -/// explicitly for plain-HTTP development environments. +/// 3. `OXICLOUD_BASE_URL` starts with `http` → `false`. +/// 4. **Default: `false`** for compatibility with HTTP deployments +/// (Docker, local development). Set `OXICLOUD_COOKIE_SECURE=true` +/// explicitly for production HTTPS environments. fn cookie_secure() -> bool { if let Ok(v) = std::env::var("OXICLOUD_COOKIE_SECURE") { let secure = v == "true" || v == "1"; @@ -44,18 +46,25 @@ fn cookie_secure() -> bool { } return secure; } - // Auto-detect from base URL, defaulting to secure when unset - let secure = std::env::var("OXICLOUD_BASE_URL") - .map(|u| u.starts_with("https")) - .unwrap_or(true); - if !secure { - tracing::warn!( - "OXICLOUD_BASE_URL does not start with https — \ - cookie Secure flag is OFF. Set OXICLOUD_COOKIE_SECURE=true \ - to override if your proxy terminates TLS." - ); + // Auto-detect from base URL, defaulting to insecure for compatibility + match std::env::var("OXICLOUD_BASE_URL") { + Ok(url) if url.starts_with("https") => true, + Ok(url) if url.starts_with("http://") => { + tracing::info!( + "OXICLOUD_BASE_URL is HTTP — cookie Secure flag is OFF. \ + Set OXICLOUD_COOKIE_SECURE=true to override if your proxy terminates TLS." + ); + false + } + _ => { + // Default to false for compatibility with HTTP deployments + tracing::info!( + "OXICLOUD_BASE_URL not set — defaulting to non-secure cookies \ + for HTTP compatibility. Set OXICLOUD_COOKIE_SECURE=true for HTTPS deployments." + ); + false + } } - secure } /// Build a `Set-Cookie` header value. diff --git a/src/main.rs b/src/main.rs index c0043ced..a7d4c6fb 100755 --- a/src/main.rs +++ b/src/main.rs @@ -416,15 +416,18 @@ async fn main() -> Result<(), Box> { app = app .layer(SetResponseHeaderLayer::overriding( HeaderName::from_static("content-security-policy"), - // All inline scripts and styles have been migrated to external - // files, so 'unsafe-inline' is no longer needed. + // Note: 'unsafe-inline' is required for style-src because the + // frontend JavaScript dynamically sets inline styles (e.g., + // element.style.display = 'none'). This is a common pattern + // for UI state management and cannot be easily migrated to + // external CSS classes without significant refactoring. // frame-src: '*' only matches network schemes, so 'blob:' must be // listed explicitly for inline PDF/document viewers. // media-src: needed for blob: video/audio playback. HeaderValue::from_static( "default-src 'self'; \ script-src 'self'; \ - style-src 'self'; \ + style-src 'self' 'unsafe-inline'; \ img-src 'self' data: blob:; \ media-src 'self' blob:; \ connect-src 'self'; \