* docs(naming): drop scrum4me- prefix from doc filenames Rename 10 docs/scrum4me-*.md files to unprefixed kebab-case names. Update every internal link in docs/, CLAUDE.md, AGENTS.md, README.md. * docs(naming): lowercase API.md and MD3 filenames Rename docs/API.md → docs/api.md and docs/MD3_Color_Scheme_Documentation.md → docs/md3-color-scheme.md. Update all internal links across 7 files. * docs(naming): rename plan file to kebab-case ASCII Rename "docs/plans/Tweede Claude Agent — Planning Agent.md" → docs/plans/tweede-claude-agent-planning.md. No external links needed updating. * docs(naming): rename middleware.md to proxy.md (next 16) docs/patterns/middleware.md → docs/patterns/proxy.md following the Next.js 16 proxy.ts rename. Update link in CLAUDE.md. * docs(naming): polish CLAUDE.md doc-index after renames Fix doubled scrum4me-scrum4me-mcp repo references (cascade from prior sed) in CLAUDE.md, docs/architecture.md, backlog.md, agent-instruction-audit.md, and plans/ST-1109. Update 'Middleware' label to 'Proxy middleware' in patterns table.
4.1 KiB
Patroon: QR-pairing via unauth-SSE + pre-auth cookie
Het M10 QR-login-mechanisme is herbruikbaar voor elke feature die realtime- feedback wil tussen twee browsers/devices vóórdat de eindgebruiker is geauthenticeerd. De typische vorm:
"Apparaat A start een proces, krijgt een token. Apparaat B (bekend kanaal) bevestigt iets. Apparaat A wil dat realtime weten en daarna iets claimen."
Voorbeelden waar dit zou kunnen passen: device-pairing voor 2FA-setup, login- op-TV via QR, "claim deze export"-flow, account-overdracht tussen sessies.
Drie eindpunten
| Endpoint | Auth | Doel |
|---|---|---|
POST /api/.../start |
anon | maakt resource aan, retourneert mobile-secret in body, zet HttpOnly device-token cookie |
GET /api/.../stream/[id] |
cookie | SSE die op LISTEN/NOTIFY wacht op statusverandering |
POST /api/.../claim |
cookie | atomic state-transitie van "approved" → "consumed", wisselt cookie in voor échte sessie |
Plus een server-action-laag die door het tweede device wordt aangeroepen na het scannen / klikken van een link met fragment-secret.
Vier security-uitgangspunten
- Twee gescheiden geheimen — een voor het kanaal richting het tweede device (in QR-fragment), een voor het oorspronkelijke device (in HttpOnly cookie). Beide alleen als sha256-hash in DB.
- Geen secret in URL. Path en querystring lekken naar access logs,
reverse proxies, observability. Geheimen reizen alleen via:
- URL-fragment (
#…) — browsers sturen die niet naar de server - HttpOnly cookies — meestal niet gelogd, en alleen leesbaar door server
- POST-body — niet gelogd standaard
- URL-fragment (
- Atomic consume. Het claim-endpoint doet één UPDATE met een composite WHERE op alle invarianten (status, hash, expiry). PostgreSQL row-locking garandeert dat concurrent dubbele claims slechts één caller succes geven.
- Path-scoped cookie.
Path=/api/.../...zorgt dat de pre-auth cookie alleen naar pairing-routes gaat — niet naar de rest van de app.
Sjabloon-bestanden
Ga voor M10 specifiek? Kopieer en pas aan:
lib/auth/pairing.ts— secret/token generators + sha256 + timing-safe verify + expiry helperlib/auth/pair-cookie.ts— set/read/clear van Path-scoped HttpOnly cookieapp/api/auth/pair/start/route.ts— anon POST, rate-limited, sets cookieapp/api/auth/pair/stream/[id]/route.ts— SSE met cookie-auth, LISTEN op eigen channelapp/api/auth/pair/claim/route.ts— atomic update + iron-session schrijvenactions/pairing.ts— Server Actions voor het tweede deviceapp/(app)/m/pair/pair-confirmation.tsx— Client island dielocation.hashparseert
Voor het tweede device zit de auth meestal al in de bestaande (app)-layout
guard. De Client Component gebruikt window.location.hash (niet useSearchParams)
om het secret op te pikken.
TTL-richtlijn
Drie tijden in escalerende volgorde, alle korter dan de reguliere sessie:
- Pending (cookie + DB-rij) — kort genoeg dat een verloren cookie/QR weinig schade aanricht. M10: 5 minuten.
- Approved (na bevestiging) — kort genoeg dat een approved-maar-niet- geclaimde pairing niet eindeloos open blijft. M10: 5 minuten extra.
- Resulterende sessie — kort genoeg voor publieke apparaten, lang genoeg
voor een werkdag. M10: 8 uur, plus
paired: true-vlag voor toekomstige remote-revoke.
Wanneer dit patroon NIET gebruiken
- Wanneer beide kanten al ingelogd zijn — dan is een normaal API-call met bestaande sessie eenvoudiger.
- Wanneer realtime niet kritiek is — een korte poll (
setIntervalop een status-endpoint) is simpeler dan een SSE-stream. - Wanneer er één centraal apparaat is — gebruik dan een normale sessie; de twee-device-dans is alleen nodig om credentials van het ene apparaat naar het andere te brengen.
Referenties
- Volledige flow + threat-model:
docs/architecture.md§ QR-pairing flow - Endpoint-contract:
docs/api.md§ Auth — QR-pairing - LISTEN/NOTIFY-pattern:
app/api/realtime/solo/route.ts(M8 ST-802) — zelfde ReadableStream + heartbeat + hard-close + abort-cleanup, alleen ander channel