feat(auth): add WebAuthn MFA credentials (#1628)

* feat(auth): add WebAuthn MFA credentials

* fix(auth): harden WebAuthn MFA review paths

* fix(auth): polish WebAuthn error handling

* fix(auth): handle duplicate WebAuthn credential races

* fix(auth): permit WebAuthn credential params

* fix(auth): trim WebAuthn registration controller cleanup

* fix(auth): tighten WebAuthn MFA handling

* fix(auth): pin WebAuthn relying party config
This commit is contained in:
ghost
2026-05-03 14:13:28 -06:00
committed by GitHub
parent faf31b9c91
commit 911aa34ba9
29 changed files with 1117 additions and 10 deletions

View File

@@ -28,6 +28,7 @@ class User < ApplicationRecord
has_many :sessions, dependent: :destroy
has_many :chats, dependent: :destroy
has_many :api_keys, dependent: :destroy
has_many :webauthn_credentials, dependent: :destroy
has_many :mobile_devices, dependent: :destroy
has_many :invitations, foreign_key: :inviter_id, dependent: :destroy
has_many :impersonator_support_sessions, class_name: "ImpersonationSession", foreign_key: :impersonator_id, dependent: :destroy
@@ -227,11 +228,14 @@ class User < ApplicationRecord
end
def disable_mfa!
update!(
otp_secret: nil,
otp_required: false,
otp_backup_codes: []
)
transaction do
update!(
otp_secret: nil,
otp_required: false,
otp_backup_codes: []
)
webauthn_credentials.destroy_all
end
end
def verify_otp?(code)
@@ -245,6 +249,20 @@ class User < ApplicationRecord
totp.provisioning_uri(email)
end
def ensure_webauthn_id!
return webauthn_id if webauthn_id.present?
with_lock do
update!(webauthn_id: WebAuthn.generate_user_id) unless webauthn_id.present?
end
webauthn_id
end
def webauthn_enabled?
otp_required? && webauthn_credentials.exists?
end
def onboarded?
onboarded_at.present?
end