webauthn-rails 0.1.0 → 0.1.2
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/lib/generators/erb/webauthn_authentication/templates/app/views/passkeys/new.html.erb.tt +2 -2
- data/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/passkeys_controller_test.rb +16 -24
- data/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/second_factor_authentications_controller_test.rb +131 -0
- data/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/second_factor_webauthn_credentials_controller_test.rb +103 -0
- data/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb +8 -10
- data/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb +30 -30
- data/lib/generators/test_unit/webauthn_authentication/templates/test/test_helpers/virtual_authenticator_test_helper.rb +38 -0
- data/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb +2 -0
- data/lib/generators/webauthn_authentication/templates/app/controllers/passkeys_controller.rb +2 -2
- data/lib/generators/webauthn_authentication/templates/app/controllers/second_factor_authentications_controller.rb +3 -3
- data/lib/generators/webauthn_authentication/templates/app/controllers/second_factor_webauthn_credentials_controller.rb +2 -2
- data/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +7 -5
- data/lib/webauthn/rails/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8425de86118ec096864312cfb7b4ef98947437da28d88cdc5f12b0a4191aea9
|
|
4
|
+
data.tar.gz: 43f56151976719e1bb00200b3134cdde63b6786f5afaf225c274982e324258fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d44fa7ccb7a3adc9248a74ab73f2758e0b3fb3fd4b7b70e98cb8956fac70c9c84b3eec954f39e4f261f26d8a4e3112bc4e4a289cab19542b2c11bbe4d4462d99
|
|
7
|
+
data.tar.gz: 784af4bb7538eccd87f50c4ce61d57ee8e3d92b823586c0c08c3c24718405b21d4258de6f599eca3e33b143a24ba1384fbc1365ec8e357186483dd708ef7686b
|
data/lib/generators/erb/webauthn_authentication/templates/app/views/passkeys/new.html.erb.tt
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"webauthn-credentials-options-url-value": create_options_passkeys_path,
|
|
11
11
|
}) do |form| %>
|
|
12
12
|
<div class="field">
|
|
13
|
-
<%%= form.label :nickname, '
|
|
13
|
+
<%%= form.label :nickname, 'Passkey nickname' %>
|
|
14
14
|
<%%= form.text_field :nickname, required: true %>
|
|
15
15
|
</div>
|
|
16
16
|
<%%= form.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %>
|
|
17
|
-
<%%= form.submit "Add
|
|
17
|
+
<%%= form.submit "Add Passkey", disabled: true, data: { "webauthn-credentials-target": "submitButton" } %>
|
|
18
18
|
<%% end %>
|
|
@@ -7,7 +7,7 @@ class PasskeysControllerTest < ActionDispatch::IntegrationTest
|
|
|
7
7
|
@client = WebAuthn::FakeClient.new(WebAuthn.configuration.allowed_origins.first)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
test "
|
|
10
|
+
test "create_options" do
|
|
11
11
|
sign_in_as @user
|
|
12
12
|
post create_options_passkeys_url
|
|
13
13
|
|
|
@@ -20,14 +20,14 @@ class PasskeysControllerTest < ActionDispatch::IntegrationTest
|
|
|
20
20
|
assert_equal session[:current_registration][:challenge], body["challenge"]
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
test "
|
|
23
|
+
test "create_options unauthenticated" do
|
|
24
24
|
post create_options_passkeys_url
|
|
25
25
|
|
|
26
26
|
assert_response :redirect
|
|
27
27
|
assert_redirected_to new_session_url
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
test "
|
|
30
|
+
test "create" do
|
|
31
31
|
sign_in_as @user
|
|
32
32
|
|
|
33
33
|
post create_options_passkeys_url
|
|
@@ -48,11 +48,11 @@ class PasskeysControllerTest < ActionDispatch::IntegrationTest
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
assert_redirected_to root_path
|
|
51
|
-
assert_match (/
|
|
51
|
+
assert_match (/Passkey registered successfully/), flash[:notice]
|
|
52
52
|
assert_nil session[:current_registration]
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
test "
|
|
55
|
+
test "create with WebAuthn error" do
|
|
56
56
|
sign_in_as @user
|
|
57
57
|
|
|
58
58
|
post create_options_passkeys_url
|
|
@@ -77,34 +77,26 @@ class PasskeysControllerTest < ActionDispatch::IntegrationTest
|
|
|
77
77
|
assert_nil session[:current_registration]
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
test "
|
|
81
|
-
post passkeys_url
|
|
82
|
-
credential: {
|
|
83
|
-
nickname: "My Passkey",
|
|
84
|
-
public_key_credential: "{}"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
80
|
+
test "create unauthenticated" do
|
|
81
|
+
post passkeys_url
|
|
87
82
|
|
|
88
83
|
assert_response :redirect
|
|
89
84
|
assert_redirected_to new_session_url
|
|
90
85
|
end
|
|
91
86
|
|
|
92
|
-
test "
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
authentication_factor: 0
|
|
101
|
-
)
|
|
102
|
-
end
|
|
87
|
+
test "destroy" do
|
|
88
|
+
credential = WebauthnCredential.passkey.create!(
|
|
89
|
+
nickname: "My Passkey",
|
|
90
|
+
user: @user,
|
|
91
|
+
external_id: "external-id",
|
|
92
|
+
public_key: "public-key",
|
|
93
|
+
sign_count: 0,
|
|
94
|
+
)
|
|
103
95
|
|
|
104
96
|
sign_in_as @user
|
|
105
97
|
|
|
106
98
|
assert_difference("WebauthnCredential.count", -1) do
|
|
107
|
-
delete passkey_url(
|
|
99
|
+
delete passkey_url(credential)
|
|
108
100
|
end
|
|
109
101
|
assert_redirected_to root_path
|
|
110
102
|
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "webauthn/fake_client"
|
|
3
|
+
|
|
4
|
+
class SecondFactorAuthenticationsControllerTest < ActionDispatch::IntegrationTest
|
|
5
|
+
setup do
|
|
6
|
+
@user = users(:one)
|
|
7
|
+
@client = WebAuthn::FakeClient.new(WebAuthn.configuration.allowed_origins.first)
|
|
8
|
+
|
|
9
|
+
creation_options = WebAuthn::Credential.options_for_create(
|
|
10
|
+
user: { id: @user.webauthn_id, name: @user.email_address },
|
|
11
|
+
authenticator_selection: { resident_key: "discouraged", user_verification: "discouraged" }
|
|
12
|
+
)
|
|
13
|
+
create_options = @client.create(challenge: creation_options.challenge)
|
|
14
|
+
credential = WebAuthn::Credential.from_create(create_options)
|
|
15
|
+
|
|
16
|
+
WebauthnCredential.second_factor.create!(
|
|
17
|
+
nickname: "My Security Key",
|
|
18
|
+
user: @user,
|
|
19
|
+
external_id: credential.id,
|
|
20
|
+
public_key: credential.public_key,
|
|
21
|
+
sign_count: 0
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "get_options" do
|
|
26
|
+
post session_path, params: { email_address: @user.email_address, password: "password" }
|
|
27
|
+
|
|
28
|
+
post get_options_second_factor_authentication_url
|
|
29
|
+
|
|
30
|
+
assert_response :success
|
|
31
|
+
body = JSON.parse(response.body)
|
|
32
|
+
assert body["challenge"].present?
|
|
33
|
+
assert body["userVerification"] == "discouraged"
|
|
34
|
+
|
|
35
|
+
assert_equal session[:current_authentication][:challenge], body["challenge"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test "create" do
|
|
39
|
+
post session_path, params: { email_address: @user.email_address, password: "password" }
|
|
40
|
+
|
|
41
|
+
post get_options_second_factor_authentication_url
|
|
42
|
+
challenge = session[:current_authentication][:challenge]
|
|
43
|
+
|
|
44
|
+
public_key_credential = @client.get(challenge: challenge, user_verified: false)
|
|
45
|
+
|
|
46
|
+
post second_factor_authentication_url, params: {
|
|
47
|
+
session: {
|
|
48
|
+
public_key_credential: public_key_credential.to_json
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
assert_redirected_to root_path
|
|
53
|
+
assert_nil session[:current_authentication]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
test "create with a passkey" do
|
|
57
|
+
client = WebAuthn::FakeClient.new(WebAuthn.configuration.allowed_origins.first)
|
|
58
|
+
|
|
59
|
+
creation_options = WebAuthn::Credential.options_for_create(
|
|
60
|
+
user: { id: @user.webauthn_id, name: @user.email_address },
|
|
61
|
+
authenticator_selection: { resident_key: "discouraged", user_verification: "discouraged" }
|
|
62
|
+
)
|
|
63
|
+
create_options = client.create(challenge: creation_options.challenge)
|
|
64
|
+
credential = WebAuthn::Credential.from_create(create_options)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
WebauthnCredential.passkey.create!(
|
|
68
|
+
nickname: "My Security Key",
|
|
69
|
+
user: @user,
|
|
70
|
+
external_id: credential.id,
|
|
71
|
+
public_key: credential.public_key,
|
|
72
|
+
sign_count: 0
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
post session_path, params: { email_address: @user.email_address, password: "password" }
|
|
76
|
+
|
|
77
|
+
post get_options_second_factor_authentication_url
|
|
78
|
+
challenge = session[:current_authentication][:challenge]
|
|
79
|
+
|
|
80
|
+
public_key_credential = client.get(challenge: challenge, user_verified: false)
|
|
81
|
+
|
|
82
|
+
post second_factor_authentication_url, params: {
|
|
83
|
+
session: {
|
|
84
|
+
public_key_credential: public_key_credential.to_json
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
assert_redirected_to root_path
|
|
89
|
+
assert_nil session[:current_authentication]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
test "create with WebAuthn error" do
|
|
93
|
+
post session_path, params: { email_address: @user.email_address, password: "password" }
|
|
94
|
+
|
|
95
|
+
post get_options_second_factor_authentication_url
|
|
96
|
+
|
|
97
|
+
public_key_credential = @client.get(
|
|
98
|
+
user_verified: false
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
post second_factor_authentication_url, params: {
|
|
102
|
+
session: {
|
|
103
|
+
public_key_credential: public_key_credential.to_json
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
assert_redirected_to new_second_factor_authentication_path
|
|
108
|
+
assert_match (/Verification failed/), flash[:alert]
|
|
109
|
+
assert_nil session[:current_authentication]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
test "create with unrecognized credential" do
|
|
113
|
+
post session_path, params: { email_address: @user.email_address, password: "password" }
|
|
114
|
+
|
|
115
|
+
post get_options_second_factor_authentication_url
|
|
116
|
+
challenge = session[:current_authentication][:challenge]
|
|
117
|
+
|
|
118
|
+
public_key_credential = @client.get(challenge: challenge, user_verified: false)
|
|
119
|
+
public_key_credential["id"]= "invalid-id"
|
|
120
|
+
|
|
121
|
+
post second_factor_authentication_url, params: {
|
|
122
|
+
session: {
|
|
123
|
+
public_key_credential: public_key_credential.to_json
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
assert_redirected_to new_second_factor_authentication_path
|
|
128
|
+
assert_match (/Credential not recognized/), flash[:alert]
|
|
129
|
+
assert_nil session[:current_authentication]
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "webauthn/fake_client"
|
|
3
|
+
|
|
4
|
+
class SecondFactorWebauthnCredentialsControllerTest < ActionDispatch::IntegrationTest
|
|
5
|
+
setup do
|
|
6
|
+
@user = users(:one)
|
|
7
|
+
@client = WebAuthn::FakeClient.new(WebAuthn.configuration.allowed_origins.first)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
test "create_options" do
|
|
11
|
+
sign_in_as @user
|
|
12
|
+
post create_options_second_factor_webauthn_credentials_url
|
|
13
|
+
|
|
14
|
+
assert_response :success
|
|
15
|
+
body = JSON.parse(response.body)
|
|
16
|
+
assert body["challenge"].present?
|
|
17
|
+
assert body["authenticatorSelection"]["residentKey"] == "discouraged"
|
|
18
|
+
assert body["authenticatorSelection"]["userVerification"] == "discouraged"
|
|
19
|
+
|
|
20
|
+
assert_equal session[:current_registration][:challenge], body["challenge"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
test "create_options unauthenticated" do
|
|
24
|
+
post create_options_second_factor_webauthn_credentials_url
|
|
25
|
+
|
|
26
|
+
assert_response :redirect
|
|
27
|
+
assert_redirected_to new_session_url
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test "create" do
|
|
31
|
+
sign_in_as @user
|
|
32
|
+
|
|
33
|
+
post create_options_second_factor_webauthn_credentials_url
|
|
34
|
+
challenge = session[:current_registration][:challenge]
|
|
35
|
+
|
|
36
|
+
public_key_credential = @client.create(
|
|
37
|
+
challenge: challenge,
|
|
38
|
+
user_verified: false,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert_difference("WebauthnCredential.second_factor.count", 1) do
|
|
42
|
+
post second_factor_webauthn_credentials_url, params: {
|
|
43
|
+
credential: {
|
|
44
|
+
nickname: "My Security Key",
|
|
45
|
+
public_key_credential: public_key_credential.to_json
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
assert_redirected_to root_path
|
|
51
|
+
assert_match (/Security Key registered successfully/), flash[:notice]
|
|
52
|
+
assert_nil session[:current_registration]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "create with WebAuthn error" do
|
|
56
|
+
sign_in_as @user
|
|
57
|
+
|
|
58
|
+
post create_options_second_factor_webauthn_credentials_url
|
|
59
|
+
|
|
60
|
+
public_key_credential = @client.create(
|
|
61
|
+
user_verified: false,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
assert_no_difference("WebauthnCredential.count") do
|
|
65
|
+
post second_factor_webauthn_credentials_url, params: {
|
|
66
|
+
credential: {
|
|
67
|
+
nickname: "My Security Key",
|
|
68
|
+
public_key_credential: public_key_credential.to_json
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
assert_redirected_to new_second_factor_webauthn_credential_path
|
|
74
|
+
assert_match (/Verification failed/), flash[:alert]
|
|
75
|
+
assert_nil session[:current_registration]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
test "create unauthenticated" do
|
|
79
|
+
post second_factor_webauthn_credentials_url
|
|
80
|
+
|
|
81
|
+
assert_response :redirect
|
|
82
|
+
assert_redirected_to new_session_url
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
test "destroy" do
|
|
86
|
+
credential = WebauthnCredential.second_factor.create!(
|
|
87
|
+
user: @user,
|
|
88
|
+
nickname: "My Security Key",
|
|
89
|
+
external_id: "external_id",
|
|
90
|
+
public_key: "public_key",
|
|
91
|
+
sign_count: 0
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
sign_in_as @user
|
|
95
|
+
|
|
96
|
+
assert_difference("WebauthnCredential.second_factor.count", -1) do
|
|
97
|
+
delete second_factor_webauthn_credential_url(credential)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
assert_redirected_to root_path
|
|
101
|
+
assert_match (/Security Key deleted successfully/), flash[:notice]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -12,17 +12,16 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
12
12
|
create_options = @client.create(challenge: creation_options.challenge)
|
|
13
13
|
credential = WebAuthn::Credential.from_create(create_options)
|
|
14
14
|
|
|
15
|
-
WebauthnCredential.create!(
|
|
15
|
+
WebauthnCredential.passkey.create!(
|
|
16
16
|
nickname: "My Passkey",
|
|
17
17
|
user: @user,
|
|
18
18
|
external_id: credential.id,
|
|
19
19
|
public_key: credential.public_key,
|
|
20
20
|
sign_count: 0,
|
|
21
|
-
authentication_factor: 0
|
|
22
21
|
)
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
test "
|
|
24
|
+
test "get_options" do
|
|
26
25
|
post get_options_webauthn_session_url
|
|
27
26
|
|
|
28
27
|
assert_response :success
|
|
@@ -33,7 +32,7 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
33
32
|
assert_equal session[:current_authentication][:challenge], body["challenge"]
|
|
34
33
|
end
|
|
35
34
|
|
|
36
|
-
test "
|
|
35
|
+
test "create" do
|
|
37
36
|
post get_options_webauthn_session_url
|
|
38
37
|
challenge = session[:current_authentication][:challenge]
|
|
39
38
|
|
|
@@ -49,7 +48,7 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
49
48
|
assert_nil session[:current_authentication]
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
test "
|
|
51
|
+
test "create with WebAuthn error" do
|
|
53
52
|
post get_options_webauthn_session_url
|
|
54
53
|
challenge = session[:current_authentication][:challenge]
|
|
55
54
|
|
|
@@ -66,7 +65,7 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
66
65
|
assert_nil session[:current_authentication]
|
|
67
66
|
end
|
|
68
67
|
|
|
69
|
-
test "
|
|
68
|
+
test "create with unrecognized credential" do
|
|
70
69
|
post get_options_webauthn_session_url
|
|
71
70
|
challenge = session[:current_authentication][:challenge]
|
|
72
71
|
|
|
@@ -84,7 +83,7 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
84
83
|
assert_nil session[:current_authentication]
|
|
85
84
|
end
|
|
86
85
|
|
|
87
|
-
test "
|
|
86
|
+
test "create with a second factor credential" do
|
|
88
87
|
client = WebAuthn::FakeClient.new(WebAuthn.configuration.allowed_origins.first)
|
|
89
88
|
|
|
90
89
|
creation_options = WebAuthn::Credential.options_for_create(
|
|
@@ -93,13 +92,12 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
93
92
|
create_options = client.create(challenge: creation_options.challenge)
|
|
94
93
|
credential = WebAuthn::Credential.from_create(create_options)
|
|
95
94
|
|
|
96
|
-
WebauthnCredential.create!(
|
|
95
|
+
WebauthnCredential.second_factor.create!(
|
|
97
96
|
nickname: "Second Factor Key",
|
|
98
97
|
user: @user,
|
|
99
98
|
external_id: credential.id,
|
|
100
99
|
public_key: credential.public_key,
|
|
101
100
|
sign_count: 0,
|
|
102
|
-
authentication_factor: 1
|
|
103
101
|
)
|
|
104
102
|
|
|
105
103
|
post get_options_webauthn_session_url
|
|
@@ -118,7 +116,7 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest
|
|
|
118
116
|
assert_nil session[:current_authentication]
|
|
119
117
|
end
|
|
120
118
|
|
|
121
|
-
test "
|
|
119
|
+
test "destroy" do
|
|
122
120
|
delete webauthn_session_url
|
|
123
121
|
assert_redirected_to new_session_path
|
|
124
122
|
end
|
|
@@ -5,8 +5,7 @@ class ManageWebauthnCredentialsTest < ApplicationSystemTestCase
|
|
|
5
5
|
include VirtualAuthenticatorTestHelper
|
|
6
6
|
|
|
7
7
|
def setup
|
|
8
|
-
user = User.create!(email_address: "alice@example.com", password: "S3cr3tP@ssw0rd!")
|
|
9
|
-
sign_in_as(user)
|
|
8
|
+
@user = User.create!(email_address: "alice@example.com", password: "S3cr3tP@ssw0rd!")
|
|
10
9
|
@authenticator = add_virtual_authenticator
|
|
11
10
|
end
|
|
12
11
|
|
|
@@ -14,63 +13,64 @@ class ManageWebauthnCredentialsTest < ApplicationSystemTestCase
|
|
|
14
13
|
@authenticator.remove!
|
|
15
14
|
end
|
|
16
15
|
|
|
17
|
-
test "
|
|
18
|
-
|
|
16
|
+
test "adding a passkey" do
|
|
17
|
+
sign_in_as(@user)
|
|
19
18
|
|
|
19
|
+
visit new_passkey_path
|
|
20
|
+
fill_in("Passkey nickname", with: "Touch ID")
|
|
20
21
|
click_on "Add Passkey"
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
assert_selector "div", text: "Security Key registered successfully"
|
|
27
|
-
assert_selector "span", text: "Touch ID"
|
|
23
|
+
assert_current_path root_path
|
|
24
|
+
# Add custom assertions based on your application's behavior
|
|
25
|
+
# assert_text "Passkey registered successfully"
|
|
26
|
+
end
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
test "signing in with existing passkey" do
|
|
29
|
+
add_passkey_to_authenticator(@authenticator, @user)
|
|
31
30
|
|
|
31
|
+
visit new_session_path
|
|
32
32
|
click_on "Sign In with Passkey"
|
|
33
33
|
|
|
34
|
-
assert_current_path
|
|
35
|
-
|
|
34
|
+
assert_current_path root_path
|
|
35
|
+
# Add custom assertions based on your application's behavior
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
test "
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
click_on "Add Second Factor Key"
|
|
38
|
+
test "adding a 2FA WebAuthn credential" do
|
|
39
|
+
sign_in_as(@user)
|
|
42
40
|
|
|
41
|
+
visit new_second_factor_webauthn_credential_path
|
|
43
42
|
fill_in("Security Key nickname", with: "Touch ID")
|
|
44
43
|
click_on "Add Security Key"
|
|
45
44
|
|
|
46
|
-
assert_current_path
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
assert_current_path root_path
|
|
46
|
+
# Add custom assertions based on your application's behavior
|
|
47
|
+
# assert_text "Security Key registered successfully"
|
|
48
|
+
end
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
test "sign in with existing 2FA WebAuthn credential" do
|
|
51
|
+
add_security_key_to_authenticator(@authenticator, @user)
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
fill_in "
|
|
53
|
+
visit new_session_path
|
|
54
|
+
fill_in "email_address", with: @user.email_address
|
|
55
|
+
fill_in "password", with: @user.password
|
|
55
56
|
click_on "Sign in"
|
|
56
57
|
|
|
58
|
+
assert_current_path new_second_factor_authentication_path
|
|
57
59
|
assert_selector "h3", text: "Two-factor authentication"
|
|
58
60
|
click_on "Use Security Key"
|
|
59
61
|
|
|
60
|
-
assert_current_path
|
|
61
|
-
|
|
62
|
+
assert_current_path root_path
|
|
63
|
+
# Add custom assertions based on your application's behavior
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
private
|
|
65
67
|
|
|
66
68
|
def sign_in_as(user)
|
|
67
69
|
visit new_session_path
|
|
68
|
-
|
|
69
70
|
fill_in "email_address", with: user.email_address
|
|
70
71
|
fill_in "password", with: user.password
|
|
71
|
-
|
|
72
72
|
click_on "Sign in"
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
assert_current_path root_path
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -6,4 +6,42 @@ module VirtualAuthenticatorTestHelper
|
|
|
6
6
|
options.resident_key = true
|
|
7
7
|
page.driver.browser.add_virtual_authenticator(options)
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
def add_passkey_to_authenticator(authenticator, user)
|
|
11
|
+
add_credential_to_authenticator(authenticator, user, passkey: true)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add_security_key_to_authenticator(authenticator, user)
|
|
15
|
+
add_credential_to_authenticator(authenticator, user, passkey: false)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_credential_to_authenticator(authenticator, user, passkey:)
|
|
19
|
+
credential_id = SecureRandom.random_bytes(16)
|
|
20
|
+
encoded_credential_id = Base64.urlsafe_encode64(credential_id)
|
|
21
|
+
key = OpenSSL::PKey.generate_key("ED25519")
|
|
22
|
+
encoded_private_key = Base64.urlsafe_encode64(key.private_to_der)
|
|
23
|
+
|
|
24
|
+
cose_public_key = COSE::Key::OKP.from_pkey(OpenSSL::PKey.read(key.public_to_der))
|
|
25
|
+
cose_public_key.alg = -8
|
|
26
|
+
encoded_cose_public_key = Base64.urlsafe_encode64(cose_public_key.serialize)
|
|
27
|
+
|
|
28
|
+
credential_json = {
|
|
29
|
+
"credentialId" => encoded_credential_id,
|
|
30
|
+
"isResidentCredential" => passkey,
|
|
31
|
+
"rpId" => "localhost",
|
|
32
|
+
"privateKey" => encoded_private_key,
|
|
33
|
+
"signCount" => 0
|
|
34
|
+
}
|
|
35
|
+
credential_json["userHandle"] = user.webauthn_id if passkey
|
|
36
|
+
|
|
37
|
+
authenticator.add_credential(credential_json)
|
|
38
|
+
|
|
39
|
+
user.webauthn_credentials.create!(
|
|
40
|
+
nickname: "My Credential",
|
|
41
|
+
external_id: Base64.urlsafe_encode64(credential_id, padding: false),
|
|
42
|
+
public_key: encoded_cose_public_key,
|
|
43
|
+
sign_count: 0,
|
|
44
|
+
authentication_factor: passkey ? :first_factor : :second_factor
|
|
45
|
+
)
|
|
46
|
+
end
|
|
9
47
|
end
|
|
@@ -9,6 +9,8 @@ module TestUnit
|
|
|
9
9
|
def create_controller_test_files
|
|
10
10
|
template "test/controllers/passkeys_controller_test.rb"
|
|
11
11
|
template "test/controllers/webauthn_sessions_controller_test.rb"
|
|
12
|
+
template "test/controllers/second_factor_authentications_controller_test.rb"
|
|
13
|
+
template "test/controllers/second_factor_webauthn_credentials_controller_test.rb"
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def create_system_test_files
|
data/lib/generators/webauthn_authentication/templates/app/controllers/passkeys_controller.rb
CHANGED
|
@@ -35,7 +35,7 @@ class PasskeysController < ApplicationController
|
|
|
35
35
|
public_key: webauthn_credential.public_key,
|
|
36
36
|
sign_count: webauthn_credential.sign_count
|
|
37
37
|
)
|
|
38
|
-
redirect_to root_path, notice: "
|
|
38
|
+
redirect_to root_path, notice: "Passkey registered successfully"
|
|
39
39
|
else
|
|
40
40
|
flash[:alert] = "Error registering credential"
|
|
41
41
|
render :new
|
|
@@ -50,7 +50,7 @@ class PasskeysController < ApplicationController
|
|
|
50
50
|
def destroy
|
|
51
51
|
Current.user.passkeys.destroy(params[:id])
|
|
52
52
|
|
|
53
|
-
redirect_to root_path, notice: "
|
|
53
|
+
redirect_to root_path, notice: "Passkey deleted successfully"
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
private
|
|
@@ -35,9 +35,9 @@ class SecondFactorAuthenticationsController < ApplicationController
|
|
|
35
35
|
redirect_to after_authentication_url
|
|
36
36
|
rescue WebAuthn::Error => e
|
|
37
37
|
redirect_to new_second_factor_authentication_path, alert: "Verification failed: #{e.message}"
|
|
38
|
-
ensure
|
|
39
|
-
session.delete(:current_authentication)
|
|
40
38
|
end
|
|
39
|
+
ensure
|
|
40
|
+
session.delete(:current_authentication)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
private
|
|
@@ -57,6 +57,6 @@ class SecondFactorAuthenticationsController < ApplicationController
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def current_authentication_user_id
|
|
60
|
-
session
|
|
60
|
+
session.dig(:current_authentication, :user_id) || session.dig(:current_authentication, "user_id")
|
|
61
61
|
end
|
|
62
62
|
end
|
|
@@ -40,9 +40,9 @@ class SecondFactorWebauthnCredentialsController < ApplicationController
|
|
|
40
40
|
end
|
|
41
41
|
rescue WebAuthn::Error => e
|
|
42
42
|
redirect_to new_second_factor_webauthn_credential_path, alert: "Verification failed: #{e.message}"
|
|
43
|
-
ensure
|
|
44
|
-
session.delete(:current_registration)
|
|
45
43
|
end
|
|
44
|
+
ensure
|
|
45
|
+
session.delete(:current_registration)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def destroy
|
|
@@ -8,7 +8,11 @@ end
|
|
|
8
8
|
|
|
9
9
|
class WebauthnAuthenticationGenerator < ::Rails::Generators::Base
|
|
10
10
|
include ActiveRecord::Generators::Migration
|
|
11
|
-
|
|
11
|
+
if Rails.version >= "8.1"
|
|
12
|
+
include Rails::Generators::BundleHelper
|
|
13
|
+
else
|
|
14
|
+
include BundleHelper
|
|
15
|
+
end
|
|
12
16
|
|
|
13
17
|
source_root File.expand_path("../templates", __FILE__)
|
|
14
18
|
|
|
@@ -28,7 +32,7 @@ class WebauthnAuthenticationGenerator < ::Rails::Generators::Base
|
|
|
28
32
|
if File.exist?(File.join(destination_root, "app/controllers/sessions_controller.rb"))
|
|
29
33
|
gsub_file "app/controllers/sessions_controller.rb",
|
|
30
34
|
/^ def create.*?^ end/m,
|
|
31
|
-
<<~RUBY.
|
|
35
|
+
<<~RUBY.chomp.indent(2)
|
|
32
36
|
def create
|
|
33
37
|
if user = User.authenticate_by(params.permit(:email_address, :password))
|
|
34
38
|
if user.second_factor_enabled?
|
|
@@ -54,9 +58,7 @@ class WebauthnAuthenticationGenerator < ::Rails::Generators::Base
|
|
|
54
58
|
<<-RUBY.strip_heredoc.indent(4)
|
|
55
59
|
|
|
56
60
|
def require_no_authentication
|
|
57
|
-
if
|
|
58
|
-
redirect_to root_path
|
|
59
|
-
end
|
|
61
|
+
redirect_to root_path if find_session_by_cookie
|
|
60
62
|
end
|
|
61
63
|
RUBY
|
|
62
64
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: webauthn-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cedarcode
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|
|
@@ -41,6 +41,8 @@ files:
|
|
|
41
41
|
- lib/generators/erb/webauthn_authentication/templates/app/views/second_factor_webauthn_credentials/new.html.erb.tt
|
|
42
42
|
- lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb
|
|
43
43
|
- lib/generators/test_unit/webauthn_authentication/templates/test/controllers/passkeys_controller_test.rb
|
|
44
|
+
- lib/generators/test_unit/webauthn_authentication/templates/test/controllers/second_factor_authentications_controller_test.rb
|
|
45
|
+
- lib/generators/test_unit/webauthn_authentication/templates/test/controllers/second_factor_webauthn_credentials_controller_test.rb
|
|
44
46
|
- lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb
|
|
45
47
|
- lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb
|
|
46
48
|
- lib/generators/test_unit/webauthn_authentication/templates/test/test_helpers/virtual_authenticator_test_helper.rb
|