Gebruikers- & sessiebeheer: registratie, actieve-sessie-overzicht & force-logout #52

Merged
janpeter merged 8 commits from claude/lucid-bell-88117e into main 2026-06-18 18:03:03 +02:00
Owner

Gebruikers- & sessiebeheer

Voegt gebruikers- en sessiebeheer toe (Story ST-014, PBI-10, sprint S-2026-06-18-1).

Wat

  • Registratiepagina (/users/new) — achter login; elke ingelogde gebruiker maakt accounts aan (geen rollen).
  • Menu "Gebruikers" (/users) — overzicht van actieve sessies (gebruikersnaam, inlog-tijdstip, laatste activiteit, apparaat/IP) met per sessie beëindigen (geforceerd uitloggen); eigen sessie beëindigen = direct uitloggen.
  • Wachtwoord wijzigen blijft ongewijzigd in /settings (non-goal).

Hoe

  • Nieuw Prisma Session-model + migratie; iron-session krijgt een sessionId in de cookie (server-side sessie-tracking, want iron-session was stateless).
  • Server Actions voor alles (login/logout, registratie, beëindigen, heartbeat) — ingebouwde CSRF-bescherming, cookie-writes alleen in de action-fase. De (app)-layout blijft read-only (Server Components mogen in Next 16 geen cookies schrijven).
  • Heartbeat (SessionHeartbeatheartbeatAction) houdt last_seen_at vers, dwingt force-logout af (≤60s) en upgradet legacy-cookies zonder geforceerde re-login.
  • Autorisatie van nieuwe gevoelige surfaces zit op page/action-niveau (requireSession()), onafhankelijk van de proxy.
  • Testbare DI-core/'use server'-wrapper-splitsing (*-core.ts + *.ts), conform immich-people.

Verificatie

  • npm run build groen (TypeScript + ESLint schoon).
  • npm test: ~40 nieuwe unit-tests groen (sessions, auth, heartbeat, users, MainNav) via DI-fakes. (De resterende suite-failures zijn pre-existing DATABASE_URL-tests die in CI/dev mét DB draaien.)
  • Reviews: interne adversariële multi-agent review + 2× externe Codex-review op het spec → GO, plus Codex code review op de diff → GO (1 minor opgelost: in-flight guard in de heartbeat). Codex bevestigde o.a. dat prisma migrate diff exact overeenkomt met de gecommitte migration.sql.

Deploy / opvolging

  • Migratie wordt toegepast via de bestaande migrate-service (prisma migrate deploy).
  • IDEA-132 (apart, niet-blokkerend): de repo heeft twee proxy-bestanden (proxy.ts met CSRF/CSP vs src/proxy.ts met alleen userId-check) — consolideren naar één. Deze feature is daar bewust onafhankelijk van gemaakt.

Spec: docs/superpowers/specs/2026-06-18-user-session-management-design.md

🤖 Generated with Claude Code

## Gebruikers- & sessiebeheer Voegt gebruikers- en sessiebeheer toe (Story **ST-014**, **PBI-10**, sprint `S-2026-06-18-1`). ### Wat - **Registratiepagina** (`/users/new`) — achter login; elke ingelogde gebruiker maakt accounts aan (geen rollen). - **Menu "Gebruikers"** (`/users`) — overzicht van **actieve sessies** (gebruikersnaam, inlog-tijdstip, laatste activiteit, apparaat/IP) met per sessie **beëindigen** (geforceerd uitloggen); eigen sessie beëindigen = direct uitloggen. - **Wachtwoord wijzigen** blijft ongewijzigd in `/settings` (non-goal). ### Hoe - Nieuw Prisma **`Session`-model** + migratie; iron-session krijgt een `sessionId` in de cookie (server-side sessie-tracking, want iron-session was stateless). - **Server Actions** voor alles (login/logout, registratie, beëindigen, **heartbeat**) — ingebouwde CSRF-bescherming, cookie-writes alleen in de action-fase. De `(app)`-layout blijft **read-only** (Server Components mogen in Next 16 geen cookies schrijven). - **Heartbeat** (`SessionHeartbeat` → `heartbeatAction`) houdt `last_seen_at` vers, dwingt force-logout af (≤60s) en upgradet legacy-cookies zonder geforceerde re-login. - Autorisatie van nieuwe gevoelige surfaces zit op **page/action-niveau** (`requireSession()`), onafhankelijk van de proxy. - Testbare **DI-core/`'use server'`-wrapper**-splitsing (`*-core.ts` + `*.ts`), conform `immich-people`. ### Verificatie - `npm run build` groen (TypeScript + ESLint schoon). - `npm test`: ~40 nieuwe unit-tests groen (sessions, auth, heartbeat, users, MainNav) via DI-fakes. (De resterende suite-failures zijn pre-existing `DATABASE_URL`-tests die in CI/dev mét DB draaien.) - **Reviews:** interne adversariële multi-agent review + **2× externe Codex-review op het spec → GO**, plus **Codex code review op de diff → GO** (1 minor opgelost: in-flight guard in de heartbeat). Codex bevestigde o.a. dat `prisma migrate diff` exact overeenkomt met de gecommitte `migration.sql`. ### Deploy / opvolging - Migratie wordt toegepast via de bestaande `migrate`-service (`prisma migrate deploy`). - **IDEA-132** (apart, niet-blokkerend): de repo heeft twee proxy-bestanden (`proxy.ts` met CSRF/CSP vs `src/proxy.ts` met alleen `userId`-check) — consolideren naar één. Deze feature is daar bewust onafhankelijk van gemaakt. Spec: `docs/superpowers/specs/2026-06-18-user-session-management-design.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Brainstorm-ontwerp + interne adversariële multi-agent review + externe
Codex-review (2 rondes → OORDEEL: GO). Server-side sessie-tracking,
registratiepagina achter login, en 'Gebruikers'-overzicht van actieve
sessies met force-logout. Proxy-consolidatie apart getrackt als IDEA-132.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prisma Session-tabel (+ AppUser.sessions relatie) en migratie. Nieuwe
src/lib/sessions.ts met pure helpers (isSessionActive, parseRequestMeta met
x-real-ip-voorkeur, formatRelativeTime) en DI-DB-functies (create/touch via
updateMany+count/delete/listActive met narrow select/prune op last_seen_at).
SessionData krijgt optioneel sessionId. 16 unit-tests groen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
loginAction maakt nu een Session-rij (met user-agent/IP), zet sessionId in de
cookie, ruimt de oude rij op bij her-login en rolt terug als save() faalt.
logoutAction verwijdert de rij best-effort vóór destroy(). Testbare kern in
auth-core.ts (immich-people-patroon); auth.ts blijft dunne 'use server'-wrapper.
7 unit-tests groen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
heartbeatAction (DI-core) houdt last_seen_at vers, logt uit bij een verdwenen
rij (force-logout) en upgradet legacy-cookies zonder re-login. Client-component
pingt bij mount/elke 60s/visibilitychange en redirect/refresht op het resultaat.
Read-only (app)-layout mount het bij een actieve sessie. 5 unit-tests groen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
registerUserAction (zod: username 3-100 + patroon, wachtwoord >=8 + bevestiging;
uniciteit-precheck + P2002-vangnet; hashPassword + create). terminateSessionAction
(eigen sessie -> delete + destroy + redirect /login; andere -> delete + revalidate).
Testbare kern in users-core.ts; 11 unit-tests groen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/users toont actieve sessies (gebruiker + jij-badge, apparaat/IP, ingelogd,
laatste activiteit) met per rij een beëindigen-knop (window.confirm). /users/new
biedt het registratieformulier (useActionState). 'Gebruikers'-item toegevoegd aan
MainNav (vóór Instellingen); nav-test uitgebreid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
De form-wrapper negeert het core-resultaat (alleen voor tests) zodat het type
klopt met <form action>. Fixt de type-check in next build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(sessions): in-flight guard in SessionHeartbeat (codex review)
All checks were successful
CI / docker-build (pull_request) Successful in 2m44s
f3477d3ac4
Voorkomt dat overlappende heartbeats (mount/interval/visibility) een legacy-cookie
twee keer upgraden en kort dubbele Session-rijen achterlaten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/Media-Organizer!52
No description provided.