fix(auth): invalidate session cache on login

Stale sessions from a stolen device could be reused up to the cache
TTL after a legitimate user re-logs in, because login never cleared
the existing cache entry.

Changes:
- Add invalidate_by_user(user_id) to SessionCache protocol
- InMemorySessionCache maintains a user_id -> set[token] index to
  support O(1) invalidation of all sessions for a given user
- NoOpSessionCache stub updated for API compatibility
- auth_service.login() now returns the Session object alongside
  signed_token and expires_at
- login router calls session_cache.invalidate_by_user(session.id)
  immediately after successful authentication

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-03 20:51:51 +02:00
parent ae9313568e
commit c3cd1574dc
3 changed files with 43 additions and 6 deletions

View File

@@ -151,7 +151,7 @@ async def login(
session_duration_minutes: int,
session_secret: str,
session_repo: SessionRepository = default_session_repo,
) -> tuple[str, str]:
) -> tuple[str, str, Session]:
"""Verify *password*, create a new session, and sign the token.
Args:
@@ -161,7 +161,8 @@ async def login(
session_secret: Secret used to sign the session token.
Returns:
A tuple of the signed session token and its expiry timestamp.
A tuple of the signed session token, its expiry timestamp,
and the newly created session object.
Raises:
ValueError: If the password is incorrect or no password hash is stored.
@@ -185,7 +186,7 @@ async def login(
)
signed_token = sign_session_token(session.token, session_secret)
log.info("bangui_login_success", session_id=session.id)
return signed_token, session.expires_at
return signed_token, session.expires_at, session
async def validate_session(