Add a credential_id_hash column containing sha256-hex of the raw WebAuthn credential ID and enforce it with a unique index across all three backends.
Check credential_id_hash before saving to return a clean duplicate-passkey error in the common case, and map database unique violations to the same error to close the TOCTOU window.
Keep PRF keyset/status unit coverage exhaustive so a refactor cannot accidentally report partial PRF state as enabled.
Consume passkey challenges before parsing or user lookup so every submission carrying a valid token is single-use, then delay user-scoped event attribution and account-state checks until the assertion is cryptographically bound to a registered credential.
Return the generic passkey auth failure for malformed assertions, missing or corrupt challenges, unverified email state, and failed WebAuthn verification; do not send verification-reminder email from this unauthenticated flow.
Add WebAuthn DOMAIN compatibility gates, stricter PRF option gating, key-rotation race checks, cascade deletes, login rate limits on authenticated passkey endpoints, and typed credential IDs on delete.
- Wire key rotation to re-encrypt each passkey's PRF keys, with a superset check in validate_keydata so a passkey can't be left stale.
- Persist signature-counter updates and rotated PRF keys with column-scoped writes, avoiding a broad full-row credential update.
- Compute prfStatus as the full WebAuthnPrfStatus instead of a 1/0 placeholder.
- Move the login challenge from an in-memory cache to a DB-backed table with a scheduled cleanup job.
- Use webauthn-rs's discoverable-credential API instead of the JSON state-injection workaround.
- Make challenge consumption single-use, rate-limit assertion-options, and return a single generic auth-failure message.
- Honor SSO_ONLY at every passkey entry point: login grant, enrollment, refresh, and the unauthenticated assertion-options challenge.
- Migrations: real MySQL foreign key and indexes.
- Add prfStatus unit tests; codebase-consistency pass.