Builds out the Settings → Security tab (previously a "coming soon"
placeholder) and the backend behind it. Still single-user; multi-user
and SSO remain Phases 3-4.
Backend:
- New `sessions` table (id, user_id, user_agent, ip, created_at,
last_seen_at) and `login_events` table (user_id, username, ip,
user_agent, success, created_at).
- Login and setup now mint a session row and embed its id as a `sid`
claim in the JWT. The `authenticate` hook validates that the session
still exists (and bumps last_seen_at), so revoking a session genuinely
invalidates its token instead of relying on the JWT signature alone.
Tokens minted before sessions existed have no `sid` and stay valid
until expiry, for backward compatibility.
- Every login attempt (success and failure) is recorded in login_events
for the audit trail.
- New endpoints: PUT /api/auth/password (verifies current via bcrypt,
hashes new at cost 12, revokes all *other* sessions on success),
GET /api/auth/sessions, DELETE /api/auth/sessions/:id (can't revoke
the current one), POST /api/auth/logout (revokes current session),
GET /api/auth/login-events?limit.
- AuthContext.logout() now calls POST /api/auth/logout best-effort so
signing out revokes the server session, not just the local token.
Frontend:
- SecuritySection: change-password form (current/new/confirm with
show/hide and client-side validation), active-sessions list (device
description from user-agent, IP, last-seen relative time, per-session
"Sign out" for non-current sessions), and a recent login-activity feed
(success/failure dot, user, IP, relative time).
- api.ts: changePassword/listSessions/revokeSession/logout/
listLoginEvents + AuthSession/LoginEvent types.
Verified end-to-end against a throwaway backend instance: session
creation, second-device session, failed-login logging, cross-session
revocation invalidating the revoked token, password change keeping the
current session alive while revoking others, and logout invalidating the
current session. Frontend + backend both type-check clean.
Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>