T13.1: idle timeout + absolute ceiling (D18)
Sessions now slide on activity (AUTH_IDLE_MINUTES, default 30) capped by a hard ceiling from original sign-in (AUTH_SESSION_HOURS, meaning changed, default 12 -> proposed 8). login_at carried across reissues so the ceiling survives refreshes; pre-D18 tokens with no login_at fall back to iat. Refresh is throttled (~IDLE_MINUTES/3) so the cookie isn't rewritten on every request. Wired into auth_gate (server/app.py) - no DB hit, reads only the already-validated claims. Verified: 7 unit-level checks (fresh-token expiry, past-ceiling refusal, throttling, mid-session extension, legacy-token fallback both live and expired, idle cutoff itself) all pass, plus the full 27-check smoke suite still passes end to end through the new middleware path.
This commit is contained in:
@@ -103,12 +103,23 @@ def _csrf_ok(request: Request) -> bool:
|
||||
async def auth_gate(request: Request, call_next):
|
||||
path = request.url.path
|
||||
method = request.method
|
||||
claims = None
|
||||
if method != "OPTIONS" and auth._needs_auth(path):
|
||||
if not auth.is_request_authenticated(request):
|
||||
claims = auth.is_request_authenticated(request)
|
||||
if not claims:
|
||||
return JSONResponse(status_code=401, content={"detail": "Not authenticated"})
|
||||
if method in _UNSAFE_METHODS and path.startswith("/api/") and not _csrf_ok(request):
|
||||
return JSONResponse(status_code=403, content={"detail": "Cross-origin request rejected"})
|
||||
return await call_next(request)
|
||||
response = await call_next(request)
|
||||
# D18: slide the session forward on activity, capped by its absolute
|
||||
# ceiling. Reads only the already-validated claims - no DB hit here, and
|
||||
# the separate is_active/token_version check in get_current_user still
|
||||
# runs on its own for every request regardless of whether this refreshes.
|
||||
if claims is not None:
|
||||
refreshed = auth.maybe_refresh_token(claims)
|
||||
if refreshed:
|
||||
auth.set_session_cookie(response, request, refreshed)
|
||||
return response
|
||||
|
||||
|
||||
def gen_id(prefix: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user