From b46295d0cf1baee452f7a12876246d07cdb98d56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:12:59 +0000 Subject: [PATCH] fix(auth): support session cookie authentication in API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update AuthenticatedUser extractor to check session cookies first - Add authenticate_via_session() helper function - Session cookies now work for all API write operations - Change SameSite to Strict for better CSRF protection - Add BREWLOG_SECURE_COOKIES env var to enable secure flag in production This fixes the bug where authenticated frontend users got 401 errors when submitting forms. API endpoints now accept both Bearer tokens and session cookies for authentication. All 65 tests pass (8 unit + 42 server + 15 CLI) ✅ Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com> --- src/server/auth.rs | 33 ++++++++++++++++++++++++++++++++- src/server/routes/auth.rs | 9 +++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/server/auth.rs b/src/server/auth.rs index 9c366a8..1e0995d 100644 --- a/src/server/auth.rs +++ b/src/server/auth.rs @@ -5,11 +5,14 @@ use axum::{ middleware::Next, response::{IntoResponse, Response}, }; +use tower_cookies::Cookies; use crate::domain::users::User; use crate::infrastructure::auth::hash_token; use crate::server::server::AppState; +const SESSION_COOKIE_NAME: &str = "brewlog_session"; + /// Extension type to carry authenticated user through request handlers #[derive(Clone)] pub struct AuthenticatedUser(pub User); @@ -27,7 +30,14 @@ impl FromRequestParts for AuthenticatedUser { return Ok(user.clone()); } - // Otherwise, extract from Authorization header directly + // Try to authenticate via session cookie first + if let Ok(cookies) = Cookies::from_request_parts(parts, state).await { + if let Some(user) = authenticate_via_session(state, &cookies).await { + return Ok(AuthenticatedUser(user)); + } + } + + // Fall back to Bearer token authentication let auth_header = parts .headers .get(header::AUTHORIZATION) @@ -73,6 +83,27 @@ impl FromRequestParts for AuthenticatedUser { } } +/// Authenticate via session cookie +async fn authenticate_via_session(state: &AppState, cookies: &Cookies) -> Option { + let cookie = cookies.get(SESSION_COOKIE_NAME)?; + let session_token = cookie.value(); + let session_token_hash = hash_token(session_token); + + // Check if session exists and is valid + let session = state + .session_repo + .get_by_token_hash(&session_token_hash) + .await + .ok()?; + + if session.is_expired() { + return None; + } + + // Get the user + state.user_repo.get(session.user_id).await.ok() +} + /// Middleware that optionally extracts authentication from the request /// Sets AuthenticatedUser extension if valid token is found pub async fn optional_auth_middleware( diff --git a/src/server/routes/auth.rs b/src/server/routes/auth.rs index 75eba66..235e868 100644 --- a/src/server/routes/auth.rs +++ b/src/server/routes/auth.rs @@ -92,8 +92,13 @@ pub(crate) async fn login_submit( let mut cookie = Cookie::new(SESSION_COOKIE_NAME, session_token); cookie.set_path("/"); cookie.set_http_only(true); - cookie.set_same_site(tower_cookies::cookie::SameSite::Lax); - // In production, set secure flag: cookie.set_secure(true); + cookie.set_same_site(tower_cookies::cookie::SameSite::Strict); + + // Enable secure flag if BREWLOG_SECURE_COOKIES is set to "true" + // This should be enabled in production when serving over HTTPS + if std::env::var("BREWLOG_SECURE_COOKIES").unwrap_or_default() == "true" { + cookie.set_secure(true); + } cookies.add(cookie);