mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-28 15:45:22 -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 pathlib import Path
|
||||||
|
|
||||||
from core.atomic_io import atomic_write_json, atomic_write_text
|
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.constants import DEEP_RESEARCH_DIR, MEMORY_FILE, PASSWORD_MIN_LENGTH, SKILLS_DIR
|
||||||
from src.rate_limiter import RateLimiter
|
from src.rate_limiter import RateLimiter
|
||||||
from src.settings_scrub import scrub_settings
|
from src.settings_scrub import scrub_settings
|
||||||
@@ -161,7 +161,7 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||||||
path="/",
|
path="/",
|
||||||
)
|
)
|
||||||
if body.remember:
|
if body.remember:
|
||||||
cookie_kwargs["max_age"] = 60 * 60 * 24 * 7 # 7 days
|
cookie_kwargs["max_age"] = TOKEN_TTL
|
||||||
response.set_cookie(**cookie_kwargs)
|
response.set_cookie(**cookie_kwargs)
|
||||||
return {"ok": True, "username": username}
|
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))
|
asyncio.run(endpoint(body=body, request=request))
|
||||||
|
|
||||||
assert exc.value.status_code == 400
|
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