From c512534bfa7a914bb1635b851a45d914fefdf2f3 Mon Sep 17 00:00:00 2001 From: Diocrafts Date: Sun, 12 Apr 2026 01:38:19 +0200 Subject: [PATCH] fix: session_expired after login on HTTP deployments (#241) Three changes to fix the immediate-logout issue reported by multiple Docker users: 1. Add explicit `credentials: 'same-origin'` to the login fetch call. This was the only fetch in the entire codebase missing it. While modern browsers default to 'same-origin', some privacy configs or older engines may default to 'omit', silently dropping Set-Cookie headers from the login response. 2. Post-login cookie verification: after a successful login, the frontend now checks that the CSRF cookie (non-HttpOnly, readable by JS) was actually stored before redirecting. If the browser rejected the cookies, a clear error message is shown explaining the OXICLOUD_COOKIE_SECURE / HTTP mismatch. 3. Server-side diagnostic: the login handler now warns in logs when Secure cookies are set on a request that didn't arrive via HTTPS (no X-Forwarded-Proto: https header), pointing admins to the OXICLOUD_COOKIE_SECURE=false fix. Root cause: users who set OXICLOUD_BASE_URL=https://... (or have OXICLOUD_COOKIE_SECURE=true) but access via plain HTTP get cookies with the Secure flag, which browsers silently reject over HTTP. --- src/interfaces/api/cookie_auth.rs | 4 ++++ src/interfaces/api/handlers/auth_handler.rs | 20 ++++++++++++++++++++ static/js/features/auth/auth.js | 17 ++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/interfaces/api/cookie_auth.rs b/src/interfaces/api/cookie_auth.rs index f50353fe..63fd9d66 100644 --- a/src/interfaces/api/cookie_auth.rs +++ b/src/interfaces/api/cookie_auth.rs @@ -34,6 +34,10 @@ pub const CSRF_HEADER: &str = "x-csrf-token"; /// 4. **Default: `false`** for compatibility with HTTP deployments /// (Docker, local development). Set `OXICLOUD_COOKIE_SECURE=true` /// explicitly for production HTTPS environments. +pub fn is_cookie_secure() -> bool { + cookie_secure() +} + fn cookie_secure() -> bool { if let Ok(v) = std::env::var("OXICLOUD_COOKIE_SECURE") { let secure = v == "true" || v == "1"; diff --git a/src/interfaces/api/handlers/auth_handler.rs b/src/interfaces/api/handlers/auth_handler.rs index be6bd5d4..25a304a9 100644 --- a/src/interfaces/api/handlers/auth_handler.rs +++ b/src/interfaces/api/handlers/auth_handler.rs @@ -121,6 +121,7 @@ async fn register( async fn login( State(state): State>, + headers: HeaderMap, Json(dto): Json, ) -> Result { // Add detailed logging for debugging @@ -204,6 +205,25 @@ async fn login( state.core.config.auth.refresh_token_expiry_secs, ); cookie_auth::append_csrf_cookie(response.headers_mut(), auth_response.expires_in); + + // Diagnostic: warn when Secure cookies are set but the request + // arrived over plain HTTP — the browser will reject them (#241). + if cookie_auth::is_cookie_secure() { + let is_tls = headers + .get("x-forwarded-proto") + .and_then(|v| v.to_str().ok()) + .is_some_and(|p| p.eq_ignore_ascii_case("https")); + if !is_tls { + tracing::warn!( + "Login for '{}': Secure cookies are enabled but the request \ + does not appear to be over HTTPS (no X-Forwarded-Proto: https). \ + The browser may reject the cookies. Set OXICLOUD_COOKIE_SECURE=false \ + in .env if you access OxiCloud via plain HTTP.", + dto.username, + ); + } + } + Ok(response) } Err(err) => { diff --git a/static/js/features/auth/auth.js b/static/js/features/auth/auth.js index a4f71e39..947dd488 100644 --- a/static/js/features/auth/auth.js +++ b/static/js/features/auth/auth.js @@ -898,7 +898,21 @@ if (isLoginPage && loginForm) { localStorage.setItem(USER_DATA_KEY, JSON.stringify(data.user)); } - // Redirect to main app + // Redirect to main app — but first verify the browser accepted + // the auth cookies. The CSRF cookie (oxicloud_csrf) is non-HttpOnly + // so JS can read it. If it's missing the browser rejected the + // Set-Cookie (usually because of Secure flag over plain HTTP). + const csrfStored = document.cookie.split('; ').some(c => c.startsWith('oxicloud_csrf=')); + if (!csrfStored) { + console.error('Auth cookies were NOT stored by the browser. ' + + 'This usually means OXICLOUD_COOKIE_SECURE=true (or OXICLOUD_BASE_URL=https://...) ' + + 'is set but you are accessing via plain HTTP.'); + loginError.textContent = 'Login succeeded but the browser rejected the session cookie. ' + + 'If you are accessing via HTTP, set OXICLOUD_COOKIE_SECURE=false in your .env file ' + + 'or access via HTTPS through a reverse proxy.'; + loginError.style.display = 'block'; + return; + } redirectToMainApp(); } catch (error) { loginError.textContent = error.message || 'Error logging in'; @@ -1030,6 +1044,7 @@ async function login(username, password) { const response = await fetch(LOGIN_ENDPOINT, { method: 'POST', + credentials: 'same-origin', headers: { 'Content-Type': 'application/json', ...getCsrfHeaders()