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:
parent
efb265a68b
commit
baef5e0e2e
6 changed files with 96 additions and 10 deletions
|
|
@ -48,11 +48,15 @@ async def _login_user_without_password(client: AsyncClient) -> str:
|
|||
)
|
||||
await user_repo.create(user)
|
||||
|
||||
# Simulate session login via magic link
|
||||
# Simulate session login via magic link (GET shows the form, POST consumes)
|
||||
token = await get_csrf_token(client)
|
||||
magic_link_service = app.state.magic_link_service
|
||||
link = await magic_link_service.create(username="newuser", created_by="admin", note="test")
|
||||
await client.get(f"/register/{link.token}", follow_redirects=False)
|
||||
await client.post(
|
||||
f"/register/{link.token}",
|
||||
headers={"X-CSRF-Token": token},
|
||||
follow_redirects=False,
|
||||
)
|
||||
# Re-fetch CSRF token after session change
|
||||
token = await get_csrf_token(client)
|
||||
return token
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue