Add passkey model layer
Related to #569927 (closed)
What does this MR do and why?
Adds the passkey model layer
- Adds the
:authentication_mode,:passkey_eligible, and:last_used_atcolumns with appropriate validation in theWebauthnRegistrationtable - Connects to the
Usermodel via associations - Updates the
WebauthnRegistrationfactory
Database Review
Query Plans (Backwards-compatibility)
Table
| has_many | Query Plan |
|---|---|
| Initial (:webauthn_registrations) | link |
| Post-migration (:webauthn_registrations) | link |
| Post-migration (:passkeys) | link |
Local Migrations
bin/rails db:migrate
main: == [advisory_lock_connection] object_id: 156460, pg_backend_pid: 63025
main: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: migrating =========
main: -- add_column(:webauthn_registrations, :authentication_mode, :integer, {:default=>1, :null=>false})
main: -> 0.0483s
main: -- add_column(:webauthn_registrations, :passkey_eligible, :boolean, {:default=>false, :null=>false})
main: -> 0.0023s
main: -- add_column(:webauthn_registrations, :last_used_at, :datetime_with_timezone)
main: -> 0.0071s
main: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: migrated (0.0631s)
main: == [advisory_lock_connection] object_id: 156460, pg_backend_pid: 63025
ci: == [advisory_lock_connection] object_id: 156460, pg_backend_pid: 63026
ci: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: migrating =========
ci: -- add_column(:webauthn_registrations, :authentication_mode, :integer, {:default=>1, :null=>false})
ci: -> 0.0086s
ci: -- add_column(:webauthn_registrations, :passkey_eligible, :boolean, {:default=>false, :null=>false})
ci: -> 0.0028s
ci: -- add_column(:webauthn_registrations, :last_used_at, :datetime_with_timezone)
ci: -> 0.0012s
ci: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: migrated (0.0233s)
ci: == [advisory_lock_connection] object_id: 156460, pg_backend_pid: 63026
VERSION=20250926104422 bin/rails db:migrate:down:main && VERSION=20250926104422 bin/rails db:migrate:down:ci
main: == [advisory_lock_connection] object_id: 156160, pg_backend_pid: 64613
main: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: reverting =========
main: -- remove_column(:webauthn_registrations, :authentication_mode)
main: -> 0.0413s
main: -- remove_column(:webauthn_registrations, :passkey_eligible)
main: -> 0.0020s
main: -- remove_column(:webauthn_registrations, :last_used_at)
main: -> 0.0022s
main: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: reverted (0.0521s)
main: == [advisory_lock_connection] object_id: 156160, pg_backend_pid: 64613
ci: == [advisory_lock_connection] object_id: 156160, pg_backend_pid: 65283
ci: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: reverting =========
ci: -- remove_column(:webauthn_registrations, :authentication_mode)
ci: -> 0.0559s
ci: -- remove_column(:webauthn_registrations, :passkey_eligible)
ci: -> 0.0021s
ci: -- remove_column(:webauthn_registrations, :last_used_at)
ci: -> 0.0020s
ci: == 20250926104422 AddPasskeyColumnsToWebauthnRegistrations: reverted (0.0775s)
ci: == [advisory_lock_connection] object_id: 156160, pg_backend_pid: 65283
How to set up and validate locally
- Clone this branch & run migrations
bin/rails db:migrate
- Open a rails console
gdk rails console
- Create a user with a WebAuthn registration and verify model changes.
user = User.first
auth1 = user.second_factor_webauthn_registrations.create!(
name: '1Password',
credential_xid: SecureRandom.hex,
public_key: SecureRandom.hex,
counter: 0
)
user.second_factor_webauthn_registrations.size
user.passkeys.size
auth1.update!(authentication_mode: 'passwordless')
user.second_factor_webauthn_registrations.size
user.passkeys.size
auth1.last_used_at
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Edited by Hakeem Abdul-Razak