devise-webauthn 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/app/controllers/devise/second_factor_webauthn_credentials_controller.rb +71 -0
- data/app/controllers/devise/two_factor_authentications_controller.rb +44 -0
- data/app/views/devise/second_factor_webauthn_credentials/new.html.erb +15 -0
- data/app/views/devise/two_factor_authentications/new.html.erb +12 -0
- data/config/locales/en.yml +6 -0
- data/devise-webauthn.gemspec +1 -1
- data/lib/devise/models/passkey_authenticatable.rb +3 -7
- data/lib/devise/models/webauthn_credential_authenticatable.rb +21 -0
- data/lib/devise/models/webauthn_two_factor_authenticatable.rb +22 -0
- data/lib/devise/strategies/database_authenticatable.rb +48 -0
- data/lib/devise/strategies/passkey_authenticatable.rb +8 -3
- data/lib/devise/strategies/webauthn_two_factor_authenticatable.rb +51 -0
- data/lib/devise/webauthn/engine.rb +9 -0
- data/lib/devise/webauthn/routes.rb +10 -0
- data/lib/devise/webauthn/url_helpers.rb +4 -1
- data/lib/devise/webauthn/version.rb +1 -1
- data/lib/generators/devise/webauthn/webauthn_credential_model/webauthn_credential_model_generator.rb +6 -1
- metadata +16 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b2e30cdcc561dc53baf71ada282183019321b12b1820afc6107b67c000c1297
|
|
4
|
+
data.tar.gz: f01204525d77a874a131b4a4f13928faa9d6819160792bc12efdb0ad9b9b6d5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c4c14744bbaeb2ed55957baa3d868dd5eb4ebb48300a13a17cd110c329a373f18f97706da1d08f2cfe48d09d5e7a96d0d409c8af485658a55c357f121dbadde
|
|
7
|
+
data.tar.gz: 3456027c51e2aab1434a47afe3943e4e75910d38bba9e0dc551662cc0a0f4708d52d108e30229fe390f93f89e8d411d7be669cc9c2ffdb31dea2aa41aa280669
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## [v0.2.0](https://github.com/cedarcode/devise-webauthn/compare/v0.1.2...v0.2.0/) - 2025-12-03
|
|
6
|
+
|
|
7
|
+
- Add new `webauthn_two_factor_authenticatable` module for enabling 2FA using WebAuthn credentials.
|
|
8
|
+
|
|
9
|
+
## [v0.1.2](https://github.com/cedarcode/devise-webauthn/compare/v0.1.1...v0.1.2/) - 2025-12-03
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed sign in with passkey for resources with name different from "User"
|
|
14
|
+
|
|
3
15
|
## [v0.1.1](https://github.com/cedarcode/devise-webauthn/compare/v0.1.0...v0.1.1/) - 2025-11-13
|
|
4
16
|
|
|
5
17
|
### Changed
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
class SecondFactorWebauthnCredentialsController < DeviseController
|
|
5
|
+
before_action :authenticate_scope!
|
|
6
|
+
|
|
7
|
+
def new
|
|
8
|
+
@options = WebAuthn::Credential.options_for_create(
|
|
9
|
+
user: {
|
|
10
|
+
id: resource.webauthn_id,
|
|
11
|
+
name: resource.email
|
|
12
|
+
},
|
|
13
|
+
exclude: resource.webauthn_credentials.pluck(:external_id),
|
|
14
|
+
authenticator_selection: {
|
|
15
|
+
resident_key: "discouraged",
|
|
16
|
+
user_verification: "discouraged"
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Store challenge in session for later verification
|
|
21
|
+
session[:webauthn_challenge] = @options.challenge
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create
|
|
25
|
+
security_key_from_params = WebAuthn::Credential.from_create(JSON.parse(params[:public_key_credential]))
|
|
26
|
+
|
|
27
|
+
if verify_and_save_security_key(security_key_from_params)
|
|
28
|
+
set_flash_message! :notice, :security_key_created
|
|
29
|
+
else
|
|
30
|
+
set_flash_message! :alert, :webauthn_credential_verification_failed, scope: :"devise.failure"
|
|
31
|
+
end
|
|
32
|
+
redirect_to after_update_path
|
|
33
|
+
rescue WebAuthn::Error
|
|
34
|
+
set_flash_message! :alert, :webauthn_credential_verification_failed, scope: :"devise.failure"
|
|
35
|
+
redirect_to after_update_path
|
|
36
|
+
ensure
|
|
37
|
+
session.delete(:webauthn_challenge)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def destroy
|
|
41
|
+
resource.second_factor_webauthn_credentials.destroy(params[:id])
|
|
42
|
+
redirect_to after_update_path
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def authenticate_scope!
|
|
48
|
+
send(:"authenticate_#{resource_name}!", force: true)
|
|
49
|
+
self.resource = send(:"current_#{resource_name}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def verify_and_save_security_key(security_key_from_params)
|
|
53
|
+
security_key_from_params.verify(
|
|
54
|
+
session[:webauthn_challenge]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
resource.second_factor_webauthn_credentials.create(
|
|
58
|
+
external_id: security_key_from_params.id,
|
|
59
|
+
name: params[:name],
|
|
60
|
+
public_key: security_key_from_params.public_key,
|
|
61
|
+
sign_count: security_key_from_params.sign_count
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The default url to be used after creating a second factor key. You can overwrite
|
|
66
|
+
# this method in your own SecondFactorWebauthnCredentialsController.
|
|
67
|
+
def after_update_path
|
|
68
|
+
new_second_factor_webauthn_credential_path(resource_name)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
class TwoFactorAuthenticationsController < DeviseController
|
|
5
|
+
prepend_before_action :set_resource, only: :new
|
|
6
|
+
prepend_before_action :ensure_sign_in_initiated
|
|
7
|
+
prepend_before_action :require_no_authentication
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
@options = WebAuthn::Credential.options_for_get(
|
|
11
|
+
allow: @resource.webauthn_credentials.pluck(:external_id),
|
|
12
|
+
user_verification: "discouraged"
|
|
13
|
+
)
|
|
14
|
+
session[:two_factor_authentication_challenge] = @options.challenge
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
self.resource = warden.authenticate!(auth_options)
|
|
19
|
+
set_flash_message! :notice, :signed_in, scope: :"devise.sessions"
|
|
20
|
+
sign_in(resource_name, resource)
|
|
21
|
+
yield resource if block_given?
|
|
22
|
+
respond_with resource, location: after_sign_in_path_for(resource)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def auth_options
|
|
28
|
+
{ scope: resource_name, recall: "#{controller_path}#new", locale: I18n.locale }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def ensure_sign_in_initiated
|
|
34
|
+
return if session[:current_authentication_resource_id].present?
|
|
35
|
+
|
|
36
|
+
set_flash_message! :alert, :sign_in_not_initiated, scope: :"devise.failure"
|
|
37
|
+
redirect_to new_session_path(resource_name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_resource
|
|
41
|
+
@resource = resource_class.find(session[:current_authentication_resource_id])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%= form_with(
|
|
2
|
+
url: second_factor_webauthn_credentials_path(resource),
|
|
3
|
+
method: :post,
|
|
4
|
+
data: {
|
|
5
|
+
action: "webauthn-credentials#create:prevent",
|
|
6
|
+
controller: "webauthn-credentials",
|
|
7
|
+
webauthn_credentials_options_param: @options,
|
|
8
|
+
}
|
|
9
|
+
) do |form| %>
|
|
10
|
+
<%= form.hidden_field(:public_key_credential,
|
|
11
|
+
data: { "webauthn-credentials-target": "credentialHiddenInput" }) %>
|
|
12
|
+
<%= form.label :name, 'Security Key name' %>
|
|
13
|
+
<%= form.text_field :name, required: true %>
|
|
14
|
+
<%= form.submit 'Create Security Key' %>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%= form_with(
|
|
2
|
+
url: two_factor_authentication_path(resource_name),
|
|
3
|
+
method: :post,
|
|
4
|
+
data: {
|
|
5
|
+
action: "webauthn-credentials#get:prevent",
|
|
6
|
+
controller: "webauthn-credentials",
|
|
7
|
+
webauthn_credentials_options_param: @options
|
|
8
|
+
},
|
|
9
|
+
) do |f| %>
|
|
10
|
+
<%= f.hidden_field(:public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" }) %>
|
|
11
|
+
<%= f.button('Use security key', type: "submit") %>
|
|
12
|
+
<% end %>
|
data/config/locales/en.yml
CHANGED
|
@@ -3,6 +3,12 @@ en:
|
|
|
3
3
|
passkeys:
|
|
4
4
|
passkey_created: "Passkey created successfully."
|
|
5
5
|
passkey_creation_failed: "Passkey creation failed."
|
|
6
|
+
second_factor_webauthn_credentials:
|
|
7
|
+
security_key_created: "Security Key created successfully."
|
|
6
8
|
failure:
|
|
7
9
|
passkey_not_found: "Your passkey doesn't exist or is not valid."
|
|
8
10
|
passkey_verification_failed: "Passkey verification failed."
|
|
11
|
+
sign_in_not_initiated: "Sign in was not initiated."
|
|
12
|
+
two_factor_required: "Two-factor authentication is required to sign in."
|
|
13
|
+
webauthn_credential_not_found: "Your WebAuthn credential doesn't exist or is not valid."
|
|
14
|
+
webauthn_credential_verification_failed: "Webauthn credential verification failed."
|
data/devise-webauthn.gemspec
CHANGED
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.homepage = "https://github.com/cedarcode/devise-webauthn"
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
18
|
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/
|
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
|
20
20
|
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/concern"
|
|
4
|
+
require "devise/models/webauthn_credential_authenticatable"
|
|
4
5
|
require "devise/strategies/passkey_authenticatable"
|
|
5
6
|
|
|
6
7
|
module Devise
|
|
7
8
|
module Models
|
|
8
9
|
module PasskeyAuthenticatable
|
|
9
10
|
extend ActiveSupport::Concern
|
|
11
|
+
include WebauthnCredentialAuthenticatable
|
|
10
12
|
|
|
11
13
|
included do
|
|
12
|
-
has_many :passkeys,
|
|
13
|
-
|
|
14
|
-
validates :webauthn_id, uniqueness: true, allow_blank: true
|
|
15
|
-
|
|
16
|
-
after_initialize do
|
|
17
|
-
self.webauthn_id ||= WebAuthn.generate_user_id
|
|
18
|
-
end
|
|
14
|
+
has_many :passkeys, -> { passkey }, class_name: "WebauthnCredential"
|
|
19
15
|
end
|
|
20
16
|
end
|
|
21
17
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module Devise
|
|
6
|
+
module Models
|
|
7
|
+
module WebauthnCredentialAuthenticatable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
has_many :webauthn_credentials, dependent: :destroy
|
|
12
|
+
|
|
13
|
+
validates :webauthn_id, uniqueness: true, allow_blank: true
|
|
14
|
+
|
|
15
|
+
after_initialize do
|
|
16
|
+
self.webauthn_id ||= WebAuthn.generate_user_id
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
require "devise/models/webauthn_credential_authenticatable"
|
|
5
|
+
require "devise/strategies/webauthn_two_factor_authenticatable"
|
|
6
|
+
|
|
7
|
+
module Devise
|
|
8
|
+
module Models
|
|
9
|
+
module WebauthnTwoFactorAuthenticatable
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
include WebauthnCredentialAuthenticatable
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
has_many :second_factor_webauthn_credentials, -> { second_factor }, class_name: "WebauthnCredential"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def second_factor_enabled?
|
|
18
|
+
webauthn_credentials.any?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'devise/strategies/authenticatable'
|
|
4
|
+
|
|
5
|
+
module Devise
|
|
6
|
+
module Strategies
|
|
7
|
+
# Default strategy for signing in a user, based on their email and password in the database.
|
|
8
|
+
class DatabaseAuthenticatable < Authenticatable
|
|
9
|
+
def authenticate!
|
|
10
|
+
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
|
|
11
|
+
hashed = false
|
|
12
|
+
|
|
13
|
+
if validate(resource){ hashed = true; resource.valid_password?(password) }
|
|
14
|
+
if resource.second_factor_enabled?
|
|
15
|
+
session[:current_authentication_resource_id] = resource.id
|
|
16
|
+
request.flash[:notice] = two_factor_required_message
|
|
17
|
+
request.commit_flash
|
|
18
|
+
redirect!(two_factor_authentication_path, {}, message: two_factor_required_message)
|
|
19
|
+
else
|
|
20
|
+
remember_me(resource)
|
|
21
|
+
resource.after_database_authentication
|
|
22
|
+
success!(resource)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# In paranoid mode, hash the password even when a resource doesn't exist for the given authentication key.
|
|
27
|
+
# This is necessary to prevent enumeration attacks - e.g. the request is faster when a resource doesn't
|
|
28
|
+
# exist in the database if the password hashing algorithm is not called.
|
|
29
|
+
mapping.to.new.password = password if !hashed && Devise.paranoid
|
|
30
|
+
unless resource
|
|
31
|
+
Devise.paranoid ? fail(:invalid) : fail(:not_found_in_database)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def two_factor_authentication_path
|
|
38
|
+
Rails.application.routes.url_helpers.send(:"new_#{scope}_two_factor_authentication_path")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def two_factor_required_message
|
|
42
|
+
I18n.t(:"#{scope}.two_factor_required", resource_name: scope, scope: "devise.failure", default: :two_factor_required)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)
|
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module Devise
|
|
4
4
|
module Strategies
|
|
5
|
-
class PasskeyAuthenticatable <
|
|
5
|
+
class PasskeyAuthenticatable < Devise::Strategies::Base
|
|
6
6
|
def valid?
|
|
7
7
|
passkey_param.present? && session[:authentication_challenge].present?
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def authenticate!
|
|
11
11
|
passkey_from_params = WebAuthn::Credential.from_get(JSON.parse(passkey_param))
|
|
12
|
-
stored_passkey = WebauthnCredential.find_by(external_id: passkey_from_params.id)
|
|
12
|
+
stored_passkey = WebauthnCredential.passkey.find_by(external_id: passkey_from_params.id)
|
|
13
13
|
|
|
14
14
|
return fail!(:passkey_not_found) if stored_passkey.blank?
|
|
15
15
|
|
|
16
16
|
verify_passkeys(passkey_from_params, stored_passkey)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
resource = stored_passkey.public_send(resource_name)
|
|
19
|
+
success!(resource)
|
|
19
20
|
rescue WebAuthn::Error
|
|
20
21
|
fail!(:passkey_verification_failed)
|
|
21
22
|
ensure
|
|
@@ -38,6 +39,10 @@ module Devise
|
|
|
38
39
|
|
|
39
40
|
stored_passkey.update!(sign_count: passkey_from_params.sign_count)
|
|
40
41
|
end
|
|
42
|
+
|
|
43
|
+
def resource_name
|
|
44
|
+
mapping.to.name.underscore
|
|
45
|
+
end
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Strategies
|
|
5
|
+
class WebauthnTwoFactorAuthenticatable < Devise::Strategies::Base
|
|
6
|
+
def valid?
|
|
7
|
+
credential_param.present? && session[:two_factor_authentication_challenge].present?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def authenticate!
|
|
11
|
+
credential_from_params = WebAuthn::Credential.from_get(JSON.parse(credential_param))
|
|
12
|
+
stored_credential = WebauthnCredential.find_by(external_id: credential_from_params.id)
|
|
13
|
+
|
|
14
|
+
return fail!(:webauthn_credential_not_found) if stored_credential.blank?
|
|
15
|
+
|
|
16
|
+
verify_credential(credential_from_params, stored_credential)
|
|
17
|
+
|
|
18
|
+
resource = stored_credential.public_send(resource_name)
|
|
19
|
+
success!(resource)
|
|
20
|
+
|
|
21
|
+
session.delete(:current_authentication_resource_id)
|
|
22
|
+
rescue WebAuthn::Error
|
|
23
|
+
fail!(:webauthn_credential_verification_failed)
|
|
24
|
+
ensure
|
|
25
|
+
session.delete(:two_factor_authentication_challenge)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def credential_param
|
|
31
|
+
params[:public_key_credential]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def verify_credential(credential_from_params, stored_credential)
|
|
35
|
+
credential_from_params.verify(
|
|
36
|
+
session[:two_factor_authentication_challenge],
|
|
37
|
+
public_key: stored_credential.public_key,
|
|
38
|
+
sign_count: stored_credential.sign_count
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
stored_credential.update!(sign_count: credential_from_params.sign_count)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resource_name
|
|
45
|
+
mapping.to.name.underscore
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Warden::Strategies.add(:webauthn_two_factor_authenticatable, Devise::Strategies::WebauthnTwoFactorAuthenticatable)
|
|
@@ -14,6 +14,15 @@ module Devise
|
|
|
14
14
|
route: { passkey_authentication: [] }
|
|
15
15
|
}
|
|
16
16
|
)
|
|
17
|
+
|
|
18
|
+
Devise.add_module(
|
|
19
|
+
:webauthn_two_factor_authenticatable,
|
|
20
|
+
{
|
|
21
|
+
model: "devise/models/webauthn_two_factor_authenticatable",
|
|
22
|
+
strategy: true,
|
|
23
|
+
route: { two_factor_authentication: [] }
|
|
24
|
+
}
|
|
25
|
+
)
|
|
17
26
|
end
|
|
18
27
|
|
|
19
28
|
initializer "devise.webauthn.helpers" do
|
|
@@ -8,6 +8,16 @@ module ActionDispatch
|
|
|
8
8
|
def devise_passkey_authentication(_mapping, controllers)
|
|
9
9
|
resources :passkeys, only: %i[new create destroy], controller: controllers[:passkeys]
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
def devise_two_factor_authentication(_mapping, controllers)
|
|
13
|
+
resource :two_factor_authentication,
|
|
14
|
+
only: %i[new create],
|
|
15
|
+
controller: controllers[:two_factor_authentications]
|
|
16
|
+
|
|
17
|
+
resources :second_factor_webauthn_credentials,
|
|
18
|
+
only: %i[new create destroy],
|
|
19
|
+
controller: controllers[:second_factor_webauthn_credentials]
|
|
20
|
+
end
|
|
11
21
|
end
|
|
12
22
|
end
|
|
13
23
|
end
|
|
@@ -23,7 +23,10 @@ module Devise
|
|
|
23
23
|
module UrlHelpers
|
|
24
24
|
{
|
|
25
25
|
passkeys: [nil],
|
|
26
|
-
passkey: [nil, :new]
|
|
26
|
+
passkey: [nil, :new],
|
|
27
|
+
two_factor_authentication: [nil, :new],
|
|
28
|
+
second_factor_webauthn_credentials: [nil],
|
|
29
|
+
second_factor_webauthn_credential: [nil, :new]
|
|
27
30
|
}.each do |route, actions|
|
|
28
31
|
%i[path url].each do |path_or_url|
|
|
29
32
|
actions.each do |action|
|
data/lib/generators/devise/webauthn/webauthn_credential_model/webauthn_credential_model_generator.rb
CHANGED
|
@@ -19,7 +19,8 @@ module Devise
|
|
|
19
19
|
"name:string",
|
|
20
20
|
"public_key:text",
|
|
21
21
|
"sign_count:integer{8}",
|
|
22
|
-
"#{user_model_name}:references"
|
|
22
|
+
"#{user_model_name}:references",
|
|
23
|
+
"authentication_factor:integer{1}"
|
|
23
24
|
]
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -28,6 +29,10 @@ module Devise
|
|
|
28
29
|
<<~RUBY.indent(2)
|
|
29
30
|
validates :external_id, :public_key, :name, :sign_count, presence: true
|
|
30
31
|
validates :external_id, uniqueness: true
|
|
32
|
+
|
|
33
|
+
enum :authentication_factor, { first_factor: 0, second_factor: 1 }
|
|
34
|
+
|
|
35
|
+
scope :passkey, -> { first_factor }
|
|
31
36
|
RUBY
|
|
32
37
|
end
|
|
33
38
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise-webauthn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cedarcode
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2025-12-03 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: devise
|
|
@@ -37,6 +38,7 @@ dependencies:
|
|
|
37
38
|
- - "~>"
|
|
38
39
|
- !ruby/object:Gem::Version
|
|
39
40
|
version: '3.0'
|
|
41
|
+
description:
|
|
40
42
|
email:
|
|
41
43
|
- webauthn@cedarcode.com
|
|
42
44
|
executables: []
|
|
@@ -56,8 +58,12 @@ files:
|
|
|
56
58
|
- README.md
|
|
57
59
|
- Rakefile
|
|
58
60
|
- app/controllers/devise/passkeys_controller.rb
|
|
61
|
+
- app/controllers/devise/second_factor_webauthn_credentials_controller.rb
|
|
62
|
+
- app/controllers/devise/two_factor_authentications_controller.rb
|
|
59
63
|
- app/views/devise/passkeys/new.html.erb
|
|
64
|
+
- app/views/devise/second_factor_webauthn_credentials/new.html.erb
|
|
60
65
|
- app/views/devise/sessions/new.html.erb
|
|
66
|
+
- app/views/devise/two_factor_authentications/new.html.erb
|
|
61
67
|
- bin/console
|
|
62
68
|
- bin/rails
|
|
63
69
|
- bin/setup
|
|
@@ -69,7 +75,11 @@ files:
|
|
|
69
75
|
- gemfiles/rails_8_0.gemfile
|
|
70
76
|
- gemfiles/rails_edge.gemfile
|
|
71
77
|
- lib/devise/models/passkey_authenticatable.rb
|
|
78
|
+
- lib/devise/models/webauthn_credential_authenticatable.rb
|
|
79
|
+
- lib/devise/models/webauthn_two_factor_authenticatable.rb
|
|
80
|
+
- lib/devise/strategies/database_authenticatable.rb
|
|
72
81
|
- lib/devise/strategies/passkey_authenticatable.rb
|
|
82
|
+
- lib/devise/strategies/webauthn_two_factor_authenticatable.rb
|
|
73
83
|
- lib/devise/webauthn.rb
|
|
74
84
|
- lib/devise/webauthn/engine.rb
|
|
75
85
|
- lib/devise/webauthn/helpers/credentials_helper.rb
|
|
@@ -93,8 +103,9 @@ licenses:
|
|
|
93
103
|
metadata:
|
|
94
104
|
homepage_uri: https://github.com/cedarcode/devise-webauthn
|
|
95
105
|
source_code_uri: https://github.com/cedarcode/devise-webauthn
|
|
96
|
-
changelog_uri: https://github.com/cedarcode/devise-webauthn/blob/
|
|
106
|
+
changelog_uri: https://github.com/cedarcode/devise-webauthn/blob/master/CHANGELOG.md
|
|
97
107
|
rubygems_mfa_required: 'true'
|
|
108
|
+
post_install_message:
|
|
98
109
|
rdoc_options: []
|
|
99
110
|
require_paths:
|
|
100
111
|
- lib
|
|
@@ -109,7 +120,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
109
120
|
- !ruby/object:Gem::Version
|
|
110
121
|
version: '0'
|
|
111
122
|
requirements: []
|
|
112
|
-
rubygems_version: 3.
|
|
123
|
+
rubygems_version: 3.2.1
|
|
124
|
+
signing_key:
|
|
113
125
|
specification_version: 4
|
|
114
126
|
summary: Devise extension to support WebAuthn.
|
|
115
127
|
test_files: []
|