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

@@ -0,0 +1,21 @@
class CreateWebauthnCredentials < ActiveRecord::Migration[7.2]
def change
add_column :users, :webauthn_id, :string
add_index :users, :webauthn_id, unique: true, where: "webauthn_id IS NOT NULL"
create_table :webauthn_credentials, id: :uuid do |t|
t.references :user, null: false, foreign_key: true, type: :uuid
t.string :nickname, null: false
t.string :credential_id, null: false
t.text :public_key, null: false
t.bigint :sign_count, null: false, default: 0
t.string :transports, array: true, null: false, default: []
t.datetime :last_used_at
t.timestamps
end
add_index :webauthn_credentials, :credential_id, unique: true
add_check_constraint :webauthn_credentials, "sign_count >= 0", name: "chk_webauthn_credentials_sign_count_non_negative"
end
end

18
db/schema.rb generated
View File

@@ -1605,6 +1605,7 @@ ActiveRecord::Schema[7.2].define(version: 2026_05_02_120000) do
t.string "locale"
t.string "ui_layout"
t.uuid "default_account_id"
t.string "webauthn_id"
t.index ["default_account_id"], name: "index_users_on_default_account_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["family_id"], name: "index_users_on_family_id"
@@ -1612,6 +1613,7 @@ ActiveRecord::Schema[7.2].define(version: 2026_05_02_120000) do
t.index ["locale"], name: "index_users_on_locale"
t.index ["otp_secret"], name: "index_users_on_otp_secret", unique: true, where: "(otp_secret IS NOT NULL)"
t.index ["preferences"], name: "index_users_on_preferences", using: :gin
t.index ["webauthn_id"], name: "index_users_on_webauthn_id", unique: true, where: "(webauthn_id IS NOT NULL)"
end
create_table "valuations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
@@ -1633,6 +1635,21 @@ ActiveRecord::Schema[7.2].define(version: 2026_05_02_120000) do
t.string "subtype"
end
create_table "webauthn_credentials", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "user_id", null: false
t.string "nickname", null: false
t.string "credential_id", null: false
t.text "public_key", null: false
t.bigint "sign_count", default: 0, null: false
t.string "transports", default: [], null: false, array: true
t.datetime "last_used_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.check_constraint "sign_count >= 0", name: "chk_webauthn_credentials_sign_count_non_negative"
t.index ["credential_id"], name: "index_webauthn_credentials_on_credential_id", unique: true
t.index ["user_id"], name: "index_webauthn_credentials_on_user_id"
end
add_foreign_key "account_providers", "accounts", on_delete: :cascade
add_foreign_key "account_shares", "accounts"
add_foreign_key "account_shares", "users"
@@ -1728,4 +1745,5 @@ ActiveRecord::Schema[7.2].define(version: 2026_05_02_120000) do
add_foreign_key "users", "accounts", column: "default_account_id", on_delete: :nullify
add_foreign_key "users", "chats", column: "last_viewed_chat_id"
add_foreign_key "users", "families"
add_foreign_key "webauthn_credentials", "users"
end