fix(security): require CSRF-protected POST to consume a registration link

GET /register/{token} consumed the magic-link token and created a session, so
a side-effecting state change happened on a safe method — link prefetchers,
email scanners, or a cross-site GET could trigger account setup/login.

Split the flow: GET validates the token (without consuming) and renders a
confirmation form; POST /register/{token} consumes the token, runs the
existing checks, and establishes the session. The POST carries a CSRF token
and the session is reset on login as for other auth paths.

Refs: porchlight-9k0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Johan Lundberg 2026-06-05 13:40:30 +02:00
parent efb265a68b
commit baef5e0e2e
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
6 changed files with 96 additions and 10 deletions

View file

@ -9,7 +9,9 @@ test.describe('Full user journey', () => {
expect(fixtures.register_token).toBeTruthy();
// ---- Step 1: Register via magic link ----
// GET shows a confirmation page; submitting it (POST) consumes the token.
await page.goto(`/register/${fixtures.register_token}`);
await page.click('button[type="submit"]');
// Should redirect to /manage/credentials?setup=1
await page.waitForURL('**/manage/credentials?setup=1', { timeout: 5000 });