fix(auth): a cookie that lands after sign-out no longer signs the browser back in

A renewal answers with the session its request started with. Each session now
carries an id; signing out, or signing in as someone else, revokes it
(revoked_sessions, migration 0120), and a cookie carrying a revoked id is
refused. A cookie from before session ids gets one drawn from the cookie
itself. The race after log-out-everywhere fails closed and stays.
This commit is contained in:
2026-11-14 17:56:36 +01:00
parent d265c84c8b
commit 02714e4986
7 changed files with 164 additions and 8 deletions
+12
View File
@@ -190,6 +190,18 @@ class AuthToken(Base, TimestampMixin):
used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
class RevokedSession(Base):
"""The id of a browser session that signed out, or that a sign-in as someone else replaced
(`auth.revoke_session`). A request still in flight then can answer with that session's cookie,
re-signed by its renewal, after the sign-out cleared it; a cookie whose id is here is refused. A
row outlives every cookie that could carry its id, then `revoke_session` drops it."""
__tablename__ = "revoked_sessions"
sid: Mapped[str] = mapped_column(String(32), primary_key=True)
revoked_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
class Invite(Base, CanonicalEmailMixin):
"""Access-request / whitelist row. Source of truth for who may sign in; the env
ALLOWED_EMAILS/ADMIN_EMAILS act only as a bootstrap fallback (see auth.is_allowed)."""