rodauth 1.20.0 → 2.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 +4 -4
- data/CHANGELOG +170 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +211 -79
- data/doc/account_expiration.rdoc +12 -26
- data/doc/active_sessions.rdoc +49 -0
- data/doc/audit_logging.rdoc +44 -0
- data/doc/base.rdoc +75 -128
- data/doc/change_login.rdoc +7 -14
- data/doc/change_password.rdoc +9 -13
- data/doc/change_password_notify.rdoc +2 -2
- data/doc/close_account.rdoc +9 -16
- data/doc/confirm_password.rdoc +12 -5
- data/doc/create_account.rdoc +11 -22
- data/doc/disallow_password_reuse.rdoc +6 -13
- data/doc/email_auth.rdoc +15 -14
- data/doc/email_base.rdoc +6 -15
- data/doc/http_basic_auth.rdoc +10 -1
- data/doc/internals.rdoc +1 -1
- data/doc/jwt.rdoc +22 -22
- data/doc/jwt_cors.rdoc +22 -0
- data/doc/jwt_refresh.rdoc +12 -8
- data/doc/lockout.rdoc +17 -15
- data/doc/login.rdoc +10 -2
- data/doc/login_password_requirements_base.rdoc +15 -37
- data/doc/logout.rdoc +2 -2
- data/doc/otp.rdoc +24 -19
- data/doc/password_complexity.rdoc +10 -26
- data/doc/password_expiration.rdoc +11 -25
- data/doc/password_grace_period.rdoc +16 -2
- data/doc/recovery_codes.rdoc +18 -12
- data/doc/release_notes/1.21.0.txt +12 -0
- data/doc/release_notes/1.22.0.txt +11 -0
- data/doc/release_notes/1.23.0.txt +32 -0
- data/doc/release_notes/2.0.0.txt +361 -0
- data/doc/release_notes/2.1.0.txt +31 -0
- data/doc/remember.rdoc +40 -64
- data/doc/reset_password.rdoc +12 -9
- data/doc/session_expiration.rdoc +1 -0
- data/doc/single_session.rdoc +16 -25
- data/doc/sms_codes.rdoc +24 -14
- data/doc/two_factor_base.rdoc +60 -22
- data/doc/verify_account.rdoc +14 -12
- data/doc/verify_account_grace_period.rdoc +6 -2
- data/doc/verify_login_change.rdoc +9 -8
- data/doc/webauthn.rdoc +115 -0
- data/doc/webauthn_login.rdoc +15 -0
- data/doc/webauthn_verify_account.rdoc +9 -0
- data/javascript/webauthn_auth.js +45 -0
- data/javascript/webauthn_setup.js +35 -0
- data/lib/roda/plugins/rodauth.rb +1 -1
- data/lib/rodauth.rb +32 -24
- data/lib/rodauth/features/account_expiration.rb +5 -5
- data/lib/rodauth/features/active_sessions.rb +160 -0
- data/lib/rodauth/features/audit_logging.rb +96 -0
- data/lib/rodauth/features/base.rb +144 -43
- data/lib/rodauth/features/change_password_notify.rb +2 -2
- data/lib/rodauth/features/confirm_password.rb +40 -2
- data/lib/rodauth/features/create_account.rb +8 -13
- data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
- data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
- data/lib/rodauth/features/email_auth.rb +30 -29
- data/lib/rodauth/features/email_base.rb +9 -4
- data/lib/rodauth/features/http_basic_auth.rb +55 -35
- data/lib/rodauth/features/jwt.rb +58 -10
- data/lib/rodauth/features/jwt_cors.rb +53 -0
- data/lib/rodauth/features/jwt_refresh.rb +3 -3
- data/lib/rodauth/features/lockout.rb +12 -14
- data/lib/rodauth/features/login.rb +54 -10
- data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
- data/lib/rodauth/features/otp.rb +72 -74
- data/lib/rodauth/features/password_complexity.rb +4 -11
- data/lib/rodauth/features/password_expiration.rb +2 -2
- data/lib/rodauth/features/password_grace_period.rb +17 -10
- data/lib/rodauth/features/recovery_codes.rb +49 -53
- data/lib/rodauth/features/remember.rb +11 -27
- data/lib/rodauth/features/reset_password.rb +26 -26
- data/lib/rodauth/features/session_expiration.rb +6 -4
- data/lib/rodauth/features/single_session.rb +7 -5
- data/lib/rodauth/features/sms_codes.rb +62 -71
- data/lib/rodauth/features/two_factor_base.rb +132 -28
- data/lib/rodauth/features/verify_account.rb +25 -21
- data/lib/rodauth/features/verify_account_grace_period.rb +20 -9
- data/lib/rodauth/features/verify_login_change.rb +12 -11
- data/lib/rodauth/features/webauthn.rb +507 -0
- data/lib/rodauth/features/webauthn_login.rb +70 -0
- data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
- data/lib/rodauth/version.rb +2 -2
- data/templates/button.str +1 -3
- data/templates/change-login.str +1 -2
- data/templates/change-password.str +3 -5
- data/templates/close-account.str +2 -2
- data/templates/confirm-password.str +1 -1
- data/templates/create-account.str +1 -1
- data/templates/email-auth-email.str +1 -1
- data/templates/email-auth-request-form.str +2 -3
- data/templates/email-auth.str +1 -1
- data/templates/global-logout-field.str +6 -0
- data/templates/login-confirm-field.str +2 -4
- data/templates/login-display.str +3 -2
- data/templates/login-field.str +2 -4
- data/templates/login-form-footer.str +6 -0
- data/templates/login-form.str +7 -0
- data/templates/login.str +1 -9
- data/templates/logout.str +1 -1
- data/templates/multi-phase-login.str +3 -0
- data/templates/otp-auth-code-field.str +5 -3
- data/templates/otp-auth.str +1 -1
- data/templates/otp-disable.str +1 -1
- data/templates/otp-setup.str +3 -3
- data/templates/password-confirm-field.str +2 -4
- data/templates/password-field.str +2 -4
- data/templates/recovery-auth.str +3 -6
- data/templates/recovery-codes.str +1 -1
- data/templates/remember.str +15 -20
- data/templates/reset-password-email.str +1 -1
- data/templates/reset-password-request.str +3 -3
- data/templates/reset-password.str +1 -2
- data/templates/sms-auth.str +1 -1
- data/templates/sms-code-field.str +5 -3
- data/templates/sms-confirm.str +1 -2
- data/templates/sms-disable.str +1 -2
- data/templates/sms-request.str +1 -1
- data/templates/sms-setup.str +6 -4
- data/templates/two-factor-auth.str +5 -0
- data/templates/two-factor-disable.str +6 -0
- data/templates/two-factor-manage.str +16 -0
- data/templates/unlock-account-email.str +1 -1
- data/templates/unlock-account-request.str +4 -4
- data/templates/unlock-account.str +1 -1
- data/templates/verify-account-email.str +1 -1
- data/templates/verify-account-resend.str +3 -3
- data/templates/verify-account.str +1 -2
- data/templates/verify-login-change-email.str +2 -1
- data/templates/verify-login-change.str +1 -1
- data/templates/webauthn-auth.str +11 -0
- data/templates/webauthn-remove.str +14 -0
- data/templates/webauthn-setup.str +12 -0
- metadata +89 -50
- data/Rakefile +0 -179
- data/doc/verify_change_login.rdoc +0 -11
- data/lib/rodauth/features/verify_change_login.rb +0 -20
- data/spec/account_expiration_spec.rb +0 -225
- data/spec/all.rb +0 -1
- data/spec/change_login_spec.rb +0 -156
- data/spec/change_password_notify_spec.rb +0 -33
- data/spec/change_password_spec.rb +0 -202
- data/spec/close_account_spec.rb +0 -162
- data/spec/confirm_password_spec.rb +0 -70
- data/spec/create_account_spec.rb +0 -127
- data/spec/disallow_common_passwords_spec.rb +0 -93
- data/spec/disallow_password_reuse_spec.rb +0 -179
- data/spec/email_auth_spec.rb +0 -285
- data/spec/http_basic_auth_spec.rb +0 -143
- data/spec/jwt_refresh_spec.rb +0 -256
- data/spec/jwt_spec.rb +0 -235
- data/spec/lockout_spec.rb +0 -250
- data/spec/login_spec.rb +0 -328
- data/spec/migrate/001_tables.rb +0 -184
- data/spec/migrate/002_account_password_hash_column.rb +0 -11
- data/spec/migrate_password/001_tables.rb +0 -73
- data/spec/migrate_travis/001_tables.rb +0 -141
- data/spec/password_complexity_spec.rb +0 -109
- data/spec/password_expiration_spec.rb +0 -244
- data/spec/password_grace_period_spec.rb +0 -93
- data/spec/remember_spec.rb +0 -451
- data/spec/reset_password_spec.rb +0 -229
- data/spec/rodauth_spec.rb +0 -343
- data/spec/session_expiration_spec.rb +0 -58
- data/spec/single_session_spec.rb +0 -127
- data/spec/spec_helper.rb +0 -327
- data/spec/two_factor_spec.rb +0 -1423
- data/spec/update_password_hash_spec.rb +0 -40
- data/spec/verify_account_grace_period_spec.rb +0 -171
- data/spec/verify_account_spec.rb +0 -240
- data/spec/verify_change_login_spec.rb +0 -46
- data/spec/verify_login_change_spec.rb +0 -232
- data/spec/views/layout-other.str +0 -11
- data/spec/views/layout.str +0 -11
- data/spec/views/login.str +0 -21
|
@@ -4,10 +4,6 @@ module Rodauth
|
|
|
4
4
|
Feature.define(:verify_account, :VerifyAccount) do
|
|
5
5
|
depends :login, :create_account, :email_base
|
|
6
6
|
|
|
7
|
-
def_deprecated_alias :attempt_to_create_unverified_account_error_flash, :attempt_to_create_unverified_account_notice_message
|
|
8
|
-
def_deprecated_alias :attempt_to_login_to_unverified_account_error_flash, :attempt_to_login_to_unverified_account_notice_message
|
|
9
|
-
def_deprecated_alias :no_matching_verify_account_key_error_flash, :no_matching_verify_account_key_message
|
|
10
|
-
|
|
11
7
|
error_flash "Unable to verify account"
|
|
12
8
|
error_flash "Unable to resend verify account email", 'verify_account_resend'
|
|
13
9
|
error_flash "An email has recently been sent to you with a link to verify your account", 'verify_account_email_recently_sent'
|
|
@@ -31,26 +27,29 @@ module Rodauth
|
|
|
31
27
|
redirect(:verify_account_email_sent){default_post_email_redirect}
|
|
32
28
|
redirect(:verify_account_email_recently_sent){default_post_email_redirect}
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
translatable_method :verify_account_email_subject, 'Verify Account'
|
|
35
31
|
auth_value_method :verify_account_key_param, 'key'
|
|
36
32
|
auth_value_method :verify_account_autologin?, true
|
|
37
33
|
auth_value_method :verify_account_table, :account_verification_keys
|
|
38
34
|
auth_value_method :verify_account_id_column, :id
|
|
39
|
-
auth_value_method :verify_account_email_last_sent_column,
|
|
35
|
+
auth_value_method :verify_account_email_last_sent_column, :email_last_sent
|
|
40
36
|
auth_value_method :verify_account_skip_resend_email_within, 300
|
|
41
37
|
auth_value_method :verify_account_key_column, :key
|
|
42
|
-
|
|
38
|
+
translatable_method :verify_account_resend_explanatory_text, "<p>If you no longer have the email to verify the account, you can request that it be resent to you:</p>"
|
|
39
|
+
translatable_method :verify_account_resend_link_text, "Resend Verify Account Information"
|
|
43
40
|
session_key :verify_account_session_key, :verify_account_key
|
|
44
|
-
auth_value_method :verify_account_set_password?,
|
|
41
|
+
auth_value_method :verify_account_set_password?, true
|
|
45
42
|
|
|
46
43
|
auth_methods(
|
|
47
44
|
:allow_resending_verify_account_email?,
|
|
48
45
|
:create_verify_account_key,
|
|
49
46
|
:create_verify_account_email,
|
|
50
47
|
:get_verify_account_key,
|
|
48
|
+
:get_verify_account_email_last_sent,
|
|
51
49
|
:remove_verify_account_key,
|
|
52
50
|
:resend_verify_account_view,
|
|
53
51
|
:send_verify_account_email,
|
|
52
|
+
:set_verify_account_email_last_sent,
|
|
54
53
|
:verify_account,
|
|
55
54
|
:verify_account_email_body,
|
|
56
55
|
:verify_account_email_link,
|
|
@@ -95,10 +94,11 @@ module Rodauth
|
|
|
95
94
|
route do |r|
|
|
96
95
|
verify_account_check_already_logged_in
|
|
97
96
|
before_verify_account_route
|
|
97
|
+
@password_field_autocomplete_value = 'new-password'
|
|
98
98
|
|
|
99
99
|
r.get do
|
|
100
100
|
if key = param_or_nil(verify_account_key_param)
|
|
101
|
-
|
|
101
|
+
set_session_value(verify_account_session_key, key)
|
|
102
102
|
redirect(r.path)
|
|
103
103
|
end
|
|
104
104
|
|
|
@@ -106,7 +106,7 @@ module Rodauth
|
|
|
106
106
|
if account_from_verify_account_key(key)
|
|
107
107
|
verify_account_view
|
|
108
108
|
else
|
|
109
|
-
|
|
109
|
+
remove_session_value(verify_account_session_key)
|
|
110
110
|
set_redirect_error_flash no_matching_verify_account_key_error_flash
|
|
111
111
|
redirect require_login_redirect
|
|
112
112
|
end
|
|
@@ -145,10 +145,10 @@ module Rodauth
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
if verify_account_autologin?
|
|
148
|
-
|
|
148
|
+
autologin_session('verify_account')
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
remove_session_value(verify_account_session_key)
|
|
152
152
|
set_notice_flash verify_account_notice_flash
|
|
153
153
|
redirect verify_account_redirect
|
|
154
154
|
end
|
|
@@ -158,6 +158,10 @@ module Rodauth
|
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
def require_login_confirmation?
|
|
162
|
+
false
|
|
163
|
+
end
|
|
164
|
+
|
|
161
165
|
def allow_resending_verify_account_email?
|
|
162
166
|
account[account_status_column] == account_unverified_status_value
|
|
163
167
|
end
|
|
@@ -201,7 +205,7 @@ module Rodauth
|
|
|
201
205
|
end
|
|
202
206
|
|
|
203
207
|
def send_verify_account_email
|
|
204
|
-
create_verify_account_email
|
|
208
|
+
send_email(create_verify_account_email)
|
|
205
209
|
end
|
|
206
210
|
|
|
207
211
|
def verify_account_email_link
|
|
@@ -220,14 +224,6 @@ module Rodauth
|
|
|
220
224
|
false
|
|
221
225
|
end
|
|
222
226
|
|
|
223
|
-
def login_form_footer
|
|
224
|
-
super + verify_account_resend_link
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def verify_account_resend_link
|
|
228
|
-
"<p><a href=\"#{prefix}/#{verify_account_resend_route}\">Resend Verify Account Information</a></p>"
|
|
229
|
-
end
|
|
230
|
-
|
|
231
227
|
def create_account_set_password?
|
|
232
228
|
return false if verify_account_set_password?
|
|
233
229
|
super
|
|
@@ -247,6 +243,14 @@ module Rodauth
|
|
|
247
243
|
|
|
248
244
|
private
|
|
249
245
|
|
|
246
|
+
def _login_form_footer_links
|
|
247
|
+
links = super
|
|
248
|
+
if !param_or_nil(login_param) || ((account || account_from_login(param(login_param))) && allow_resending_verify_account_email?)
|
|
249
|
+
links << [30, verify_account_resend_path, verify_account_resend_link_text]
|
|
250
|
+
end
|
|
251
|
+
links
|
|
252
|
+
end
|
|
253
|
+
|
|
250
254
|
def verify_account_email_recently_sent?
|
|
251
255
|
(email_last_sent = get_verify_account_email_last_sent) && (Time.now - email_last_sent < verify_account_skip_resend_email_within)
|
|
252
256
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:verify_account_grace_period, :VerifyAccountGracePeriod) do
|
|
5
5
|
depends :verify_account
|
|
6
|
-
error_flash "
|
|
6
|
+
error_flash "Please verify this account before changing the login", "unverified_change_login"
|
|
7
7
|
redirect :unverified_change_login
|
|
8
8
|
|
|
9
9
|
auth_value_method :verification_requested_at_column, :requested_at
|
|
@@ -23,7 +23,18 @@ module Rodauth
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def open_account?
|
|
26
|
-
super || account_in_unverified_grace_period?
|
|
26
|
+
super || (account_in_unverified_grace_period? && has_password?)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def verify_account_set_password?
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update_session
|
|
34
|
+
super
|
|
35
|
+
if account_in_unverified_grace_period?
|
|
36
|
+
set_session_value(unverified_account_session_key, true)
|
|
37
|
+
end
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
private
|
|
@@ -41,6 +52,13 @@ module Rodauth
|
|
|
41
52
|
super if defined?(super)
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
def allow_email_auth?
|
|
56
|
+
if defined?(super)
|
|
57
|
+
return false unless super
|
|
58
|
+
end
|
|
59
|
+
!account_in_unverified_grace_period?
|
|
60
|
+
end
|
|
61
|
+
|
|
44
62
|
def verify_account_check_already_logged_in
|
|
45
63
|
nil
|
|
46
64
|
end
|
|
@@ -56,13 +74,6 @@ module Rodauth
|
|
|
56
74
|
s
|
|
57
75
|
end
|
|
58
76
|
|
|
59
|
-
def update_session
|
|
60
|
-
super
|
|
61
|
-
if account_in_unverified_grace_period?
|
|
62
|
-
session[unverified_account_session_key] = true
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
77
|
def account_in_unverified_grace_period?
|
|
67
78
|
account[account_status_column] == account_unverified_status_value &&
|
|
68
79
|
verify_account_grace_period &&
|
|
@@ -4,10 +4,8 @@ module Rodauth
|
|
|
4
4
|
Feature.define(:verify_login_change, :VerifyLoginChange) do
|
|
5
5
|
depends :change_login, :email_base
|
|
6
6
|
|
|
7
|
-
def_deprecated_alias :no_matching_verify_login_change_key_error_flash, :no_matching_verify_login_change_key_message
|
|
8
|
-
|
|
9
7
|
error_flash "Unable to verify login change"
|
|
10
|
-
error_flash "Unable to change login as there is already an account with the new login",
|
|
8
|
+
error_flash "Unable to change login as there is already an account with the new login", 'verify_login_change_duplicate_account'
|
|
11
9
|
error_flash "There was an error verifying your login change: invalid verify login change key", 'no_matching_verify_login_change_key'
|
|
12
10
|
notice_flash "Your login change has been verified"
|
|
13
11
|
loaded_templates %w'verify-login-change verify-login-change-email'
|
|
@@ -23,8 +21,8 @@ module Rodauth
|
|
|
23
21
|
|
|
24
22
|
auth_value_method :verify_login_change_autologin?, false
|
|
25
23
|
auth_value_method :verify_login_change_deadline_column, :deadline
|
|
26
|
-
auth_value_method :verify_login_change_deadline_interval, {:days=>1}
|
|
27
|
-
|
|
24
|
+
auth_value_method :verify_login_change_deadline_interval, {:days=>1}.freeze
|
|
25
|
+
translatable_method :verify_login_change_email_subject, 'Verify Login Change'
|
|
28
26
|
auth_value_method :verify_login_change_id_column, :id
|
|
29
27
|
auth_value_method :verify_login_change_key_column, :key
|
|
30
28
|
auth_value_method :verify_login_change_key_param, 'key'
|
|
@@ -52,12 +50,11 @@ module Rodauth
|
|
|
52
50
|
)
|
|
53
51
|
|
|
54
52
|
route do |r|
|
|
55
|
-
check_already_logged_in
|
|
56
53
|
before_verify_login_change_route
|
|
57
54
|
|
|
58
55
|
r.get do
|
|
59
56
|
if key = param_or_nil(verify_login_change_key_param)
|
|
60
|
-
|
|
57
|
+
set_session_value(verify_login_change_session_key, key)
|
|
61
58
|
redirect(r.path)
|
|
62
59
|
end
|
|
63
60
|
|
|
@@ -65,7 +62,7 @@ module Rodauth
|
|
|
65
62
|
if account_from_verify_login_change_key(key)
|
|
66
63
|
verify_login_change_view
|
|
67
64
|
else
|
|
68
|
-
|
|
65
|
+
remove_session_value(verify_login_change_session_key)
|
|
69
66
|
set_redirect_error_flash no_matching_verify_login_change_key_error_flash
|
|
70
67
|
redirect require_login_redirect
|
|
71
68
|
end
|
|
@@ -92,15 +89,19 @@ module Rodauth
|
|
|
92
89
|
end
|
|
93
90
|
|
|
94
91
|
if verify_login_change_autologin?
|
|
95
|
-
|
|
92
|
+
autologin_session('verify_login_change')
|
|
96
93
|
end
|
|
97
94
|
|
|
98
|
-
|
|
95
|
+
remove_session_value(verify_login_change_session_key)
|
|
99
96
|
set_notice_flash verify_login_change_notice_flash
|
|
100
97
|
redirect verify_login_change_redirect
|
|
101
98
|
end
|
|
102
99
|
end
|
|
103
100
|
|
|
101
|
+
def require_login_confirmation?
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
104
105
|
def remove_verify_login_change_key
|
|
105
106
|
verify_login_change_ds.delete
|
|
106
107
|
end
|
|
@@ -118,7 +119,7 @@ module Rodauth
|
|
|
118
119
|
end
|
|
119
120
|
|
|
120
121
|
def send_verify_login_change_email(login)
|
|
121
|
-
create_verify_login_change_email(login)
|
|
122
|
+
send_email(create_verify_login_change_email(login))
|
|
122
123
|
end
|
|
123
124
|
|
|
124
125
|
def verify_login_change_email_link
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'webauthn'
|
|
4
|
+
|
|
5
|
+
module Rodauth
|
|
6
|
+
Feature.define(:webauthn, :Webauthn) do
|
|
7
|
+
depends :two_factor_base
|
|
8
|
+
|
|
9
|
+
loaded_templates %w'webauthn-setup webauthn-auth webauthn-remove'
|
|
10
|
+
|
|
11
|
+
view 'webauthn-setup', 'Setup WebAuthn Authentication', 'webauthn_setup'
|
|
12
|
+
view 'webauthn-auth', 'Authenticate Using WebAuthn', 'webauthn_auth'
|
|
13
|
+
view 'webauthn-remove', 'Remove WebAuthn Authenticator', 'webauthn_remove'
|
|
14
|
+
|
|
15
|
+
additional_form_tags 'webauthn_setup'
|
|
16
|
+
additional_form_tags 'webauthn_auth'
|
|
17
|
+
additional_form_tags 'webauthn_remove'
|
|
18
|
+
|
|
19
|
+
before :webauthn_setup
|
|
20
|
+
before :webauthn_auth
|
|
21
|
+
before :webauthn_remove
|
|
22
|
+
|
|
23
|
+
after :webauthn_setup
|
|
24
|
+
after :webauthn_auth_failure
|
|
25
|
+
after :webauthn_remove
|
|
26
|
+
|
|
27
|
+
button 'Setup WebAuthn Authentication', 'webauthn_setup'
|
|
28
|
+
button 'Authenticate Using WebAuthn', 'webauthn_auth'
|
|
29
|
+
button 'Remove WebAuthn Authenticator', 'webauthn_remove'
|
|
30
|
+
|
|
31
|
+
redirect :webauthn_setup
|
|
32
|
+
redirect :webauthn_remove
|
|
33
|
+
|
|
34
|
+
notice_flash "WebAuthn authentication is now setup", 'webauthn_setup'
|
|
35
|
+
notice_flash "WebAuthn authenticator has been removed", 'webauthn_remove'
|
|
36
|
+
|
|
37
|
+
error_flash "Error setting up WebAuthn authentication", 'webauthn_setup'
|
|
38
|
+
error_flash "Error authenticating using WebAuthn", 'webauthn_auth'
|
|
39
|
+
error_flash 'This account has not been setup for WebAuthn authentication', 'webauthn_not_setup'
|
|
40
|
+
error_flash "Error removing WebAuthn authenticator", 'webauthn_remove'
|
|
41
|
+
|
|
42
|
+
session_key :authenticated_webauthn_id_session_key, :webauthn_id
|
|
43
|
+
|
|
44
|
+
translatable_method :webauthn_auth_link_text, "Authenticate Using WebAuthn"
|
|
45
|
+
translatable_method :webauthn_setup_link_text, "Setup WebAuthn Authentication"
|
|
46
|
+
translatable_method :webauthn_remove_link_text, "Remove WebAuthn Authenticator"
|
|
47
|
+
|
|
48
|
+
auth_value_method :webauthn_setup_param, 'webauthn_setup'
|
|
49
|
+
auth_value_method :webauthn_auth_param, 'webauthn_auth'
|
|
50
|
+
auth_value_method :webauthn_remove_param, 'webauthn_remove'
|
|
51
|
+
auth_value_method :webauthn_setup_challenge_param, 'webauthn_setup_challenge'
|
|
52
|
+
auth_value_method :webauthn_setup_challenge_hmac_param, 'webauthn_setup_challenge_hmac'
|
|
53
|
+
auth_value_method :webauthn_auth_challenge_param, 'webauthn_auth_challenge'
|
|
54
|
+
auth_value_method :webauthn_auth_challenge_hmac_param, 'webauthn_auth_challenge_hmac'
|
|
55
|
+
|
|
56
|
+
auth_value_method :webauthn_keys_account_id_column, :account_id
|
|
57
|
+
auth_value_method :webauthn_keys_webauthn_id_column, :webauthn_id
|
|
58
|
+
auth_value_method :webauthn_keys_public_key_column, :public_key
|
|
59
|
+
auth_value_method :webauthn_keys_sign_count_column, :sign_count
|
|
60
|
+
auth_value_method :webauthn_keys_last_use_column, :last_use
|
|
61
|
+
auth_value_method :webauthn_keys_table, :account_webauthn_keys
|
|
62
|
+
|
|
63
|
+
auth_value_method :webauthn_user_ids_account_id_column, :id
|
|
64
|
+
auth_value_method :webauthn_user_ids_webauthn_id_column, :webauthn_id
|
|
65
|
+
auth_value_method :webauthn_user_ids_table, :account_webauthn_user_ids
|
|
66
|
+
|
|
67
|
+
auth_value_method :webauthn_setup_js, File.binread(File.expand_path('../../../../javascript/webauthn_setup.js', __FILE__)).freeze
|
|
68
|
+
auth_value_method :webauthn_auth_js, File.binread(File.expand_path('../../../../javascript/webauthn_auth.js', __FILE__)).freeze
|
|
69
|
+
auth_value_method :webauthn_js_host, ''
|
|
70
|
+
|
|
71
|
+
auth_value_method :webauthn_setup_timeout, 120000
|
|
72
|
+
auth_value_method :webauthn_auth_timeout, 60000
|
|
73
|
+
auth_value_method :webauthn_user_verification, 'discouraged'
|
|
74
|
+
auth_value_method :webauthn_attestation, 'none'
|
|
75
|
+
|
|
76
|
+
auth_value_method :webauthn_not_setup_error_status, 403
|
|
77
|
+
|
|
78
|
+
translatable_method :webauthn_invalid_setup_param_message, "invalid webauthn setup param"
|
|
79
|
+
translatable_method :webauthn_duplicate_webauthn_id_message, "attempt to insert duplicate webauthn id"
|
|
80
|
+
translatable_method :webauthn_invalid_auth_param_message, "invalid webauthn authentication param"
|
|
81
|
+
translatable_method :webauthn_invalid_sign_count_message, "webauthn credential has invalid sign count"
|
|
82
|
+
translatable_method :webauthn_invalid_remove_param_message, "must select valid webauthn authenticator to remove"
|
|
83
|
+
|
|
84
|
+
auth_value_methods(
|
|
85
|
+
:webauthn_authenticator_selection,
|
|
86
|
+
:webauthn_extensions,
|
|
87
|
+
:webauthn_origin,
|
|
88
|
+
:webauthn_rp_id,
|
|
89
|
+
:webauthn_rp_name,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
auth_methods(
|
|
93
|
+
:account_webauthn_ids,
|
|
94
|
+
:account_webauthn_usage,
|
|
95
|
+
:account_webauthn_user_id,
|
|
96
|
+
:add_webauthn_credential,
|
|
97
|
+
:authenticated_webauthn_id,
|
|
98
|
+
:handle_webauthn_sign_count_verification_error,
|
|
99
|
+
:new_webauthn_credential,
|
|
100
|
+
:remove_webauthn_key,
|
|
101
|
+
:remove_all_webauthn_keys_and_user_ids,
|
|
102
|
+
:valid_new_webauthn_credential?,
|
|
103
|
+
:valid_webauthn_credential_auth?,
|
|
104
|
+
:webauthn_auth_js_path,
|
|
105
|
+
:webauth_credential_options_for_get,
|
|
106
|
+
:webauthn_remove_authenticated_session,
|
|
107
|
+
:webauthn_setup_js_path,
|
|
108
|
+
:webauthn_update_session,
|
|
109
|
+
:webauthn_user_name,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
route(:webauthn_auth_js) do |r|
|
|
113
|
+
before_webauthn_auth_js_route
|
|
114
|
+
r.get do
|
|
115
|
+
response['Content-Type'] = 'text/javascript'
|
|
116
|
+
webauthn_auth_js
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
route(:webauthn_auth) do |r|
|
|
121
|
+
require_login
|
|
122
|
+
require_account_session
|
|
123
|
+
require_two_factor_not_authenticated('webauthn')
|
|
124
|
+
require_webauthn_setup
|
|
125
|
+
before_webauthn_auth_route
|
|
126
|
+
|
|
127
|
+
r.get do
|
|
128
|
+
webauthn_auth_view
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
r.post do
|
|
132
|
+
catch_error do
|
|
133
|
+
webauthn_credential = webauthn_auth_credential_from_form_submission
|
|
134
|
+
transaction do
|
|
135
|
+
before_webauthn_auth
|
|
136
|
+
webauthn_update_session(webauthn_credential.id)
|
|
137
|
+
two_factor_authenticate('webauthn')
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
after_webauthn_auth_failure
|
|
142
|
+
set_error_flash webauthn_auth_error_flash
|
|
143
|
+
webauthn_auth_view
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
route(:webauthn_setup_js) do |r|
|
|
148
|
+
before_webauthn_setup_js_route
|
|
149
|
+
r.get do
|
|
150
|
+
response['Content-Type'] = 'text/javascript'
|
|
151
|
+
webauthn_setup_js
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
route(:webauthn_setup) do |r|
|
|
156
|
+
require_authentication unless two_factor_login_type_match?('webauthn')
|
|
157
|
+
require_account_session
|
|
158
|
+
before_webauthn_setup_route
|
|
159
|
+
|
|
160
|
+
r.get do
|
|
161
|
+
webauthn_setup_view
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
r.post do
|
|
165
|
+
catch_error do
|
|
166
|
+
webauthn_credential = webauthn_setup_credential_from_form_submission
|
|
167
|
+
throw_error = false
|
|
168
|
+
|
|
169
|
+
transaction do
|
|
170
|
+
before_webauthn_setup
|
|
171
|
+
|
|
172
|
+
if raises_uniqueness_violation?{add_webauthn_credential(webauthn_credential)}
|
|
173
|
+
throw_error = true
|
|
174
|
+
raise Sequel::Rollback
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
unless two_factor_authenticated?
|
|
178
|
+
webauthn_update_session(webauthn_credential.id)
|
|
179
|
+
two_factor_update_session('webauthn')
|
|
180
|
+
end
|
|
181
|
+
after_webauthn_setup
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
if throw_error
|
|
185
|
+
throw_error_status(invalid_field_error_status, webauthn_setup_param, webauthn_duplicate_webauthn_id_message)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
set_notice_flash webauthn_setup_notice_flash
|
|
189
|
+
redirect webauthn_setup_redirect
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
set_error_flash webauthn_setup_error_flash
|
|
193
|
+
webauthn_setup_view
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
route(:webauthn_remove) do |r|
|
|
198
|
+
require_authentication unless two_factor_login_type_match?('webauthn')
|
|
199
|
+
require_account_session
|
|
200
|
+
require_webauthn_setup
|
|
201
|
+
before_webauthn_remove_route
|
|
202
|
+
|
|
203
|
+
r.get do
|
|
204
|
+
webauthn_remove_view
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
r.post do
|
|
208
|
+
catch_error do
|
|
209
|
+
unless webauthn_id = param_or_nil(webauthn_remove_param)
|
|
210
|
+
throw_error_status(invalid_field_error_status, webauthn_remove_param, webauthn_invalid_remove_param_message)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
unless two_factor_password_match?(param(password_param))
|
|
214
|
+
throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
transaction do
|
|
218
|
+
before_webauthn_remove
|
|
219
|
+
unless remove_webauthn_key(webauthn_id)
|
|
220
|
+
throw_error_status(invalid_field_error_status, webauthn_remove_param, webauthn_invalid_remove_param_message)
|
|
221
|
+
end
|
|
222
|
+
if authenticated_webauthn_id == webauthn_id && two_factor_login_type_match?('webauthn')
|
|
223
|
+
webauthn_remove_authenticated_session
|
|
224
|
+
two_factor_remove_session('webauthn')
|
|
225
|
+
end
|
|
226
|
+
after_webauthn_remove
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
set_notice_flash webauthn_remove_notice_flash
|
|
230
|
+
redirect webauthn_remove_redirect
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
set_error_flash webauthn_remove_error_flash
|
|
234
|
+
webauthn_remove_view
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def webauthn_auth_form_path
|
|
239
|
+
webauthn_auth_path
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def authenticated_webauthn_id
|
|
243
|
+
session[authenticated_webauthn_id_session_key]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def webauthn_remove_authenticated_session
|
|
247
|
+
remove_session_value(authenticated_webauthn_id_session_key)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def webauthn_update_session(webauthn_id)
|
|
251
|
+
set_session_value(authenticated_webauthn_id_session_key, webauthn_id)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def webauthn_authenticator_selection
|
|
255
|
+
{'requireResidentKey' => false, 'userVerification' => webauthn_user_verification}
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def webauthn_extensions
|
|
259
|
+
{}
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def account_webauthn_ids
|
|
263
|
+
webauthn_keys_ds.select_map(webauthn_keys_webauthn_id_column)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def account_webauthn_usage
|
|
267
|
+
webauthn_keys_ds.select_hash(webauthn_keys_webauthn_id_column, webauthn_keys_last_use_column)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def account_webauthn_user_id
|
|
271
|
+
unless webauthn_id = webauthn_user_ids_ds.get(webauthn_user_ids_webauthn_id_column)
|
|
272
|
+
webauthn_id = WebAuthn.generate_user_id
|
|
273
|
+
if e = raised_uniqueness_violation do
|
|
274
|
+
webauthn_user_ids_ds.insert(
|
|
275
|
+
webauthn_user_ids_account_id_column => webauthn_account_id,
|
|
276
|
+
webauthn_user_ids_webauthn_id_column => webauthn_id
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
# If two requests to create a webauthn user id are sent at the same time and an insert
|
|
280
|
+
# is attempted for both, one will fail with a unique constraint violation. In that case
|
|
281
|
+
# it is safe for the second one to use the webauthn user id inserted by the other request.
|
|
282
|
+
# If there is still no webauthn user id at this point, then we'll just reraise the
|
|
283
|
+
# exception.
|
|
284
|
+
# :nocov:
|
|
285
|
+
raise e unless webauthn_id = webauthn_user_ids_ds.get(webauthn_user_ids_webauthn_id_column)
|
|
286
|
+
# :nocov:
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
webauthn_id
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def new_webauthn_credential
|
|
294
|
+
WebAuthn::Credential.options_for_create(
|
|
295
|
+
:timeout => webauthn_setup_timeout,
|
|
296
|
+
:rp => {:name=>webauthn_rp_name, :id=>webauthn_rp_id},
|
|
297
|
+
:user => {:id=>account_webauthn_user_id, :name=>webauthn_user_name},
|
|
298
|
+
:authenticator_selection => webauthn_authenticator_selection,
|
|
299
|
+
:attestation => webauthn_attestation,
|
|
300
|
+
:extensions => webauthn_extensions,
|
|
301
|
+
:exclude => account_webauthn_ids,
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def valid_new_webauthn_credential?(webauthn_credential)
|
|
306
|
+
# Hack around inability to override expected_origin
|
|
307
|
+
origin = webauthn_origin
|
|
308
|
+
webauthn_credential.response.define_singleton_method(:verify) do |expected_challenge, expected_origin = nil, **kw|
|
|
309
|
+
super(expected_challenge, expected_origin || origin, **kw)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
(challenge = param_or_nil(webauthn_setup_challenge_param)) &&
|
|
313
|
+
(hmac = param_or_nil(webauthn_setup_challenge_hmac_param)) &&
|
|
314
|
+
timing_safe_eql?(compute_hmac(challenge), hmac) &&
|
|
315
|
+
webauthn_credential.verify(challenge)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def webauth_credential_options_for_get
|
|
319
|
+
WebAuthn::Credential.options_for_get(
|
|
320
|
+
:allow => account_webauthn_ids,
|
|
321
|
+
:timeout => webauthn_auth_timeout,
|
|
322
|
+
:rp_id => webauthn_rp_id,
|
|
323
|
+
:user_verification => webauthn_user_verification,
|
|
324
|
+
:extensions => webauthn_extensions,
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def webauthn_user_name
|
|
329
|
+
(account || account_from_session)[login_column]
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def webauthn_origin
|
|
333
|
+
base_url
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def webauthn_rp_id
|
|
337
|
+
webauthn_origin.sub(/\Ahttps?:\/\//, '')
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def webauthn_rp_name
|
|
341
|
+
webauthn_rp_id
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def handle_webauthn_sign_count_verification_error
|
|
345
|
+
throw_error_status(invalid_field_error_status, webauthn_auth_param, webauthn_invalid_sign_count_message)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def add_webauthn_credential(webauthn_credential)
|
|
349
|
+
webauthn_keys_ds.insert(
|
|
350
|
+
webauthn_keys_account_id_column => webauthn_account_id,
|
|
351
|
+
webauthn_keys_webauthn_id_column => webauthn_credential.id,
|
|
352
|
+
webauthn_keys_public_key_column => webauthn_credential.public_key,
|
|
353
|
+
webauthn_keys_sign_count_column => Integer(webauthn_credential.sign_count)
|
|
354
|
+
)
|
|
355
|
+
super if defined?(super)
|
|
356
|
+
nil
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def valid_webauthn_credential_auth?(webauthn_credential)
|
|
360
|
+
ds = webauthn_keys_ds.where(webauthn_keys_webauthn_id_column => webauthn_credential.id)
|
|
361
|
+
pub_key, sign_count = ds.get([webauthn_keys_public_key_column, webauthn_keys_sign_count_column])
|
|
362
|
+
|
|
363
|
+
# Hack around inability to override expected_origin
|
|
364
|
+
origin = webauthn_origin
|
|
365
|
+
webauthn_credential.response.define_singleton_method(:verify) do |expected_challenge, expected_origin = nil, **kw|
|
|
366
|
+
super(expected_challenge, expected_origin || origin, **kw)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
(challenge = param_or_nil(webauthn_auth_challenge_param)) &&
|
|
370
|
+
(hmac = param_or_nil(webauthn_auth_challenge_hmac_param)) &&
|
|
371
|
+
timing_safe_eql?(compute_hmac(challenge), hmac) &&
|
|
372
|
+
webauthn_credential.verify(challenge, public_key: pub_key, sign_count: sign_count) &&
|
|
373
|
+
ds.update(
|
|
374
|
+
webauthn_keys_sign_count_column => Integer(webauthn_credential.sign_count),
|
|
375
|
+
webauthn_keys_last_use_column => Sequel::CURRENT_TIMESTAMP
|
|
376
|
+
) == 1
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def remove_webauthn_key(webauthn_id)
|
|
380
|
+
ret = webauthn_keys_ds.where(webauthn_keys_webauthn_id_column=>webauthn_id).delete == 1
|
|
381
|
+
super if defined?(super)
|
|
382
|
+
ret
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def remove_all_webauthn_keys_and_user_ids
|
|
386
|
+
webauthn_user_ids_ds.delete
|
|
387
|
+
webauthn_keys_ds.delete
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def webauthn_setup?
|
|
391
|
+
!webauthn_keys_ds.empty?
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def require_webauthn_setup
|
|
395
|
+
unless webauthn_setup?
|
|
396
|
+
set_redirect_error_status(webauthn_not_setup_error_status)
|
|
397
|
+
set_redirect_error_flash webauthn_not_setup_error_flash
|
|
398
|
+
redirect two_factor_need_setup_redirect
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def two_factor_remove
|
|
403
|
+
super
|
|
404
|
+
remove_all_webauthn_keys_and_user_ids
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def possible_authentication_methods
|
|
408
|
+
methods = super
|
|
409
|
+
methods << 'webauthn' if webauthn_setup?
|
|
410
|
+
methods
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
private
|
|
414
|
+
|
|
415
|
+
def _two_factor_auth_links
|
|
416
|
+
links = super
|
|
417
|
+
links << [10, webauthn_auth_path, webauthn_auth_link_text] if webauthn_setup? && !two_factor_login_type_match?('webauthn')
|
|
418
|
+
links
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def _two_factor_setup_links
|
|
422
|
+
super << [10, webauthn_setup_path, webauthn_setup_link_text]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def _two_factor_remove_links
|
|
426
|
+
links = super
|
|
427
|
+
links << [10, webauthn_remove_path, webauthn_remove_link_text] if webauthn_setup?
|
|
428
|
+
links
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def _two_factor_remove_all_from_session
|
|
432
|
+
two_factor_remove_session('webauthn')
|
|
433
|
+
remove_session_value(authenticated_webauthn_id_session_key)
|
|
434
|
+
super
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def webauthn_account_id
|
|
438
|
+
session_value
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def webauthn_user_ids_ds
|
|
442
|
+
db[webauthn_user_ids_table].where(webauthn_user_ids_account_id_column => webauthn_account_id)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def webauthn_keys_ds
|
|
446
|
+
db[webauthn_keys_table].where(webauthn_keys_account_id_column => webauthn_account_id)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def webauthn_auth_credential_from_form_submission
|
|
450
|
+
case auth_data = raw_param(webauthn_auth_param)
|
|
451
|
+
when String
|
|
452
|
+
begin
|
|
453
|
+
auth_data = JSON.parse(auth_data)
|
|
454
|
+
rescue
|
|
455
|
+
throw_error_status(invalid_field_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
|
|
456
|
+
end
|
|
457
|
+
when Hash
|
|
458
|
+
# nothing
|
|
459
|
+
else
|
|
460
|
+
throw_error_status(invalid_field_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
begin
|
|
464
|
+
webauthn_credential = WebAuthn::Credential.from_get(auth_data)
|
|
465
|
+
unless valid_webauthn_credential_auth?(webauthn_credential)
|
|
466
|
+
throw_error_status(invalid_key_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
|
|
467
|
+
end
|
|
468
|
+
rescue WebAuthn::SignCountVerificationError
|
|
469
|
+
handle_webauthn_sign_count_verification_error
|
|
470
|
+
rescue WebAuthn::Error, RuntimeError, NoMethodError
|
|
471
|
+
throw_error_status(invalid_field_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
webauthn_credential
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def webauthn_setup_credential_from_form_submission
|
|
478
|
+
case setup_data = raw_param(webauthn_setup_param)
|
|
479
|
+
when String
|
|
480
|
+
begin
|
|
481
|
+
setup_data = JSON.parse(setup_data)
|
|
482
|
+
rescue
|
|
483
|
+
throw_error_status(invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
|
|
484
|
+
end
|
|
485
|
+
when Hash
|
|
486
|
+
# nothing
|
|
487
|
+
else
|
|
488
|
+
throw_error_status(invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
unless two_factor_password_match?(param(password_param))
|
|
492
|
+
throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
begin
|
|
496
|
+
webauthn_credential = WebAuthn::Credential.from_create(setup_data)
|
|
497
|
+
unless valid_new_webauthn_credential?(webauthn_credential)
|
|
498
|
+
throw_error_status(invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
|
|
499
|
+
end
|
|
500
|
+
rescue WebAuthn::Error, RuntimeError, NoMethodError
|
|
501
|
+
throw_error_status(invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
webauthn_credential
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
end
|