Harden session cookie security with configurable cookie flags
This commit is contained in:
@@ -4,6 +4,8 @@ Follows pydantic-settings patterns: all values are prefixed with BANGUI_
|
||||
and validated at startup via the Settings singleton.
|
||||
"""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
@@ -47,6 +49,25 @@ class Settings(BaseSettings):
|
||||
default="UTC",
|
||||
description="IANA timezone name used when displaying timestamps in the UI.",
|
||||
)
|
||||
session_cookie_httponly: bool = Field(
|
||||
default=True,
|
||||
description=(
|
||||
"Mark the session cookie as HttpOnly so browser scripts cannot access it."
|
||||
),
|
||||
)
|
||||
session_cookie_samesite: Literal["lax", "strict", "none"] = Field(
|
||||
default="lax",
|
||||
description=(
|
||||
"SameSite policy for the session cookie. "
|
||||
"Use 'lax', 'strict', or 'none' depending on deployment requirements."
|
||||
),
|
||||
)
|
||||
session_cookie_secure: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"Set the session cookie Secure flag when the backend is served over HTTPS."
|
||||
),
|
||||
)
|
||||
cors_allowed_origins: str | list[str] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
|
||||
@@ -70,9 +70,9 @@ async def login(
|
||||
response.set_cookie(
|
||||
key=_COOKIE_NAME,
|
||||
value=signed_token,
|
||||
httponly=True,
|
||||
samesite="lax",
|
||||
secure=False, # Set to True in production behind HTTPS
|
||||
httponly=settings.session_cookie_httponly,
|
||||
samesite=settings.session_cookie_samesite,
|
||||
secure=settings.session_cookie_secure,
|
||||
max_age=settings.session_duration_minutes * 60,
|
||||
)
|
||||
return LoginResponse(token=signed_token, expires_at=session.expires_at)
|
||||
|
||||
@@ -66,6 +66,22 @@ class TestLogin:
|
||||
assert response.status_code == 200
|
||||
assert "bangui_session" in response.cookies
|
||||
assert "." in response.cookies["bangui_session"]
|
||||
set_cookie = response.headers.get("set-cookie", "")
|
||||
assert "HttpOnly" in set_cookie
|
||||
assert "SameSite=lax" in set_cookie
|
||||
|
||||
async def test_login_sets_secure_cookie_when_enabled(
|
||||
self, client: AsyncClient
|
||||
) -> None:
|
||||
"""Login sets the Secure flag when session cookies are configured for HTTPS."""
|
||||
client._transport.app.state.settings.session_cookie_secure = True
|
||||
await _do_setup(client)
|
||||
response = await client.post(
|
||||
"/api/auth/login", json={"password": "mysecretpass1"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
set_cookie = response.headers.get("set-cookie", "")
|
||||
assert "Secure" in set_cookie
|
||||
|
||||
async def test_login_fails_with_wrong_password(
|
||||
self, client: AsyncClient
|
||||
|
||||
Reference in New Issue
Block a user