practical 0.1.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 +7 -0
- data/README.md +37 -0
- data/Rakefile +10 -0
- data/app/components/practical/views/base_component.rb +6 -0
- data/app/components/practical/views/button_component.rb +27 -0
- data/app/components/practical/views/datatable/filter_applied.rb +25 -0
- data/app/components/practical/views/datatable/filter_section_component.html.erb +9 -0
- data/app/components/practical/views/datatable/filter_section_component.rb +19 -0
- data/app/components/practical/views/datatable/sort_link_component.rb +48 -0
- data/app/components/practical/views/datatable.rb +36 -0
- data/app/components/practical/views/flash_messages_component.rb +65 -0
- data/app/components/practical/views/form/error_list_component.rb +15 -0
- data/app/components/practical/views/form/error_list_item_component.rb +20 -0
- data/app/components/practical/views/form/error_list_item_template_component.rb +9 -0
- data/app/components/practical/views/form/fallback_errors_section_component.html.erb +7 -0
- data/app/components/practical/views/form/fallback_errors_section_component.rb +21 -0
- data/app/components/practical/views/form/field_errors_component.rb +28 -0
- data/app/components/practical/views/form/field_title_component.rb +23 -0
- data/app/components/practical/views/form/fieldset_title_component.rb +20 -0
- data/app/components/practical/views/form/input_component.html.erb +7 -0
- data/app/components/practical/views/form/input_component.rb +22 -0
- data/app/components/practical/views/form/option_label_component.rb +21 -0
- data/app/components/practical/views/form/practical_editor_component.rb +26 -0
- data/app/components/practical/views/form/required_radio_collection_wrapper_component.rb +23 -0
- data/app/components/practical/views/form_wrapper.rb +21 -0
- data/app/components/practical/views/icon_component.rb +36 -0
- data/app/components/practical/views/icon_for_file_extension_component.rb +53 -0
- data/app/components/practical/views/modal_dialog_component.html.erb +10 -0
- data/app/components/practical/views/modal_dialog_component.rb +16 -0
- data/app/components/practical/views/navigation/breadcrumb_item_component.rb +20 -0
- data/app/components/practical/views/navigation/breadcrumbs_component.html.erb +31 -0
- data/app/components/practical/views/navigation/breadcrumbs_component.rb +41 -0
- data/app/components/practical/views/navigation/navigation_link_component.rb +39 -0
- data/app/components/practical/views/navigation/pagination/goto_form_component.html.erb +31 -0
- data/app/components/practical/views/navigation/pagination/goto_form_component.rb +34 -0
- data/app/components/practical/views/navigation/pagination_component.html.erb +11 -0
- data/app/components/practical/views/navigation/pagination_component.rb +98 -0
- data/app/components/practical/views/open_dialog_button_component.rb +16 -0
- data/app/components/practical/views/page_component.html.erb +53 -0
- data/app/components/practical/views/page_component.rb +12 -0
- data/app/components/practical/views/relative_time_component.rb +13 -0
- data/app/components/practical/views/tiptap_document_component.rb +311 -0
- data/app/components/practical/views/toast_component.html.erb +26 -0
- data/app/components/practical/views/toast_component.rb +19 -0
- data/app/controllers/concerns/practical/auth/passkeys/emergency_registrations.rb +57 -0
- data/app/controllers/concerns/practical/auth/passkeys/web_authn_debug_context.rb +13 -0
- data/app/controllers/concerns/practical/views/flash_helpers.rb +37 -0
- data/app/controllers/concerns/practical/views/json_redirection.rb +7 -0
- data/app/lib/practical/defaults/shrine.rb +48 -0
- data/app/lib/practical/test/helpers/administrator/test_helpers.rb +7 -0
- data/app/lib/practical/test/helpers/extra_assertions.rb +7 -0
- data/app/lib/practical/test/helpers/flash_assertions.rb +8 -0
- data/app/lib/practical/test/helpers/integration/assertions.rb +23 -0
- data/app/lib/practical/test/helpers/passkey/system/base.rb +52 -0
- data/app/lib/practical/test/helpers/passkey/system/rack_test.rb +45 -0
- data/app/lib/practical/test/helpers/passkey/system/selenium.rb +107 -0
- data/app/lib/practical/test/helpers/passkey/test_helper.rb +128 -0
- data/app/lib/practical/test/helpers/postmark.rb +11 -0
- data/app/lib/practical/test/helpers/relation_builder_assertions.rb +18 -0
- data/app/lib/practical/test/helpers/setup/debug.rb +8 -0
- data/app/lib/practical/test/helpers/setup/faker_seed_pinning.rb +8 -0
- data/app/lib/practical/test/helpers/setup/simplecov.rb +17 -0
- data/app/lib/practical/test/helpers/shrine/test_data.rb +101 -0
- data/app/lib/practical/test/helpers/spy_assertions.rb +7 -0
- data/app/lib/practical/test/helpers/system/assertions.rb +33 -0
- data/app/lib/practical/test/helpers/system/capybara_prep.rb +10 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/base.rb +372 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/self_service.rb +66 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/base.rb +119 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/no_self_destroy.rb +13 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/no_self_signup.rb +22 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_destroy.rb +134 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_signup.rb +221 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/update.rb +220 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/sessions/base.rb +108 -0
- data/app/lib/practical/test/shared/auth/passkeys/forms/emergency_registration.rb +82 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/emergency_registration/base.rb +89 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/emergency_registration/use_for_and_notify.rb +48 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/passkey.rb +101 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/resource_with_passkeys.rb +57 -0
- data/app/lib/practical/test/shared/auth/passkeys/policies/passkey.rb +18 -0
- data/app/lib/practical/test/shared/auth/passkeys/services/send_emergency_registration.rb +41 -0
- data/app/lib/practical/test/shared/models/normalized_email.rb +23 -0
- data/app/lib/practical/test/shared/models/user.rb +27 -0
- data/app/lib/practical/test/shared/models/utility/ip_address.rb +42 -0
- data/app/lib/practical/test/shared/models/utility/user_agent.rb +43 -0
- data/app/lib/practical/views/button/styling.rb +23 -0
- data/app/lib/practical/views/error_handling.rb +33 -0
- data/app/lib/practical/views/form_builders/base.rb +152 -0
- data/app/lib/practical/views/icon_set.rb +156 -0
- data/app/lib/practical/views/web_awesome/style_utility/appearance_variant.rb +19 -0
- data/app/lib/practical/views/web_awesome/style_utility/base.rb +21 -0
- data/app/lib/practical/views/web_awesome/style_utility/color_variant.rb +17 -0
- data/app/lib/practical/views/web_awesome/style_utility/size.rb +31 -0
- data/config/locales/auth.en.yml +38 -0
- data/config/locales/devise.passkeys.en.yml +18 -0
- data/config/locales/practical_framework.en.yml +9 -0
- data/config/routes.rb +4 -0
- data/db/seeds/setup.rb +13 -0
- data/db/seeds/users/default.rb +34 -0
- data/lib/generators/practical/test/helper/USAGE +8 -0
- data/lib/generators/practical/test/helper/helper_generator.rb +9 -0
- data/lib/generators/practical/test/helper/templates/helper.rb.tt +4 -0
- data/lib/generators/practical/test/shared_test/USAGE +9 -0
- data/lib/generators/practical/test/shared_test/shared_test_generator.rb +7 -0
- data/lib/generators/practical/test/shared_test/templates/shared_test.rb.tt +9 -0
- data/lib/generators/practical/views/component/USAGE +9 -0
- data/lib/generators/practical/views/component/component_generator.rb +20 -0
- data/lib/practical/framework/engine.rb +35 -0
- data/lib/practical/helpers/form_with_helper.rb +10 -0
- data/lib/practical/helpers/icon_helper.rb +18 -0
- data/lib/practical/helpers/text_helper.rb +20 -0
- data/lib/practical/helpers/translation_helper.rb +25 -0
- data/lib/practical/version.rb +5 -0
- data/lib/practical/views/element_helper.rb +48 -0
- data/lib/practical.rb +21 -0
- data/lib/tasks/practical/coverage.rake +19 -0
- data/lib/tasks/practical/framework_tasks.rake +6 -0
- metadata +303 -0
@@ -0,0 +1,372 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Auth::Passkeys::Controllers::EmergencyRegistration::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "show: renders successfully when given an emergency_registration token for the resource's emergency registration class" do
|
8
|
+
show_emergency_registration_action(token: valid_emergency_registration_token)
|
9
|
+
assert_response :success
|
10
|
+
end
|
11
|
+
|
12
|
+
test "show: raises ActiveSupport::MessageVerifier::InvalidSignature if an expired token is given" do
|
13
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
14
|
+
show_emergency_registration_action(token: expired_emergency_registration_token)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
test "show: raises ActiveSupport::MessageVerifier::InvalidSignature if a bad token is given" do
|
19
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
20
|
+
show_emergency_registration_action(token: SecureRandom.hex)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test "show: raises ActiveSupport::MessageVerifier::InvalidSignature if a raw emergency registration ID is given" do
|
25
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
26
|
+
show_emergency_registration_action(token: raw_emergency_registration_id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
test "new_create_challenge: renders successfully when given a valid emergency_registration token for the resource's emergency registration class" do
|
31
|
+
emergency_passkey_registration = valid_emergency_registration
|
32
|
+
assert_nil emergency_passkey_registration.used_at
|
33
|
+
|
34
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
35
|
+
|
36
|
+
get_new_challenge_action(token: token)
|
37
|
+
assert_response :ok
|
38
|
+
|
39
|
+
assert_passkey_registration_challenge(
|
40
|
+
data: response.parsed_body,
|
41
|
+
stored_challenge: expected_stored_challenge,
|
42
|
+
relying_party_data: expected_relying_party_data,
|
43
|
+
user_data: expected_user_data_for_challenge,
|
44
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "new_create_challenge: raises ActiveSupport::MessageVerifier::InvalidSignature if an expired token is given" do
|
49
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
50
|
+
get_new_challenge_action(token: expired_emergency_registration_token)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
test "new_create_challenge: raises ActiveSupport::MessageVerifier::InvalidSignature if a bad token is given" do
|
55
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
56
|
+
get_new_challenge_action(token: SecureRandom.hex)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
test "new_create_challenge: raises ActiveSupport::MessageVerifier::InvalidSignature if a raw emergency registration ID is given" do
|
61
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
62
|
+
get_new_challenge_action(token: raw_emergency_registration_id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
test "use: registers a new passkey for the owner, using the client details" do
|
67
|
+
emergency_passkey_registration = valid_emergency_registration
|
68
|
+
assert_nil emergency_passkey_registration.used_at
|
69
|
+
|
70
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
71
|
+
|
72
|
+
get_new_challenge_action(token: token)
|
73
|
+
assert_response :ok
|
74
|
+
|
75
|
+
assert_passkey_registration_challenge(
|
76
|
+
data: response.parsed_body,
|
77
|
+
stored_challenge: expected_stored_challenge,
|
78
|
+
relying_party_data: expected_relying_party_data,
|
79
|
+
user_data: expected_user_data_for_challenge,
|
80
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
81
|
+
)
|
82
|
+
|
83
|
+
challenge = expected_stored_challenge
|
84
|
+
client = webauthn_client
|
85
|
+
|
86
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
87
|
+
label = Faker::Computer.os
|
88
|
+
|
89
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: raw_credential)
|
90
|
+
|
91
|
+
assert_difference "#{passkey_class}.count", +1 do
|
92
|
+
use_emergency_registration_action(token: token, params: params)
|
93
|
+
assert_json_redirected_to expected_new_session_url
|
94
|
+
end
|
95
|
+
|
96
|
+
credential = hydrate_response_from_raw_credential(client: client, relying_party: webauthn_relying_party, raw_credential: raw_credential).credential
|
97
|
+
|
98
|
+
new_passkey = emergency_passkey_registration.reload.passkey
|
99
|
+
assert_equal label, new_passkey.label
|
100
|
+
assert_equal Base64.strict_encode64(credential.id), new_passkey.external_id
|
101
|
+
assert_not_nil new_passkey.public_key
|
102
|
+
assert_nil new_passkey.last_used_at
|
103
|
+
end
|
104
|
+
|
105
|
+
test "use: does not allow overriding who the passkey is registered for" do
|
106
|
+
old_owner = owner_instance
|
107
|
+
emergency_passkey_registration = valid_emergency_registration
|
108
|
+
assert_nil emergency_passkey_registration.used_at
|
109
|
+
|
110
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
111
|
+
|
112
|
+
get_new_challenge_action(token: token)
|
113
|
+
assert_response :ok
|
114
|
+
|
115
|
+
assert_passkey_registration_challenge(
|
116
|
+
data: response.parsed_body,
|
117
|
+
stored_challenge: expected_stored_challenge,
|
118
|
+
relying_party_data: expected_relying_party_data,
|
119
|
+
user_data: expected_user_data_for_challenge,
|
120
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
121
|
+
)
|
122
|
+
|
123
|
+
challenge = expected_stored_challenge
|
124
|
+
client = webauthn_client
|
125
|
+
|
126
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
127
|
+
label = Faker::Computer.os
|
128
|
+
|
129
|
+
params = params_that_try_to_override_owner_during_emergency_registration(label: label, raw_credential: raw_credential)
|
130
|
+
|
131
|
+
assert_difference "#{passkey_class}.count", +1 do
|
132
|
+
use_emergency_registration_action(token: token, params: params)
|
133
|
+
assert_json_redirected_to expected_new_session_url
|
134
|
+
end
|
135
|
+
|
136
|
+
credential = hydrate_response_from_raw_credential(client: client, relying_party: webauthn_relying_party, raw_credential: raw_credential).credential
|
137
|
+
|
138
|
+
new_passkey = emergency_passkey_registration.reload.passkey
|
139
|
+
assert_equal label, new_passkey.label
|
140
|
+
assert_equal Base64.strict_encode64(credential.id), new_passkey.external_id
|
141
|
+
assert_not_nil new_passkey.public_key
|
142
|
+
assert_nil new_passkey.last_used_at
|
143
|
+
|
144
|
+
assert_old_owner_owns_passkey(passkey: new_passkey)
|
145
|
+
end
|
146
|
+
|
147
|
+
test "use: raises ActiveRecord::RecordNotFound if an expired token is given" do
|
148
|
+
emergency_passkey_registration = valid_emergency_registration
|
149
|
+
assert_nil emergency_passkey_registration.used_at
|
150
|
+
|
151
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
152
|
+
|
153
|
+
get_new_challenge_action(token: token)
|
154
|
+
assert_response :ok
|
155
|
+
|
156
|
+
assert_passkey_registration_challenge(
|
157
|
+
data: response.parsed_body,
|
158
|
+
stored_challenge: expected_stored_challenge,
|
159
|
+
relying_party_data: expected_relying_party_data,
|
160
|
+
user_data: expected_user_data_for_challenge,
|
161
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
162
|
+
)
|
163
|
+
|
164
|
+
challenge = expected_stored_challenge
|
165
|
+
client = webauthn_client
|
166
|
+
|
167
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
168
|
+
label = Faker::Computer.os
|
169
|
+
|
170
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: raw_credential)
|
171
|
+
|
172
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
173
|
+
use_emergency_registration_action(token: expired_emergency_registration_token, params: params)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
test "use: raises ActiveRecord::RecordNotFound if a bad token is given" do
|
178
|
+
emergency_passkey_registration = valid_emergency_registration
|
179
|
+
assert_nil emergency_passkey_registration.used_at
|
180
|
+
|
181
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
182
|
+
|
183
|
+
get_new_challenge_action(token: token)
|
184
|
+
assert_response :ok
|
185
|
+
|
186
|
+
assert_passkey_registration_challenge(
|
187
|
+
data: response.parsed_body,
|
188
|
+
stored_challenge: expected_stored_challenge,
|
189
|
+
relying_party_data: expected_relying_party_data,
|
190
|
+
user_data: expected_user_data_for_challenge,
|
191
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
192
|
+
)
|
193
|
+
|
194
|
+
challenge = expected_stored_challenge
|
195
|
+
client = webauthn_client
|
196
|
+
|
197
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
198
|
+
label = Faker::Computer.os
|
199
|
+
|
200
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: raw_credential)
|
201
|
+
|
202
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
203
|
+
use_emergency_registration_action(token: SecureRandom.hex, params: params)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
test "use: raises ActiveRecord::RecordNotFound if a raw emergency registration ID is given" do
|
208
|
+
emergency_passkey_registration = valid_emergency_registration
|
209
|
+
assert_nil emergency_passkey_registration.used_at
|
210
|
+
|
211
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
212
|
+
|
213
|
+
get_new_challenge_action(token: token)
|
214
|
+
assert_response :ok
|
215
|
+
|
216
|
+
assert_passkey_registration_challenge(
|
217
|
+
data: response.parsed_body,
|
218
|
+
stored_challenge: expected_stored_challenge,
|
219
|
+
relying_party_data: expected_relying_party_data,
|
220
|
+
user_data: expected_user_data_for_challenge,
|
221
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
222
|
+
)
|
223
|
+
|
224
|
+
challenge = expected_stored_challenge
|
225
|
+
client = webauthn_client
|
226
|
+
|
227
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
228
|
+
label = Faker::Computer.os
|
229
|
+
|
230
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: raw_credential)
|
231
|
+
|
232
|
+
assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
|
233
|
+
use_emergency_registration_action(token: raw_emergency_registration_id, params: params)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
test "use: returns a unprocessable_entity with the PracticalFramework error JSON if the passkey label is missing" do
|
238
|
+
emergency_passkey_registration = valid_emergency_registration
|
239
|
+
assert_nil emergency_passkey_registration.used_at
|
240
|
+
|
241
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
242
|
+
|
243
|
+
get_new_challenge_action(token: token)
|
244
|
+
assert_response :ok
|
245
|
+
|
246
|
+
assert_passkey_registration_challenge(
|
247
|
+
data: response.parsed_body,
|
248
|
+
stored_challenge: expected_stored_challenge,
|
249
|
+
relying_party_data: expected_relying_party_data,
|
250
|
+
user_data: expected_user_data_for_challenge,
|
251
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
252
|
+
)
|
253
|
+
|
254
|
+
challenge = expected_stored_challenge
|
255
|
+
client = webauthn_client
|
256
|
+
|
257
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
258
|
+
|
259
|
+
params = params_for_using_emergency_passkey_registration(label: " ", raw_credential: raw_credential)
|
260
|
+
|
261
|
+
assert_no_difference "#{passkey_class}.count" do
|
262
|
+
use_emergency_registration_action(token: token, params: params)
|
263
|
+
end
|
264
|
+
|
265
|
+
assert_response :unprocessable_entity
|
266
|
+
|
267
|
+
assert_form_error_for_label(message: "can't be blank", type: :blank)
|
268
|
+
end
|
269
|
+
|
270
|
+
test "use: returns a unprocessable_entity with the PracticalFramework error JSON if the passkey challenge fails" do
|
271
|
+
emergency_passkey_registration = valid_emergency_registration
|
272
|
+
assert_nil emergency_passkey_registration.used_at
|
273
|
+
|
274
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
275
|
+
|
276
|
+
get_new_challenge_action(token: token)
|
277
|
+
assert_response :ok
|
278
|
+
|
279
|
+
assert_passkey_registration_challenge(
|
280
|
+
data: response.parsed_body,
|
281
|
+
stored_challenge: expected_stored_challenge,
|
282
|
+
relying_party_data: expected_relying_party_data,
|
283
|
+
user_data: expected_user_data_for_challenge,
|
284
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
285
|
+
)
|
286
|
+
|
287
|
+
challenge = SecureRandom.hex
|
288
|
+
client = webauthn_client
|
289
|
+
|
290
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
291
|
+
label = Faker::Computer.os
|
292
|
+
|
293
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: raw_credential)
|
294
|
+
|
295
|
+
assert_no_difference "#{passkey_class}.count" do
|
296
|
+
use_emergency_registration_action(token: token, params: params)
|
297
|
+
end
|
298
|
+
|
299
|
+
assert_response :unprocessable_entity
|
300
|
+
|
301
|
+
assert_form_error_for_credential(message: I18n.translate("devise.emergency_passkey_registrations.webauthn_challenge_verification_error"))
|
302
|
+
end
|
303
|
+
|
304
|
+
test "use: returns a unprocessable_entity with the PracticalFramework error JSON if the credential was missing" do
|
305
|
+
emergency_passkey_registration = valid_emergency_registration
|
306
|
+
assert_nil emergency_passkey_registration.used_at
|
307
|
+
|
308
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
309
|
+
|
310
|
+
get_new_challenge_action(token: token)
|
311
|
+
assert_response :ok
|
312
|
+
|
313
|
+
assert_passkey_registration_challenge(
|
314
|
+
data: response.parsed_body,
|
315
|
+
stored_challenge: expected_stored_challenge,
|
316
|
+
relying_party_data: expected_relying_party_data,
|
317
|
+
user_data: expected_user_data_for_challenge,
|
318
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
319
|
+
)
|
320
|
+
|
321
|
+
challenge = expected_stored_challenge
|
322
|
+
client = webauthn_client
|
323
|
+
|
324
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
325
|
+
label = Faker::Computer.os
|
326
|
+
|
327
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: nil)
|
328
|
+
|
329
|
+
assert_no_difference "#{passkey_class}.count" do
|
330
|
+
use_emergency_registration_action(token: token, params: params)
|
331
|
+
end
|
332
|
+
|
333
|
+
assert_response :unprocessable_entity
|
334
|
+
|
335
|
+
assert_form_error_for_credential(message: I18n.translate("devise.emergency_passkey_registrations.credential_missing_or_could_not_be_parsed"))
|
336
|
+
end
|
337
|
+
|
338
|
+
test "use: returns a unprocessable_entity with the PracticalFramework error JSON if the credential could not be parsed" do
|
339
|
+
emergency_passkey_registration = valid_emergency_registration
|
340
|
+
assert_nil emergency_passkey_registration.used_at
|
341
|
+
|
342
|
+
token = emergency_passkey_registration.generate_token_for(:emergency_registration)
|
343
|
+
|
344
|
+
get_new_challenge_action(token: token)
|
345
|
+
assert_response :ok
|
346
|
+
|
347
|
+
assert_passkey_registration_challenge(
|
348
|
+
data: response.parsed_body,
|
349
|
+
stored_challenge: expected_stored_challenge,
|
350
|
+
relying_party_data: expected_relying_party_data,
|
351
|
+
user_data: expected_user_data_for_challenge,
|
352
|
+
credentials_to_exclude: expected_credentials_to_exclude
|
353
|
+
)
|
354
|
+
|
355
|
+
challenge = expected_stored_challenge
|
356
|
+
client = webauthn_client
|
357
|
+
|
358
|
+
raw_credential = create_credential_and_return_payload_from_challenge(client: client, challenge: challenge)
|
359
|
+
label = Faker::Computer.os
|
360
|
+
|
361
|
+
params = params_for_using_emergency_passkey_registration(label: label, raw_credential: "blah")
|
362
|
+
|
363
|
+
assert_no_difference "#{passkey_class}.count" do
|
364
|
+
use_emergency_registration_action(token: token, params: params)
|
365
|
+
end
|
366
|
+
|
367
|
+
assert_response :unprocessable_entity
|
368
|
+
|
369
|
+
assert_form_error_for_credential(message: I18n.translate("devise.emergency_passkey_registrations.credential_missing_or_could_not_be_parsed"))
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/self_service.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Auth::Passkeys::Controllers::EmergencyRegistration::SelfService
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "new: renders successfully" do
|
8
|
+
get_new_registration_action
|
9
|
+
assert_response :success
|
10
|
+
end
|
11
|
+
|
12
|
+
test "create: calls service to send emergency passkey registration with the user_agent, ip_address, and found owner" do
|
13
|
+
owner = owner_instance
|
14
|
+
|
15
|
+
params = {
|
16
|
+
new_emergency_passkey_registration_form: {
|
17
|
+
email: owner.email
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
ip_address = Faker::Internet.ip_v6_address
|
22
|
+
user_agent = Faker::Internet.user_agent
|
23
|
+
|
24
|
+
env = {"REMOTE_ADDR" => ip_address, "User-Agent" => user_agent}
|
25
|
+
|
26
|
+
service_spy = Spy.on_instance_method(send_registration_service_class, run!: true)
|
27
|
+
|
28
|
+
request_emergency_registration_action(params: params, env: env)
|
29
|
+
|
30
|
+
assert_json_redirected_to expected_new_session_url
|
31
|
+
assert_flash_message(
|
32
|
+
type: :notice,
|
33
|
+
message: I18n.translate('emergency_passkey_registrations.sent_message'),
|
34
|
+
icon_name: "envelope-dot"
|
35
|
+
)
|
36
|
+
|
37
|
+
assert_times_called(spy: service_spy, times: 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "create: silently ignores bad email addresses" do
|
41
|
+
params = {
|
42
|
+
new_emergency_passkey_registration_form: {
|
43
|
+
email: "bad@example.com"
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
ip_address = Faker::Internet.ip_v6_address
|
48
|
+
user_agent = Faker::Internet.user_agent
|
49
|
+
|
50
|
+
env = {"REMOTE_ADDR" => ip_address, "User-Agent" => user_agent}
|
51
|
+
|
52
|
+
service_spy = Spy.on_instance_method(send_registration_service_class, run!: true)
|
53
|
+
|
54
|
+
request_emergency_registration_action(params: params, env: env)
|
55
|
+
|
56
|
+
assert_json_redirected_to expected_new_session_url
|
57
|
+
assert_flash_message(
|
58
|
+
type: :notice,
|
59
|
+
message: I18n.translate('emergency_passkey_registrations.sent_message'),
|
60
|
+
icon_name: "envelope-dot"
|
61
|
+
)
|
62
|
+
|
63
|
+
assert_times_called(spy: service_spy, times: 0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Auth::Passkeys::Controllers::Reauthentication::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "new_challenge: requires the resource to be authenticated" do
|
8
|
+
issue_new_challenge_action
|
9
|
+
assert_response :not_found
|
10
|
+
end
|
11
|
+
|
12
|
+
test "new_challenge: sets the session variable that stores the new challenge" do
|
13
|
+
sign_in_as_resource
|
14
|
+
issue_new_challenge_action
|
15
|
+
assert_response :ok
|
16
|
+
assert_equal get_session_challenge, response.parsed_body["challenge"]
|
17
|
+
end
|
18
|
+
|
19
|
+
test "new_challenge: overrides the session variable that stores the new challenge" do
|
20
|
+
sign_in_as_resource
|
21
|
+
issue_new_challenge_action
|
22
|
+
assert_response :ok
|
23
|
+
old_session_challenge = get_session_challenge
|
24
|
+
|
25
|
+
issue_new_challenge_action
|
26
|
+
assert_response :ok
|
27
|
+
|
28
|
+
assert_not_equal old_session_challenge, get_session_challenge
|
29
|
+
assert_not_equal old_session_challenge, response.parsed_body["challenge"]
|
30
|
+
end
|
31
|
+
|
32
|
+
test "reauthenticate: requires the resource to be authenticated" do
|
33
|
+
reauthenticate_action(params: {})
|
34
|
+
assert_response :not_found
|
35
|
+
end
|
36
|
+
|
37
|
+
test """reauthenticate:
|
38
|
+
- verifies that the given challenge matches what is in the session
|
39
|
+
- clears the challenge session variable
|
40
|
+
- returns the reauthentication_token
|
41
|
+
- stores the reauthentication_token in the session
|
42
|
+
""" do
|
43
|
+
sign_in_as_resource
|
44
|
+
issue_new_challenge_action
|
45
|
+
assert_response :ok
|
46
|
+
|
47
|
+
assert_passkey_authentication_challenge(
|
48
|
+
data: response.parsed_body,
|
49
|
+
stored_challenge: expected_stored_challenge,
|
50
|
+
credentials_to_allow: expected_credentials_to_allow
|
51
|
+
)
|
52
|
+
|
53
|
+
challenge = response.parsed_body["challenge"]
|
54
|
+
credential = get_credential_payload_from_challenge(client: client, challenge: challenge)
|
55
|
+
|
56
|
+
reauthenticate_action(params: {passkey_credential: credential.to_json})
|
57
|
+
assert_response :ok
|
58
|
+
|
59
|
+
assert_equal expected_stored_reauthentication_token, response.parsed_body["reauthentication_token"]
|
60
|
+
assert_nil get_session_challenge
|
61
|
+
end
|
62
|
+
|
63
|
+
test """reauthenticate:
|
64
|
+
- raises an error if the challenge does not match what is in the session
|
65
|
+
- clears the challenge session variable
|
66
|
+
- does not have a reauthentication_token in the session
|
67
|
+
""" do
|
68
|
+
sign_in_as_resource
|
69
|
+
issue_new_challenge_action
|
70
|
+
assert_response :ok
|
71
|
+
|
72
|
+
assert_passkey_authentication_challenge(
|
73
|
+
data: response.parsed_body,
|
74
|
+
stored_challenge: expected_stored_challenge,
|
75
|
+
credentials_to_allow: expected_credentials_to_allow
|
76
|
+
)
|
77
|
+
|
78
|
+
challenge = SecureRandom.hex
|
79
|
+
credential = get_credential_payload_from_challenge(client: client, challenge: challenge)
|
80
|
+
|
81
|
+
reauthenticate_action(params: {passkey_credential: credential.to_json})
|
82
|
+
assert_response :unauthorized
|
83
|
+
|
84
|
+
assert_equal I18n.translate("devise.failure.webauthn_challenge_verification_error"), response.parsed_body["error"]
|
85
|
+
assert_nil expected_stored_reauthentication_token
|
86
|
+
assert_nil get_session_challenge
|
87
|
+
assert_resource_not_signed_in
|
88
|
+
end
|
89
|
+
|
90
|
+
test """reauthenticate:
|
91
|
+
- raises an error if the credential is invalid
|
92
|
+
- clears the challenge session variable
|
93
|
+
- does not have a reauthentication_token in the session
|
94
|
+
""" do
|
95
|
+
sign_in_as_resource
|
96
|
+
issue_new_challenge_action
|
97
|
+
assert_response :ok
|
98
|
+
|
99
|
+
assert_passkey_authentication_challenge(
|
100
|
+
data: response.parsed_body,
|
101
|
+
stored_challenge: expected_stored_challenge,
|
102
|
+
credentials_to_allow: expected_credentials_to_allow
|
103
|
+
)
|
104
|
+
|
105
|
+
challenge = response.parsed_body["challenge"]
|
106
|
+
credential = get_credential_payload_from_challenge(client: client, challenge: challenge)
|
107
|
+
|
108
|
+
invalidate_all_credentials
|
109
|
+
|
110
|
+
reauthenticate_action(params: {passkey_credential: credential.to_json})
|
111
|
+
assert_response :unauthorized
|
112
|
+
|
113
|
+
assert_equal I18n.translate("devise.failure.stored_credential_not_found"), response.parsed_body["error"]
|
114
|
+
assert_nil expected_stored_reauthentication_token
|
115
|
+
assert_nil get_session_challenge
|
116
|
+
assert_resource_not_signed_in
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Auth::Passkeys::Controllers::Registrations::NoSelfDestroy
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "destroy action returns 501" do
|
8
|
+
sign_in_as_resource
|
9
|
+
destroy_registration_action
|
10
|
+
assert_response :not_implemented
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Auth::Passkeys::Controllers::Registrations::NoSelfSignup
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "new registration challenge action returns 501" do
|
8
|
+
new_registration_challenge_action
|
9
|
+
assert_response :not_implemented
|
10
|
+
end
|
11
|
+
|
12
|
+
test "new registration action returns 501" do
|
13
|
+
new_registration_action
|
14
|
+
assert_response :not_implemented
|
15
|
+
end
|
16
|
+
|
17
|
+
test "create registration action returns 501" do
|
18
|
+
create_registration_action
|
19
|
+
assert_response :not_implemented
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|