mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-18 18:55:28 -04:00
fix(auth): tie remember-me cookie lifetime to TOKEN_TTL (#4472)
The persistent login cookie's max_age hardcoded 60 * 60 * 24 * 7, an independent copy of the session token lifetime that core/auth.py already defines once as TOKEN_TTL (and reports to the frontend via /api/auth/policy as session_days). If TOKEN_TTL changes, the cookie silently drifts: the browser keeps a cookie for a token whose lifetime no longer matches. Import TOKEN_TTL and use it for the cookie max_age so the session lifetime has a single source of truth. No behaviour change at the current value. Fixes #4471
This commit is contained in:
@@ -12,7 +12,7 @@ import re
|
||||
from pathlib import Path
|
||||
|
||||
from core.atomic_io import atomic_write_json, atomic_write_text
|
||||
from core.auth import AuthManager, RESERVED_USERNAMES, SetAdminResult
|
||||
from core.auth import AuthManager, RESERVED_USERNAMES, SetAdminResult, TOKEN_TTL
|
||||
from src.constants import DEEP_RESEARCH_DIR, MEMORY_FILE, PASSWORD_MIN_LENGTH, SKILLS_DIR
|
||||
from src.rate_limiter import RateLimiter
|
||||
from src.settings_scrub import scrub_settings
|
||||
@@ -161,7 +161,7 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
||||
path="/",
|
||||
)
|
||||
if body.remember:
|
||||
cookie_kwargs["max_age"] = 60 * 60 * 24 * 7 # 7 days
|
||||
cookie_kwargs["max_age"] = TOKEN_TTL
|
||||
response.set_cookie(**cookie_kwargs)
|
||||
return {"ok": True, "username": username}
|
||||
|
||||
|
||||
@@ -215,3 +215,58 @@ def test_setup_rejects_seven_char_password(tmp_path):
|
||||
asyncio.run(endpoint(body=body, request=request))
|
||||
|
||||
assert exc.value.status_code == 400
|
||||
|
||||
|
||||
# ── Login "remember me" cookie lifetime ────────────────────────────────
|
||||
|
||||
|
||||
class _CapturingResponse:
|
||||
"""Stand-in for fastapi.Response that records set_cookie kwargs."""
|
||||
|
||||
def __init__(self):
|
||||
self.cookie_kwargs = None
|
||||
|
||||
def set_cookie(self, **kwargs):
|
||||
self.cookie_kwargs = kwargs
|
||||
|
||||
|
||||
def _login_endpoint(auth_manager):
|
||||
sys.modules.pop("routes.auth_routes", None)
|
||||
_real_core_package()
|
||||
from routes.auth_routes import LoginRequest, setup_auth_routes
|
||||
|
||||
router = setup_auth_routes(auth_manager)
|
||||
for route in router.routes:
|
||||
if getattr(route, "path", None) == "/api/auth/login":
|
||||
return route.endpoint, LoginRequest
|
||||
raise AssertionError("login route not found")
|
||||
|
||||
|
||||
def test_remember_cookie_max_age_matches_token_ttl(tmp_path):
|
||||
auth_mod = _auth_module()
|
||||
mgr = _make_manager(tmp_path)
|
||||
mgr.create_user("alice", "alice-password", is_admin=False)
|
||||
endpoint, LoginRequest = _login_endpoint(mgr)
|
||||
request = SimpleNamespace(client=SimpleNamespace(host="127.0.0.1"))
|
||||
response = _CapturingResponse()
|
||||
body = LoginRequest(username="alice", password="alice-password", remember=True)
|
||||
|
||||
result = asyncio.run(endpoint(body=body, request=request, response=response))
|
||||
|
||||
assert result == {"ok": True, "username": "alice"}
|
||||
# The persistent cookie must outlive neither more nor less than the token.
|
||||
assert response.cookie_kwargs["max_age"] == auth_mod.TOKEN_TTL
|
||||
|
||||
|
||||
def test_no_remember_omits_cookie_max_age(tmp_path):
|
||||
mgr = _make_manager(tmp_path)
|
||||
mgr.create_user("bob", "bob-password", is_admin=False)
|
||||
endpoint, LoginRequest = _login_endpoint(mgr)
|
||||
request = SimpleNamespace(client=SimpleNamespace(host="127.0.0.1"))
|
||||
response = _CapturingResponse()
|
||||
body = LoginRequest(username="bob", password="bob-password", remember=False)
|
||||
|
||||
asyncio.run(endpoint(body=body, request=request, response=response))
|
||||
|
||||
# Without "remember", the cookie is a session cookie (no max_age).
|
||||
assert "max_age" not in response.cookie_kwargs
|
||||
|
||||
Reference in New Issue
Block a user