devise-webauthn 0.3.1 → 0.4.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/.github/workflows/ruby.yml +2 -2
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +20 -0
- data/Appraisals +0 -1
- data/CHANGELOG.md +45 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -1
- data/README.md +17 -4
- data/app/assets/javascript/devise/webauthn.js +7 -5
- data/app/controllers/devise/passkey_authentication_options_controller.rb +17 -0
- data/app/controllers/devise/passkey_registration_options_controller.rb +41 -0
- data/app/controllers/devise/security_key_authentication_options_controller.rb +26 -0
- data/app/controllers/devise/security_key_registration_options_controller.rb +41 -0
- data/app/views/devise/passkeys/new.html.erb +1 -1
- data/app/views/devise/second_factor_webauthn_credentials/new.html.erb +1 -1
- data/app/views/devise/sessions/new.html.erb +3 -1
- data/app/views/devise/two_factor_authentications/new.html.erb +3 -1
- data/gemfiles/devise_5_0.gemfile +1 -1
- data/gemfiles/rails_7_1.gemfile +1 -2
- data/gemfiles/rails_7_2.gemfile +1 -1
- data/gemfiles/rails_8_0.gemfile +1 -1
- data/gemfiles/rails_8_1.gemfile +1 -1
- data/gemfiles/rails_edge.gemfile +1 -1
- data/lib/devise/models/webauthn_credential_authenticatable.rb +1 -1
- data/lib/devise/strategies/passkey_authenticatable.rb +21 -5
- data/lib/devise/strategies/webauthn_two_factor_authenticatable.rb +8 -0
- data/lib/devise/webauthn/helpers/credentials_helper.rb +21 -104
- data/lib/devise/webauthn/routes.rb +9 -0
- data/lib/devise/webauthn/url_helpers.rb +5 -1
- data/lib/devise/webauthn/version.rb +1 -1
- data/lib/generators/devise/webauthn/controllers_generator.rb +8 -1
- data/lib/generators/devise/webauthn/templates/controllers/passkey_authentication_options_controller.rb.tt +8 -0
- data/lib/generators/devise/webauthn/templates/controllers/passkey_registration_options_controller.rb.tt +8 -0
- data/lib/generators/devise/webauthn/templates/controllers/security_key_authentication_options_controller.rb.tt +8 -0
- data/lib/generators/devise/webauthn/templates/controllers/security_key_registration_options_controller.rb.tt +8 -0
- data/lib/generators/devise/webauthn/webauthn_id/templates/add_webauthn_id.rb.erb +41 -0
- data/lib/generators/devise/webauthn/webauthn_id/webauthn_id_generator.rb +9 -4
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f958718af83cd2bdae3d3c08d8ac72678e2e313b93eabdc7effe1af70bda300f
|
|
4
|
+
data.tar.gz: ea997a0537ca6a68d97e7aedbff6993331eeebab1857b2d7a26177987efe7571
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e81d1880a7b6722c150b9cdd3fcabcbc07128ab5beaea22c580a6ff5d6338ac108ed55728f876eb9635e087787c875fca246de6ed307450f53907b14eadc5c5d
|
|
7
|
+
data.tar.gz: 0fe56be79a56f2ca46349ebd4e9ce1ed1f3f43df73cb7acde76a0aebce4dae4cf022c8cc01dba53c89c9d0b637145474f169cab0b4f7f4811788fdc7b0fe2479
|
data/.github/workflows/ruby.yml
CHANGED
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2026-01-02 20:36:46 UTC using RuboCop version 1.79.1.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 1
|
|
10
|
+
# Configuration parameters: Max, CountAsOne.
|
|
11
|
+
RSpec/ExampleLength:
|
|
12
|
+
Exclude:
|
|
13
|
+
- 'spec/requests/devise/two_factor_authentication_spec.rb'
|
|
14
|
+
|
|
15
|
+
# Offense count: 9
|
|
16
|
+
# Configuration parameters: Max.
|
|
17
|
+
RSpec/MultipleExpectations:
|
|
18
|
+
Exclude:
|
|
19
|
+
- 'spec/requests/devise/passkey_authentication_spec.rb'
|
|
20
|
+
- 'spec/requests/devise/two_factor_authentication_spec.rb'
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## [v0.4.0](https://github.com/cedarcode/devise-webauthn/compare/v0.3.1...v0.4.0/) - 2026-04-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Dispatch `webauthn:unsupported` for browsers missing `parseOptionsFromJSON`. [#127](https://github.com/cedarcode/devise-webauthn/pull/127) [@santiagorodriguez96]
|
|
10
|
+
- Scope `login_with_passkey_form_for` to the Devise resource so that form builder fields (e.g. `f.check_box :remember_me`) are properly namespaced (e.g. `account[remember_me]`). [#134](https://github.com/cedarcode/devise-webauthn/pull/134) [@RenzoMinelli]
|
|
11
|
+
- Allow passing a -c flag to the controller generator to specify which controller to override. [#110](https://github.com/cedarcode/devise-webauthn/pull/110) [@nicolastemciuc]
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Change `webauthn_id` generation from `after_initialize` to `before_validation` and add a data backfill to the `webauthn_id` migration generator for existing records. [#125](https://github.com/cedarcode/devise-webauthn/pull/125) [@santiagorodriguez96]
|
|
16
|
+
- Options for getting or creating passkeys and security keys are now served by dedicated Rails controllers and retrieved via JavaScript fetch requests. [#73](https://github.com/cedarcode/devise-webauthn/pull/73) [@nicolastemciuc]
|
|
17
|
+
- BREAKING!: Remove helpers for generating WebAuthn options. [#106](https://github.com/cedarcode/devise-webauthn/pull/115) [@nicolastemciuc]
|
|
18
|
+
- BREAKING!: `login_with_passkey_button` and `login_with_security_key_button` helpers have been renamed to `login_with_passkey_form_for` and `login_with_security_key_form_for`. They now take a block and no longer generate the submit button automatically. You need to explicitly add the button inside the block. [#112](https://github.com/cedarcode/devise-webauthn/pull/112) [@RenzoMinelli]
|
|
19
|
+
```erb
|
|
20
|
+
<%# Before %>
|
|
21
|
+
<%= login_with_passkey_button(:user, "Log in with passkeys") %>
|
|
22
|
+
|
|
23
|
+
<%# After %>
|
|
24
|
+
<%= login_with_passkey_form_for(:user) do |form| %>
|
|
25
|
+
<%= form.submit "Log in with passkeys" %>
|
|
26
|
+
<% end %>
|
|
27
|
+
```
|
|
28
|
+
- BREAKING!: Replace `form_classes:` keyword argument with direct keyword arguments in all form helper methods (`passkey_creation_form_for`, `login_with_passkey_form_for`, `security_key_creation_form_for`, `login_with_security_key_form_for`). All options are delegated to `form_with`, allowing you to pass any HTML attributes or form options directly. [#111](https://github.com/cedarcode/devise-webauthn/pull/111) [@RenzoMinelli]
|
|
29
|
+
```erb
|
|
30
|
+
<%# Before %>
|
|
31
|
+
<%= passkey_creation_form_for(:user, form_classes: "my-class") do |form| %>
|
|
32
|
+
...
|
|
33
|
+
<% end %>
|
|
34
|
+
|
|
35
|
+
<%# After %>
|
|
36
|
+
<%= passkey_creation_form_for(:user, class: "my-class", id: "my-form", data: { turbo: false }) do |form| %>
|
|
37
|
+
...
|
|
38
|
+
<% end %>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Validate `userHandle` from authenticator response against `webauthn_id`. [#131](https://github.com/cedarcode/devise-webauthn/pull/131) [@santiagorodriguez96]
|
|
44
|
+
- Fix `Remember me` checkbox not honored when logging in with passkeys. [#133](https://github.com/cedarcode/devise-webauthn/pull/133) [@santiagorodriguez96]
|
|
45
|
+
- Fix form helpers (`passkey_creation_form_for`, `login_with_passkey_button`, `security_key_creation_form_for`, `login_with_security_key_button`) to accept a `resource_name` instead of requiring the `resource` object from the view context. [#114](https://github.com/cedarcode/devise-webauthn/pull/114) [@RenzoMinelli]
|
|
46
|
+
|
|
5
47
|
## [v0.3.1](https://github.com/cedarcode/devise-webauthn/compare/v0.3.0...v0.3.1/) - 2026-02-10
|
|
6
48
|
|
|
7
49
|
### Fixed
|
|
@@ -22,6 +64,9 @@
|
|
|
22
64
|
- Previously generated Stimulus controller for handling WebAuthn client logic are no longer generated.
|
|
23
65
|
- Stimulus is no longer needed for this engine to work.
|
|
24
66
|
- Make helpers for generating WebAuthn options public methods. [#106](https://github.com/cedarcode/devise-webauthn/pull/106) [@santiagorodriguez96]
|
|
67
|
+
- BREAKING!: Our controller for managing second factor credentials now uses a separate method for each endpoint for setting the URL to redirect to. [#80](https://github.com/cedarcode/devise-webauthn/pull/80) [@nicolastemciuc]
|
|
68
|
+
- What used to be just an `after_update_path` for all endpoints, now it's an `after_(create|update|destroy)_path` for each endpoint.
|
|
69
|
+
- If you had overriden the controller to change the `after_update_path`, be mindful that now `create` and `destroy` endpoints will call its own method.
|
|
25
70
|
|
|
26
71
|
### Fixed
|
|
27
72
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
devise-webauthn (0.
|
|
4
|
+
devise-webauthn (0.4.0)
|
|
5
5
|
devise (>= 4.9)
|
|
6
6
|
webauthn (~> 3.0)
|
|
7
7
|
|
|
@@ -266,6 +266,8 @@ GEM
|
|
|
266
266
|
rspec-expectations (~> 3.13)
|
|
267
267
|
rspec-mocks (~> 3.13)
|
|
268
268
|
rspec-support (~> 3.13)
|
|
269
|
+
rspec-retry (0.6.2)
|
|
270
|
+
rspec-core (> 3.3)
|
|
269
271
|
rspec-support (3.13.4)
|
|
270
272
|
rubocop (1.79.1)
|
|
271
273
|
json (~> 2.3)
|
|
@@ -351,6 +353,7 @@ DEPENDENCIES
|
|
|
351
353
|
puma (~> 6.6)
|
|
352
354
|
rails (~> 8.0)
|
|
353
355
|
rspec-rails (~> 8.0)
|
|
356
|
+
rspec-retry
|
|
354
357
|
rubocop (~> 1.79)
|
|
355
358
|
rubocop-rails (~> 2.32)
|
|
356
359
|
rubocop-rspec (~> 3.6)
|
data/README.md
CHANGED
|
@@ -73,6 +73,8 @@ Then, follow these steps to integrate Devise::Webauthn:
|
|
|
73
73
|
config.rp_name = "Your App Name"
|
|
74
74
|
end
|
|
75
75
|
```
|
|
76
|
+
> [!TIP]
|
|
77
|
+
> You can find a working example on how to use this gem for passwordless and two factor authentication in [`devise-webauthn-rails-demo`](https://github.com/cedarcode/devise-webauthn-demo-app).
|
|
76
78
|
|
|
77
79
|
5. **Include bundled WebAuthn JavaScript in your application:**
|
|
78
80
|
The install generator automatically configures JavaScript loading based on your setup:
|
|
@@ -143,16 +145,27 @@ $ bin/rails generate devise:webauthn:views -v passkeys
|
|
|
143
145
|
```
|
|
144
146
|
|
|
145
147
|
### Helper methods
|
|
146
|
-
Devise::Webauthn provides helpers that can be used in your views.
|
|
148
|
+
Devise::Webauthn provides helpers that can be used in your views. These helpers accept either a resource name (e.g., `:user`) or a resource object (e.g., `@user`) as the first argument.
|
|
147
149
|
|
|
148
|
-
|
|
150
|
+
For example, for a resource named `user`, you can use the following helpers:
|
|
151
|
+
|
|
152
|
+
To add a form for logging in with passkeys:
|
|
153
|
+
```erb
|
|
154
|
+
<%= login_with_passkey_form_for(:user) do |form| %>
|
|
155
|
+
<%= form.submit "Log in with passkeys" %>
|
|
156
|
+
<% end %>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
To add a form for logging in with security keys (2FA):
|
|
149
160
|
```erb
|
|
150
|
-
<%=
|
|
161
|
+
<%= login_with_security_key_form_for(@resource) do |form| %>
|
|
162
|
+
<%= form.submit "Use security key" %>
|
|
163
|
+
<% end %>
|
|
151
164
|
```
|
|
152
165
|
|
|
153
166
|
To add a passkeys creation form:
|
|
154
167
|
```erb
|
|
155
|
-
<%= passkey_creation_form_for(
|
|
168
|
+
<%= passkey_creation_form_for(:user) do |form| %>
|
|
156
169
|
<%= form.label :name, 'Passkey name' %>
|
|
157
170
|
<%= form.text_field :name, required: true %>
|
|
158
171
|
<%= form.submit 'Create Passkey' %>
|
|
@@ -3,7 +3,9 @@ function isWebAuthnSupported() {
|
|
|
3
3
|
navigator.credentials &&
|
|
4
4
|
navigator.credentials.create &&
|
|
5
5
|
navigator.credentials.get &&
|
|
6
|
-
window.PublicKeyCredential
|
|
6
|
+
window.PublicKeyCredential &&
|
|
7
|
+
PublicKeyCredential.parseCreationOptionsFromJSON &&
|
|
8
|
+
PublicKeyCredential.parseRequestOptionsFromJSON
|
|
7
9
|
);
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -20,8 +22,8 @@ export class WebauthnCreateElement extends HTMLElement {
|
|
|
20
22
|
event.preventDefault();
|
|
21
23
|
|
|
22
24
|
try {
|
|
23
|
-
const
|
|
24
|
-
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(
|
|
25
|
+
const response = await fetch(this.getAttribute('data-options-url'));
|
|
26
|
+
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(await response.json());
|
|
25
27
|
const credential = await navigator.credentials.create({ publicKey });
|
|
26
28
|
|
|
27
29
|
this.querySelector('[data-webauthn-target="response"]').value = await this.stringifyRegistrationCredentialWithGracefullyHandlingAuthenticatorIssues(credential);
|
|
@@ -101,8 +103,8 @@ export class WebauthnGetElement extends HTMLElement {
|
|
|
101
103
|
event.preventDefault();
|
|
102
104
|
|
|
103
105
|
try {
|
|
104
|
-
const
|
|
105
|
-
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(
|
|
106
|
+
const response = await fetch(this.getAttribute('data-options-url'));
|
|
107
|
+
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(await response.json());
|
|
106
108
|
const credential = await navigator.credentials.get({ publicKey });
|
|
107
109
|
|
|
108
110
|
this.querySelector('[data-webauthn-target="response"]').value = await this.stringifyAuthenticationCredentialWithGracefullyHandlingAuthenticatorIssues(credential);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
class PasskeyAuthenticationOptionsController < DeviseController
|
|
5
|
+
def index
|
|
6
|
+
passkey_options =
|
|
7
|
+
WebAuthn::Credential.options_for_get(
|
|
8
|
+
user_verification: "required"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Store challenge in session for later verification
|
|
12
|
+
session[:authentication_challenge] = passkey_options.challenge
|
|
13
|
+
|
|
14
|
+
render json: passkey_options
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
class PasskeyRegistrationOptionsController < DeviseController
|
|
5
|
+
before_action :authenticate_scope!
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
passkey_options =
|
|
9
|
+
WebAuthn::Credential.options_for_create(
|
|
10
|
+
user: {
|
|
11
|
+
id: resource.webauthn_id,
|
|
12
|
+
name: resource_human_palatable_identifier
|
|
13
|
+
},
|
|
14
|
+
exclude: resource.passkeys.pluck(:external_id),
|
|
15
|
+
authenticator_selection: {
|
|
16
|
+
resident_key: "required",
|
|
17
|
+
user_verification: "required"
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Store challenge in session for later verification
|
|
22
|
+
session[:webauthn_challenge] = passkey_options.challenge
|
|
23
|
+
|
|
24
|
+
render json: passkey_options
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def authenticate_scope!
|
|
30
|
+
send(:"authenticate_#{resource_name}!", force: true)
|
|
31
|
+
self.resource = send(:"current_#{resource_name}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def resource_human_palatable_identifier
|
|
35
|
+
authentication_keys = resource.class.authentication_keys
|
|
36
|
+
authentication_keys = authentication_keys.keys if authentication_keys.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
authentication_keys.filter_map { |authentication_key| resource.public_send(authentication_key) }.first
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
class SecurityKeyAuthenticationOptionsController < DeviseController
|
|
5
|
+
before_action :set_resource
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
security_key_authentication_options =
|
|
9
|
+
WebAuthn::Credential.options_for_get(
|
|
10
|
+
allow: @resource.webauthn_credentials.pluck(:external_id),
|
|
11
|
+
user_verification: "discouraged"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Store challenge in session for later verification
|
|
15
|
+
session[:two_factor_authentication_challenge] = security_key_authentication_options.challenge
|
|
16
|
+
|
|
17
|
+
render json: security_key_authentication_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def set_resource
|
|
23
|
+
@resource = resource_class.find(session[:current_authentication_resource_id])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
class SecurityKeyRegistrationOptionsController < DeviseController
|
|
5
|
+
before_action :authenticate_scope!
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
create_security_key_options =
|
|
9
|
+
WebAuthn::Credential.options_for_create(
|
|
10
|
+
user: {
|
|
11
|
+
id: resource.webauthn_id,
|
|
12
|
+
name: resource_human_palatable_identifier
|
|
13
|
+
},
|
|
14
|
+
exclude: resource.webauthn_credentials.pluck(:external_id),
|
|
15
|
+
authenticator_selection: {
|
|
16
|
+
resident_key: "discouraged",
|
|
17
|
+
user_verification: "discouraged"
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Store challenge in session for later verification
|
|
22
|
+
session[:webauthn_challenge] = create_security_key_options.challenge
|
|
23
|
+
|
|
24
|
+
render json: create_security_key_options
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def authenticate_scope!
|
|
30
|
+
send(:"authenticate_#{resource_name}!", force: true)
|
|
31
|
+
self.resource = send(:"current_#{resource_name}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def resource_human_palatable_identifier
|
|
35
|
+
authentication_keys = resource.class.authentication_keys
|
|
36
|
+
authentication_keys = authentication_keys.keys if authentication_keys.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
authentication_keys.filter_map { |authentication_key| resource.public_send(authentication_key) }.first
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
<% end %>
|
|
25
25
|
|
|
26
|
-
<%=
|
|
26
|
+
<%= login_with_passkey_form_for(resource_name) do |form| %>
|
|
27
|
+
<%= form.submit "Log in with passkeys" %>
|
|
28
|
+
<% end %>
|
|
27
29
|
|
|
28
30
|
<%= render "devise/shared/links" %>
|
data/gemfiles/devise_5_0.gemfile
CHANGED
|
@@ -12,12 +12,12 @@ gem "pry-byebug", "~> 3.10"
|
|
|
12
12
|
gem "puma", "~> 6.6"
|
|
13
13
|
gem "rails", ">= 7.1"
|
|
14
14
|
gem "rspec-rails", ">= 7.1"
|
|
15
|
+
gem "rspec-retry"
|
|
15
16
|
gem "rubocop", "~> 1.79"
|
|
16
17
|
gem "rubocop-rails", "~> 2.32"
|
|
17
18
|
gem "rubocop-rspec", "~> 3.6"
|
|
18
19
|
gem "selenium-webdriver"
|
|
19
20
|
gem "sqlite3", ">= 1.6", "!= 1.7.0", "!= 1.7.1", "!= 1.7.2", "!= 1.7.3"
|
|
20
|
-
gem "stimulus-rails", "~> 1.3"
|
|
21
21
|
|
|
22
22
|
install_if -> { RUBY_VERSION < "3.0" } do
|
|
23
23
|
gem "rack", "~> 2.2"
|
data/gemfiles/rails_7_1.gemfile
CHANGED
|
@@ -12,13 +12,12 @@ gem "pry-byebug", "~> 3.10"
|
|
|
12
12
|
gem "puma", "~> 6.6"
|
|
13
13
|
gem "rails", "~> 7.1.x"
|
|
14
14
|
gem "rspec-rails", "~> 7.1"
|
|
15
|
+
gem "rspec-retry"
|
|
15
16
|
gem "rubocop", "~> 1.79"
|
|
16
17
|
gem "rubocop-rails", "~> 2.32"
|
|
17
18
|
gem "rubocop-rspec", "~> 3.6"
|
|
18
19
|
gem "selenium-webdriver"
|
|
19
20
|
gem "sqlite3", "~> 1.7"
|
|
20
|
-
gem "stimulus-rails", "~> 1.3"
|
|
21
|
-
gem "psych", "~> 4.0"
|
|
22
21
|
gem "rack", "~> 2.2"
|
|
23
22
|
|
|
24
23
|
gemspec path: "../"
|
data/gemfiles/rails_7_2.gemfile
CHANGED
|
@@ -12,11 +12,11 @@ gem "pry-byebug", "~> 3.11"
|
|
|
12
12
|
gem "puma", "~> 6.6"
|
|
13
13
|
gem "rails", "~> 7.2.x"
|
|
14
14
|
gem "rspec-rails", "~> 8.0"
|
|
15
|
+
gem "rspec-retry"
|
|
15
16
|
gem "rubocop", "~> 1.79"
|
|
16
17
|
gem "rubocop-rails", "~> 2.32"
|
|
17
18
|
gem "rubocop-rspec", "~> 3.6"
|
|
18
19
|
gem "selenium-webdriver"
|
|
19
20
|
gem "sqlite3", "~> 2.7"
|
|
20
|
-
gem "stimulus-rails", "~> 1.3"
|
|
21
21
|
|
|
22
22
|
gemspec path: "../"
|
data/gemfiles/rails_8_0.gemfile
CHANGED
|
@@ -12,11 +12,11 @@ gem "pry-byebug", "~> 3.11"
|
|
|
12
12
|
gem "puma", "~> 6.6"
|
|
13
13
|
gem "rails", "~> 8.0.x"
|
|
14
14
|
gem "rspec-rails", "~> 8.0"
|
|
15
|
+
gem "rspec-retry"
|
|
15
16
|
gem "rubocop", "~> 1.79"
|
|
16
17
|
gem "rubocop-rails", "~> 2.32"
|
|
17
18
|
gem "rubocop-rspec", "~> 3.6"
|
|
18
19
|
gem "selenium-webdriver"
|
|
19
20
|
gem "sqlite3", "~> 2.7"
|
|
20
|
-
gem "stimulus-rails", "~> 1.3"
|
|
21
21
|
|
|
22
22
|
gemspec path: "../"
|
data/gemfiles/rails_8_1.gemfile
CHANGED
|
@@ -12,11 +12,11 @@ gem "pry-byebug", "~> 3.11"
|
|
|
12
12
|
gem "puma", "~> 6.6"
|
|
13
13
|
gem "rails", "~> 8.1.x"
|
|
14
14
|
gem "rspec-rails", "~> 8.0"
|
|
15
|
+
gem "rspec-retry"
|
|
15
16
|
gem "rubocop", "~> 1.79"
|
|
16
17
|
gem "rubocop-rails", "~> 2.32"
|
|
17
18
|
gem "rubocop-rspec", "~> 3.6"
|
|
18
19
|
gem "selenium-webdriver"
|
|
19
20
|
gem "sqlite3", "~> 2.7"
|
|
20
|
-
gem "stimulus-rails", "~> 1.3"
|
|
21
21
|
|
|
22
22
|
gemspec path: "../"
|
data/gemfiles/rails_edge.gemfile
CHANGED
|
@@ -12,11 +12,11 @@ gem "pry-byebug", "~> 3.11"
|
|
|
12
12
|
gem "puma", "~> 6.6"
|
|
13
13
|
gem "rails", branch: "main", git: "https://github.com/rails/rails"
|
|
14
14
|
gem "rspec-rails", "~> 8.0"
|
|
15
|
+
gem "rspec-retry"
|
|
15
16
|
gem "rubocop", "~> 1.79"
|
|
16
17
|
gem "rubocop-rails", "~> 2.32"
|
|
17
18
|
gem "rubocop-rspec", "~> 3.6"
|
|
18
19
|
gem "selenium-webdriver"
|
|
19
20
|
gem "sqlite3", "~> 2.7"
|
|
20
|
-
gem "stimulus-rails", "~> 1.3"
|
|
21
21
|
|
|
22
22
|
gemspec path: "../"
|
|
@@ -7,15 +7,19 @@ module Devise
|
|
|
7
7
|
passkey_param.present? && session[:authentication_challenge].present?
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def authenticate!
|
|
10
|
+
def authenticate! # rubocop:disable Metrics/AbcSize
|
|
11
11
|
passkey_from_params = WebAuthn::Credential.from_get(JSON.parse(passkey_param))
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
return fail!(:passkey_not_found) if passkey_from_params.user_handle.nil?
|
|
14
|
+
|
|
15
|
+
resource = resource_class.find_by(webauthn_id: passkey_from_params.user_handle)
|
|
16
|
+
stored_passkey = resource&.passkeys&.find_by(external_id: passkey_from_params.id)
|
|
13
17
|
|
|
14
18
|
return fail!(:passkey_not_found) if stored_passkey.blank?
|
|
15
19
|
|
|
16
20
|
verify_passkeys(passkey_from_params, stored_passkey)
|
|
17
21
|
|
|
18
|
-
resource
|
|
22
|
+
remember_me(resource)
|
|
19
23
|
success!(resource)
|
|
20
24
|
rescue WebAuthn::Error
|
|
21
25
|
fail!(:passkey_verification_failed)
|
|
@@ -40,8 +44,20 @@ module Devise
|
|
|
40
44
|
stored_passkey.update!(sign_count: passkey_from_params.sign_count)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
|
-
def
|
|
44
|
-
|
|
47
|
+
def remember_me(resource)
|
|
48
|
+
resource.remember_me = remember_me? if resource.respond_to?(:remember_me=)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def remember_me?
|
|
52
|
+
params_auth_hash.is_a?(Hash) && Devise::TRUE_VALUES.include?(params_auth_hash[:remember_me])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def params_auth_hash
|
|
56
|
+
params[scope]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resource_class
|
|
60
|
+
mapping.to
|
|
45
61
|
end
|
|
46
62
|
end
|
|
47
63
|
end
|
|
@@ -16,6 +16,9 @@ module Devise
|
|
|
16
16
|
stored_credential = resource&.webauthn_credentials&.find_by(external_id: credential_from_params.id)
|
|
17
17
|
|
|
18
18
|
return fail!(:webauthn_credential_not_found) if stored_credential.blank?
|
|
19
|
+
if user_handle_mismatch?(credential_from_params, resource)
|
|
20
|
+
return fail!(:webauthn_credential_verification_failed)
|
|
21
|
+
end
|
|
19
22
|
|
|
20
23
|
verify_credential(credential_from_params, stored_credential)
|
|
21
24
|
|
|
@@ -47,6 +50,11 @@ module Devise
|
|
|
47
50
|
stored_credential.update!(sign_count: credential_from_params.sign_count)
|
|
48
51
|
end
|
|
49
52
|
|
|
53
|
+
def user_handle_mismatch?(credential_from_params, resource)
|
|
54
|
+
credential_from_params.user_handle.present? &&
|
|
55
|
+
credential_from_params.user_handle != resource.webauthn_id
|
|
56
|
+
end
|
|
57
|
+
|
|
50
58
|
def resource_class
|
|
51
59
|
mapping.to
|
|
52
60
|
end
|
|
@@ -1,140 +1,57 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Metrics/ModuleLength
|
|
4
3
|
module Devise
|
|
5
4
|
module Webauthn
|
|
6
5
|
module CredentialsHelper
|
|
7
|
-
def passkey_creation_form_for(
|
|
6
|
+
def passkey_creation_form_for(resource_or_resource_name, **options, &block)
|
|
8
7
|
form_with(
|
|
9
|
-
url: passkeys_path(
|
|
10
|
-
method: :post,
|
|
11
|
-
class: form_classes
|
|
8
|
+
**options, url: passkeys_path(resource_or_resource_name), method: :post
|
|
12
9
|
) do |f|
|
|
13
|
-
tag.webauthn_create(data: {
|
|
10
|
+
tag.webauthn_create(data: { options_url: passkey_registration_options_path(resource_or_resource_name) }) do
|
|
14
11
|
concat f.hidden_field(:public_key_credential, data: { webauthn_target: "response" })
|
|
15
12
|
concat capture(f, &block)
|
|
16
13
|
end
|
|
17
14
|
end
|
|
18
15
|
end
|
|
19
16
|
|
|
20
|
-
def
|
|
17
|
+
def login_with_passkey_form_for(resource_or_resource_name, **options, &block)
|
|
18
|
+
scope = Devise::Mapping.find_scope!(resource_or_resource_name)
|
|
19
|
+
|
|
21
20
|
form_with(
|
|
22
|
-
url: session_path,
|
|
23
|
-
method: :post,
|
|
24
|
-
class: form_classes
|
|
21
|
+
**options, scope: scope, url: session_path(resource_or_resource_name), method: :post
|
|
25
22
|
) do |f|
|
|
26
|
-
tag.webauthn_get(data: {
|
|
27
|
-
concat
|
|
28
|
-
|
|
29
|
-
concat f.button(text, type: "submit", class: button_classes, &block)
|
|
23
|
+
tag.webauthn_get(data: { options_url: passkey_authentication_options_path(resource_or_resource_name) }) do
|
|
24
|
+
concat hidden_field_tag(:public_key_credential, nil, data: { webauthn_target: "response" })
|
|
25
|
+
concat capture(f, &block)
|
|
30
26
|
end
|
|
31
27
|
end
|
|
32
28
|
end
|
|
33
29
|
|
|
34
|
-
def security_key_creation_form_for(
|
|
30
|
+
def security_key_creation_form_for(resource_or_resource_name, **options, &block)
|
|
35
31
|
form_with(
|
|
36
|
-
url: second_factor_webauthn_credentials_path(
|
|
37
|
-
method: :post,
|
|
38
|
-
class: form_classes
|
|
32
|
+
**options, url: second_factor_webauthn_credentials_path(resource_or_resource_name), method: :post
|
|
39
33
|
) do |f|
|
|
40
|
-
tag.webauthn_create(
|
|
34
|
+
tag.webauthn_create(
|
|
35
|
+
data: { options_url: security_key_registration_options_path(resource_or_resource_name) }
|
|
36
|
+
) do
|
|
41
37
|
concat f.hidden_field(:public_key_credential, data: { webauthn_target: "response" })
|
|
42
38
|
concat capture(f, &block)
|
|
43
39
|
end
|
|
44
40
|
end
|
|
45
41
|
end
|
|
46
42
|
|
|
47
|
-
def
|
|
43
|
+
def login_with_security_key_form_for(resource_or_resource_name, **options, &block)
|
|
48
44
|
form_with(
|
|
49
|
-
url: two_factor_authentication_path(
|
|
50
|
-
method: :post,
|
|
51
|
-
class: form_classes
|
|
45
|
+
**options, url: two_factor_authentication_path(resource_or_resource_name), method: :post
|
|
52
46
|
) do |f|
|
|
53
|
-
tag.webauthn_get(data: {
|
|
47
|
+
tag.webauthn_get(data: {
|
|
48
|
+
options_url: security_key_authentication_options_path(resource_or_resource_name)
|
|
49
|
+
}) do
|
|
54
50
|
concat f.hidden_field(:public_key_credential, data: { webauthn_target: "response" })
|
|
55
|
-
concat f
|
|
51
|
+
concat capture(f, &block)
|
|
56
52
|
end
|
|
57
53
|
end
|
|
58
54
|
end
|
|
59
|
-
|
|
60
|
-
def create_passkey_options(resource)
|
|
61
|
-
@create_passkey_options ||= begin
|
|
62
|
-
options = WebAuthn::Credential.options_for_create(
|
|
63
|
-
user: {
|
|
64
|
-
id: resource.webauthn_id,
|
|
65
|
-
name: resource_human_palatable_identifier
|
|
66
|
-
},
|
|
67
|
-
exclude: resource.passkeys.pluck(:external_id),
|
|
68
|
-
authenticator_selection: {
|
|
69
|
-
resident_key: "required",
|
|
70
|
-
user_verification: "required"
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
# Store challenge in session for later verification
|
|
75
|
-
session[:webauthn_challenge] = options.challenge
|
|
76
|
-
|
|
77
|
-
options
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def passkey_authentication_options
|
|
82
|
-
@passkey_authentication_options ||= begin
|
|
83
|
-
options = WebAuthn::Credential.options_for_get(
|
|
84
|
-
user_verification: "required"
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
# Store challenge in session for later verification
|
|
88
|
-
session[:authentication_challenge] = options.challenge
|
|
89
|
-
|
|
90
|
-
options
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def create_security_key_options(resource)
|
|
95
|
-
@create_security_key_options ||= begin
|
|
96
|
-
options = WebAuthn::Credential.options_for_create(
|
|
97
|
-
user: {
|
|
98
|
-
id: resource.webauthn_id,
|
|
99
|
-
name: resource_human_palatable_identifier
|
|
100
|
-
},
|
|
101
|
-
exclude: resource.webauthn_credentials.pluck(:external_id),
|
|
102
|
-
authenticator_selection: {
|
|
103
|
-
resident_key: "discouraged",
|
|
104
|
-
user_verification: "discouraged"
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
# Store challenge in session for later verification
|
|
109
|
-
session[:webauthn_challenge] = options.challenge
|
|
110
|
-
|
|
111
|
-
options
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def security_key_authentication_options(resource)
|
|
116
|
-
@security_key_authentication_options ||= begin
|
|
117
|
-
options = WebAuthn::Credential.options_for_get(
|
|
118
|
-
allow: resource.webauthn_credentials.pluck(:external_id),
|
|
119
|
-
user_verification: "discouraged"
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
# Store challenge in session for later verification
|
|
123
|
-
session[:two_factor_authentication_challenge] = options.challenge
|
|
124
|
-
|
|
125
|
-
options
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
private
|
|
130
|
-
|
|
131
|
-
def resource_human_palatable_identifier
|
|
132
|
-
authentication_keys = resource.class.authentication_keys
|
|
133
|
-
authentication_keys = authentication_keys.keys if authentication_keys.is_a?(Hash)
|
|
134
|
-
|
|
135
|
-
authentication_keys.filter_map { |authentication_key| resource.public_send(authentication_key) }.first
|
|
136
|
-
end
|
|
137
55
|
end
|
|
138
56
|
end
|
|
139
57
|
end
|
|
140
|
-
# rubocop:enable Metrics/ModuleLength
|
|
@@ -7,6 +7,10 @@ module ActionDispatch
|
|
|
7
7
|
|
|
8
8
|
def devise_passkey_authentication(_mapping, controllers)
|
|
9
9
|
resources :passkeys, only: %i[new create destroy], controller: controllers[:passkeys]
|
|
10
|
+
|
|
11
|
+
resources :passkey_authentication_options, only: :index,
|
|
12
|
+
controller: controllers[:passkey_authentication_options]
|
|
13
|
+
resources :passkey_registration_options, only: :index, controller: controllers[:passkey_registration_options]
|
|
10
14
|
end
|
|
11
15
|
|
|
12
16
|
def devise_two_factor_authentication(_mapping, controllers)
|
|
@@ -17,6 +21,11 @@ module ActionDispatch
|
|
|
17
21
|
resources :second_factor_webauthn_credentials,
|
|
18
22
|
only: %i[new create update destroy],
|
|
19
23
|
controller: controllers[:second_factor_webauthn_credentials]
|
|
24
|
+
|
|
25
|
+
resources :security_key_authentication_options, only: %i[index],
|
|
26
|
+
controller: controllers[:security_key_authentication_options]
|
|
27
|
+
resources :security_key_registration_options, only: %i[index],
|
|
28
|
+
controller: controllers[:security_key_registration_options]
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
end
|
|
@@ -24,9 +24,13 @@ module Devise
|
|
|
24
24
|
{
|
|
25
25
|
passkeys: [nil],
|
|
26
26
|
passkey: [nil, :new],
|
|
27
|
+
passkey_authentication_options: [nil],
|
|
28
|
+
passkey_registration_options: [nil],
|
|
27
29
|
two_factor_authentication: [nil, :new],
|
|
28
30
|
second_factor_webauthn_credentials: [nil],
|
|
29
|
-
second_factor_webauthn_credential: [nil, :new]
|
|
31
|
+
second_factor_webauthn_credential: [nil, :new],
|
|
32
|
+
security_key_authentication_options: [nil],
|
|
33
|
+
security_key_registration_options: [nil]
|
|
30
34
|
}.each do |route, actions|
|
|
31
35
|
%i[path url].each do |path_or_url|
|
|
32
36
|
actions.each do |action|
|
|
@@ -9,6 +9,10 @@ module Devise
|
|
|
9
9
|
passkeys
|
|
10
10
|
second_factor_webauthn_credentials
|
|
11
11
|
two_factor_authentications
|
|
12
|
+
passkey_authentication_options
|
|
13
|
+
passkey_registration_options
|
|
14
|
+
security_key_authentication_options
|
|
15
|
+
security_key_registration_options
|
|
12
16
|
].freeze
|
|
13
17
|
|
|
14
18
|
desc "Create inherited Devise::Webauthn controllers in your app/controllers folder."
|
|
@@ -16,10 +20,13 @@ module Devise
|
|
|
16
20
|
source_root File.expand_path("templates/controllers", __dir__)
|
|
17
21
|
argument :scope, required: true,
|
|
18
22
|
desc: "The scope to create controllers in, e.g. users, admins"
|
|
23
|
+
class_option :controllers, aliases: "-c", type: :array,
|
|
24
|
+
desc: "Select specific controllers to generate (#{CONTROLLERS.join(', ')})"
|
|
19
25
|
|
|
20
26
|
def create_controllers
|
|
21
27
|
@scope_prefix = scope.blank? ? "" : "#{scope.camelize}::"
|
|
22
|
-
|
|
28
|
+
controllers = options[:controllers] || CONTROLLERS
|
|
29
|
+
controllers.each do |name|
|
|
23
30
|
template "#{name}_controller.rb",
|
|
24
31
|
"app/controllers/#{scope}/#{name}_controller.rb"
|
|
25
32
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddWebauthnIdTo<%= user_table_name.camelize %> < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
4
|
+
def up
|
|
5
|
+
add_column :<%= user_table_name %>, :webauthn_id, :string
|
|
6
|
+
add_index :<%= user_table_name %>, :webauthn_id, unique: true
|
|
7
|
+
|
|
8
|
+
# WARNING: The code below backfills webauthn_id for all existing records
|
|
9
|
+
# one row at a time. For larger tables, consider removing it and running
|
|
10
|
+
# the backfill separately (e.g., in a background job or maintenance task).
|
|
11
|
+
#
|
|
12
|
+
# Worth noting: PostgreSQL and MySQL support single-query backfills:
|
|
13
|
+
#
|
|
14
|
+
# PostgreSQL:
|
|
15
|
+
# UPDATE <%= user_table_name %> SET webauthn_id = encode(gen_random_bytes(64), 'base64') WHERE webauthn_id IS NULL
|
|
16
|
+
#
|
|
17
|
+
# MySQL:
|
|
18
|
+
# UPDATE <%= user_table_name %> SET webauthn_id = TO_BASE64(RANDOM_BYTES(64)) WHERE webauthn_id IS NULL
|
|
19
|
+
#
|
|
20
|
+
execute("SELECT id FROM <%= user_table_name %> WHERE webauthn_id IS NULL").each do |row|
|
|
21
|
+
webauthn_id = WebAuthn.generate_user_id
|
|
22
|
+
execute(ActiveRecord::Base.sanitize_sql_array(
|
|
23
|
+
["UPDATE <%= user_table_name %> SET webauthn_id = ? WHERE id = ?", webauthn_id, row["id"]]
|
|
24
|
+
))
|
|
25
|
+
end
|
|
26
|
+
# Note: if your application creates records using methods that skip
|
|
27
|
+
# callbacks (e.g., insert_all), consider adding a database default
|
|
28
|
+
# to ensure webauthn_id is always set. For example, in PostgreSQL:
|
|
29
|
+
#
|
|
30
|
+
# change_column_default :<%= user_table_name %>, :webauthn_id, from: nil, to: -> { "encode(gen_random_bytes(64), 'base64')" }
|
|
31
|
+
#
|
|
32
|
+
# For the same reason, you may want to add a NOT NULL constraint in a
|
|
33
|
+
# separate migration after the backfill is complete:
|
|
34
|
+
#
|
|
35
|
+
# change_column_null :<%= user_table_name %>, :webauthn_id, false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def down
|
|
39
|
+
remove_column :<%= user_table_name %>, :webauthn_id
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -6,17 +6,22 @@ require "rails/generators/active_record"
|
|
|
6
6
|
module Devise
|
|
7
7
|
module Webauthn
|
|
8
8
|
class WebauthnIdGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
9
11
|
hide!
|
|
10
12
|
namespace "devise:webauthn:webauthn_id"
|
|
11
13
|
|
|
14
|
+
source_root File.expand_path("templates", __dir__)
|
|
15
|
+
|
|
12
16
|
desc "Add webauthn_id field to User model"
|
|
13
17
|
class_option :resource_name, type: :string, default: "user", desc: "The resource name for Devise (default: user)"
|
|
14
18
|
|
|
19
|
+
def self.next_migration_number(dirname)
|
|
20
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
21
|
+
end
|
|
22
|
+
|
|
15
23
|
def generate_migration
|
|
16
|
-
|
|
17
|
-
"add_webauthn_id_to_#{user_table_name}",
|
|
18
|
-
"webauthn_id:string:uniq"
|
|
19
|
-
]
|
|
24
|
+
migration_template "add_webauthn_id.rb.erb", "db/migrate/add_webauthn_id_to_#{user_table_name}.rb"
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
def show_instructions
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise-webauthn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cedarcode
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- ".gitignore"
|
|
51
51
|
- ".rspec"
|
|
52
52
|
- ".rubocop.yml"
|
|
53
|
+
- ".rubocop_todo.yml"
|
|
53
54
|
- ".ruby-version"
|
|
54
55
|
- Appraisals
|
|
55
56
|
- CHANGELOG.md
|
|
@@ -59,8 +60,12 @@ files:
|
|
|
59
60
|
- README.md
|
|
60
61
|
- Rakefile
|
|
61
62
|
- app/assets/javascript/devise/webauthn.js
|
|
63
|
+
- app/controllers/devise/passkey_authentication_options_controller.rb
|
|
64
|
+
- app/controllers/devise/passkey_registration_options_controller.rb
|
|
62
65
|
- app/controllers/devise/passkeys_controller.rb
|
|
63
66
|
- app/controllers/devise/second_factor_webauthn_credentials_controller.rb
|
|
67
|
+
- app/controllers/devise/security_key_authentication_options_controller.rb
|
|
68
|
+
- app/controllers/devise/security_key_registration_options_controller.rb
|
|
64
69
|
- app/controllers/devise/two_factor_authentications_controller.rb
|
|
65
70
|
- app/views/devise/passkeys/new.html.erb
|
|
66
71
|
- app/views/devise/second_factor_webauthn_credentials/new.html.erb
|
|
@@ -96,12 +101,17 @@ files:
|
|
|
96
101
|
- lib/generators/devise/webauthn/install/templates/webauthn.rb
|
|
97
102
|
- lib/generators/devise/webauthn/javascript_configuration/javascript_configuration_generator.rb
|
|
98
103
|
- lib/generators/devise/webauthn/templates/controllers/README
|
|
104
|
+
- lib/generators/devise/webauthn/templates/controllers/passkey_authentication_options_controller.rb.tt
|
|
105
|
+
- lib/generators/devise/webauthn/templates/controllers/passkey_registration_options_controller.rb.tt
|
|
99
106
|
- lib/generators/devise/webauthn/templates/controllers/passkeys_controller.rb.tt
|
|
100
107
|
- lib/generators/devise/webauthn/templates/controllers/second_factor_webauthn_credentials_controller.rb.tt
|
|
108
|
+
- lib/generators/devise/webauthn/templates/controllers/security_key_authentication_options_controller.rb.tt
|
|
109
|
+
- lib/generators/devise/webauthn/templates/controllers/security_key_registration_options_controller.rb.tt
|
|
101
110
|
- lib/generators/devise/webauthn/templates/controllers/two_factor_authentications_controller.rb.tt
|
|
102
111
|
- lib/generators/devise/webauthn/views_generator.rb
|
|
103
112
|
- lib/generators/devise/webauthn/webauthn_credential_model/templates/webauthn_credential_migration.rb.erb
|
|
104
113
|
- lib/generators/devise/webauthn/webauthn_credential_model/webauthn_credential_model_generator.rb
|
|
114
|
+
- lib/generators/devise/webauthn/webauthn_id/templates/add_webauthn_id.rb.erb
|
|
105
115
|
- lib/generators/devise/webauthn/webauthn_id/webauthn_id_generator.rb
|
|
106
116
|
homepage: https://github.com/cedarcode/devise-webauthn
|
|
107
117
|
licenses:
|