discourse/lib/second_factor
Rafael dos Santos Silva 0c90e25e47
DEV: Split passkey and security key WebAuthn ceremonies for 2FA (#40817)
Passkeys used as 2FA (behind `allow_passkeys_for_2fa`) previously shared
a single WebAuthn ceremony with second-factor security keys on
`/session/2fa`: one merged credential allow-list, posted as the
`security_key` method, with `userVerification: "preferred"`. A single
ceremony cannot both require user verification for passkeys and accept
legacy non-UV security keys, so this splits them:

* New `passkey` value (4) in `UserSecondFactor.methods` carries the
ceremony intent on the wire. No rows ever store it; passkeys live in
`user_security_keys`.
* `DiscourseWebauthn.allowed_credentials` now returns
`allowed_credential_ids` (second-factor keys only, as before the
combined ceremony) plus a separate `passkey_allowed_credential_ids`.
* `authenticate_security_key` only accepts second-factor credentials
again; the new `authenticate_passkey` only accepts first-factor
credentials. A passkey assertion posted to the security key ceremony (or
vice versa) fails with an ownership error.
* The `/session/2fa` page shows distinct "Use passkey" (UV required) and
"Use security key" (UV discouraged) actions instead of one mixed button.
* `passkeys_for_2fa_enabled?` is renamed to
`passkeys_available_as_second_factor?` (old name kept as an alias) and
now ignores disabled passkey rows.

No behavior expansion: passkeys still only satisfy `/session/2fa`.

This is the first of three stacked PRs completing the
`allow_passkeys_for_2fa` rollout so passkeys count as valid 2FA
everywhere, including `enforce_second_factor`. The safe ordering is:
every login/recovery path must be able to *challenge* a passkey before
any path starts *trusting* passkey-only accounts as compliant.
2026-06-17 12:52:47 -03:00
..
actions FIX: URL encode usernames in grant admin redirect (#34664) 2025-10-13 09:49:23 +08:00
auth_manager.rb DEV: Split passkey and security key WebAuthn ceremonies for 2FA (#40817) 2026-06-17 12:52:47 -03:00
auth_manager_result.rb
bad_challenge.rb