fix(session): write expires_at in UTC, not local time (audit 6c-2)

CreateSession wrote FormatDateTime(IncHour(Now,24)) = local, but RequireCSRF
and CleanupExpired compare against SQLite datetime('now') = UTC. On negative
UTC offsets the CSRF window fell short of the 24h auth window: between the two,
GETs authed fine but every write got 403 "Invalid CSRF token". Now
IncHour(NowUTC,24) on write + LExpires < NowUTC in Authenticate, so all three
readers are UTC-consistent. Last FormatDateTime(...,Now) in the backend —
closes §2.2 for real. NowUTC + DateUtils already in uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-20 05:25:37 +01:00
parent 71f45eb8e1
commit c2cc9c72e6
2 changed files with 14 additions and 2 deletions
+7 -2
View File
@@ -86,7 +86,8 @@ begin
LQ.Free;
end;
if (LExpires <> 0) and (LExpires < Now) then
// expires_at is now UTC (see CreateSession) — compare against UTC too.
if (LExpires <> 0) and (LExpires < NowUTC) then
begin
DeleteSessionByTokenHash(LTokenHash);
TJSONHelper.SendError(AResponse, 401, 'Session expired');
@@ -152,7 +153,11 @@ begin
ACSRFToken := RandomHex(32);
LTokenHash := SHA256Hex(AToken);
// YYYY-MM-DD HH:NN:SS, +24h, server local time (api.php uses date() = local)
LExpires := FormatDateTime('yyyy-mm-dd hh:nn:ss', IncHour(Now, 24));
// UTC like everywhere else — RequireCSRF + CleanupExpired compare against
// SQLite datetime('now') which IS UTC. Writing local here made the CSRF
// window fall short on negative-offset zones (§6c-2): "Invalid CSRF token"
// on writes while GETs still authed.
LExpires := FormatDateTime('yyyy-mm-dd hh:nn:ss', IncHour(NowUTC, 24));
DB.Lock;
try