fix(security): POST WebAuthn login-begin; render JS errors as text
Two WebAuthn client/route hardening fixes: - GET /login/webauthn/begin wrote a challenge to the session on a safe method (login-flow DoS / CSRF surface). Make it POST with CSRF, matching the registration-begin endpoint; update webauthn.js and tests accordingly. - webauthn.js rendered dynamic error text (err.message, server error fields) via innerHTML — an XSS-prone sink. Add showAlert() that sets textContent; route all dynamic error messages through it. The trusted server-rendered credentials partial is still injected as markup. Refs: porchlight-cog, porchlight-t7y Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1571706d21
commit
c175633980
3 changed files with 28 additions and 13 deletions
|
|
@ -46,10 +46,12 @@ async def _setup_user_with_webauthn(
|
|||
|
||||
|
||||
async def test_webauthn_login_begin_returns_options(client: AsyncClient) -> None:
|
||||
"""Begin is now GET with no username — returns options with empty allowCredentials."""
|
||||
"""Begin is a POST (mutates session) with no username — returns options
|
||||
with empty allowCredentials."""
|
||||
await _setup_user_with_webauthn(client)
|
||||
|
||||
res = await client.get("/login/webauthn/begin")
|
||||
token = await get_csrf_token(client)
|
||||
res = await client.post("/login/webauthn/begin", headers={"X-CSRF-Token": token})
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert "publicKey" in data
|
||||
|
|
@ -59,7 +61,8 @@ async def test_webauthn_login_begin_returns_options(client: AsyncClient) -> None
|
|||
|
||||
|
||||
async def test_webauthn_login_begin_has_user_verification_preferred(client: AsyncClient) -> None:
|
||||
res = await client.get("/login/webauthn/begin")
|
||||
token = await get_csrf_token(client)
|
||||
res = await client.post("/login/webauthn/begin", headers={"X-CSRF-Token": token})
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert data["publicKey"]["userVerification"] == "preferred"
|
||||
|
|
@ -81,10 +84,9 @@ async def test_webauthn_login_complete_returns_json_redirect(client: AsyncClient
|
|||
_userid, _pk, _credential_id, _att = await _setup_user_with_webauthn(client)
|
||||
|
||||
# Begin to get state into session
|
||||
res1 = await client.get("/login/webauthn/begin")
|
||||
assert res1.status_code == 200
|
||||
|
||||
token = await get_csrf_token(client)
|
||||
res1 = await client.post("/login/webauthn/begin", headers={"X-CSRF-Token": token})
|
||||
assert res1.status_code == 200
|
||||
# We can't easily complete the full assertion without browser interaction,
|
||||
# but we verify the endpoint returns 400 JSON (not HTML) for bad assertions
|
||||
res2 = await client.post(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue