rodauth 1.23.0 → 2.0.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 +132 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +207 -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 +74 -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 +5 -15
- data/doc/http_basic_auth.rdoc +10 -1
- data/doc/jwt.rdoc +22 -22
- data/doc/jwt_cors.rdoc +2 -3
- 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/2.0.0.txt +361 -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 +29 -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 +131 -47
- data/lib/rodauth/features/change_password_notify.rb +1 -1
- data/lib/rodauth/features/confirm_password.rb +40 -2
- data/lib/rodauth/features/create_account.rb +7 -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 +29 -27
- data/lib/rodauth/features/email_base.rb +3 -3
- data/lib/rodauth/features/http_basic_auth.rb +44 -37
- data/lib/rodauth/features/jwt.rb +51 -8
- data/lib/rodauth/features/jwt_refresh.rb +3 -3
- data/lib/rodauth/features/lockout.rb +11 -13
- data/lib/rodauth/features/login.rb +48 -8
- data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
- data/lib/rodauth/features/otp.rb +71 -81
- data/lib/rodauth/features/password_complexity.rb +4 -11
- data/lib/rodauth/features/password_expiration.rb +1 -1
- data/lib/rodauth/features/password_grace_period.rb +17 -10
- data/lib/rodauth/features/recovery_codes.rb +47 -51
- data/lib/rodauth/features/remember.rb +11 -27
- data/lib/rodauth/features/reset_password.rb +25 -25
- 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 +58 -67
- data/lib/rodauth/features/two_factor_base.rb +132 -28
- data/lib/rodauth/features/verify_account.rb +23 -20
- data/lib/rodauth/features/verify_account_grace_period.rb +19 -8
- data/lib/rodauth/features/verify_login_change.rb +11 -10
- 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-request-form.str +1 -2
- 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-request.str +2 -2
- 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-request.str +2 -2
- data/templates/unlock-account.str +1 -1
- data/templates/verify-account-resend.str +1 -1
- data/templates/verify-account.str +1 -2
- 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 +64 -11
- data/doc/verify_change_login.rdoc +0 -11
- data/lib/rodauth/features/verify_change_login.rb +0 -20
|
@@ -4,7 +4,7 @@ module Rodauth
|
|
|
4
4
|
Feature.define(:change_password_notify, :ChangePasswordNotify) do
|
|
5
5
|
depends :change_password, :email_base
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
translatable_method :password_changed_email_subject, 'Password Changed'
|
|
8
8
|
|
|
9
9
|
auth_value_methods(
|
|
10
10
|
:password_changed_email_body
|
|
@@ -4,20 +4,26 @@ module Rodauth
|
|
|
4
4
|
Feature.define(:confirm_password, :ConfirmPassword) do
|
|
5
5
|
notice_flash "Your password has been confirmed"
|
|
6
6
|
error_flash "There was an error confirming your password"
|
|
7
|
+
error_flash "You need to confirm your password before continuing", 'password_authentication_required'
|
|
7
8
|
loaded_templates %w'confirm-password password-field'
|
|
8
9
|
view 'confirm-password', 'Confirm Password'
|
|
9
10
|
additional_form_tags
|
|
10
11
|
button 'Confirm Password'
|
|
11
12
|
before
|
|
12
13
|
after
|
|
14
|
+
redirect(:password_authentication_required){confirm_password_path}
|
|
13
15
|
|
|
14
16
|
session_key :confirm_password_redirect_session_key, :confirm_password_redirect
|
|
17
|
+
translatable_method :confirm_password_link_text, "Enter Password"
|
|
18
|
+
auth_value_method :password_authentication_required_error_status, 401
|
|
19
|
+
|
|
15
20
|
auth_value_methods :confirm_password_redirect
|
|
16
21
|
|
|
17
22
|
auth_methods :confirm_password
|
|
18
23
|
|
|
19
24
|
route do |r|
|
|
20
|
-
|
|
25
|
+
require_login
|
|
26
|
+
require_account_session
|
|
21
27
|
before_confirm_password_route
|
|
22
28
|
|
|
23
29
|
request.get do
|
|
@@ -42,12 +48,44 @@ module Rodauth
|
|
|
42
48
|
end
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
def require_password_authentication
|
|
52
|
+
require_login
|
|
53
|
+
|
|
54
|
+
if require_password_authentication? && has_password?
|
|
55
|
+
set_redirect_error_status(password_authentication_required_error_status)
|
|
56
|
+
set_redirect_error_flash password_authentication_required_error_flash
|
|
57
|
+
set_session_value(confirm_password_redirect_session_key, request.fullpath)
|
|
58
|
+
redirect password_authentication_required_redirect
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
45
62
|
def confirm_password
|
|
63
|
+
authenticated_by.delete('autologin')
|
|
64
|
+
authenticated_by.delete('remember')
|
|
65
|
+
authenticated_by.delete('email_auth')
|
|
66
|
+
authenticated_by.delete('password')
|
|
67
|
+
authenticated_by.unshift("password")
|
|
68
|
+
remove_session_value(autologin_type_session_key)
|
|
46
69
|
nil
|
|
47
70
|
end
|
|
48
71
|
|
|
49
72
|
def confirm_password_redirect
|
|
50
|
-
|
|
73
|
+
remove_session_value(confirm_password_redirect_session_key) || default_redirect
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def _two_factor_auth_links
|
|
79
|
+
links = (super if defined?(super)) || []
|
|
80
|
+
if authenticated_by.length == 1 && !authenticated_by.include?('password') && has_password?
|
|
81
|
+
links << [5, confirm_password_path, confirm_password_link_text]
|
|
82
|
+
end
|
|
83
|
+
links
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def require_password_authentication?
|
|
87
|
+
return true if defined?(super) && super
|
|
88
|
+
!authenticated_by.include?('password')
|
|
51
89
|
end
|
|
52
90
|
end
|
|
53
91
|
end
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:create_account, :CreateAccount) do
|
|
5
|
-
depends :login_password_requirements_base
|
|
5
|
+
depends :login, :login_password_requirements_base
|
|
6
6
|
|
|
7
|
-
depends :login
|
|
8
7
|
notice_flash 'Your account has been created'
|
|
9
8
|
error_flash "There was an error creating your account"
|
|
10
9
|
loaded_templates %w'create-account login-field login-confirm-field password-field password-confirm-field'
|
|
@@ -16,10 +15,9 @@ module Rodauth
|
|
|
16
15
|
redirect
|
|
17
16
|
|
|
18
17
|
auth_value_method :create_account_autologin?, true
|
|
18
|
+
translatable_method :create_account_link_text, "Create a New Account"
|
|
19
19
|
auth_value_method :create_account_set_password?, true
|
|
20
20
|
|
|
21
|
-
auth_value_methods :create_account_link
|
|
22
|
-
|
|
23
21
|
auth_methods(
|
|
24
22
|
:save_account,
|
|
25
23
|
:set_new_account_password
|
|
@@ -76,7 +74,7 @@ module Rodauth
|
|
|
76
74
|
end
|
|
77
75
|
after_create_account
|
|
78
76
|
if create_account_autologin?
|
|
79
|
-
|
|
77
|
+
autologin_session('create_account')
|
|
80
78
|
end
|
|
81
79
|
set_notice_flash create_account_notice_flash
|
|
82
80
|
redirect create_account_redirect
|
|
@@ -88,14 +86,6 @@ module Rodauth
|
|
|
88
86
|
end
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
def create_account_link
|
|
92
|
-
"<p><a href=\"#{create_account_path}\">Create a New Account</a></p>"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def login_form_footer
|
|
96
|
-
super + create_account_link
|
|
97
|
-
end
|
|
98
|
-
|
|
99
89
|
def set_new_account_password(password)
|
|
100
90
|
account[account_password_hash_column] = password_hash(password)
|
|
101
91
|
end
|
|
@@ -121,6 +111,10 @@ module Rodauth
|
|
|
121
111
|
|
|
122
112
|
private
|
|
123
113
|
|
|
114
|
+
def _login_form_footer_links
|
|
115
|
+
super << [10, create_account_path, create_account_link_text]
|
|
116
|
+
end
|
|
117
|
+
|
|
124
118
|
def _new_account(login)
|
|
125
119
|
acc = {login_column=>login}
|
|
126
120
|
unless skip_status_checks?
|
|
@@ -5,7 +5,7 @@ module Rodauth
|
|
|
5
5
|
depends :login_password_requirements_base
|
|
6
6
|
|
|
7
7
|
auth_value_method :most_common_passwords_file, File.expand_path('../../../../dict/top-10_000-passwords.txt', __FILE__)
|
|
8
|
-
|
|
8
|
+
translatable_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
|
|
9
9
|
auth_value_method :most_common_passwords, nil
|
|
10
10
|
|
|
11
11
|
auth_methods :password_one_of_most_common?
|
|
@@ -4,7 +4,7 @@ module Rodauth
|
|
|
4
4
|
Feature.define(:disallow_password_reuse, :DisallowPasswordReuse) do
|
|
5
5
|
depends :login_password_requirements_base
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
translatable_method :password_same_as_previous_password_message, "same as previous password"
|
|
8
8
|
auth_value_method :previous_password_account_id_column, :account_id
|
|
9
9
|
auth_value_method :previous_password_hash_column, :password_hash
|
|
10
10
|
auth_value_method :previous_password_hash_table, :account_previous_password_hashes
|
|
@@ -4,8 +4,6 @@ module Rodauth
|
|
|
4
4
|
Feature.define(:email_auth, :EmailAuth) do
|
|
5
5
|
depends :login, :email_base
|
|
6
6
|
|
|
7
|
-
def_deprecated_alias :no_matching_email_auth_key_error_flash, :no_matching_email_auth_key_message
|
|
8
|
-
|
|
9
7
|
notice_flash "An email has been sent to you with a link to login to your account", 'email_auth_email_sent'
|
|
10
8
|
error_flash "There was an error logging you in"
|
|
11
9
|
error_flash "There was an error requesting an email link to authenticate", 'email_auth_request'
|
|
@@ -23,17 +21,16 @@ module Rodauth
|
|
|
23
21
|
redirect(:email_auth_email_recently_sent){default_post_email_redirect}
|
|
24
22
|
|
|
25
23
|
auth_value_method :email_auth_deadline_column, :deadline
|
|
26
|
-
auth_value_method :email_auth_deadline_interval, {:days=>1}
|
|
27
|
-
|
|
24
|
+
auth_value_method :email_auth_deadline_interval, {:days=>1}.freeze
|
|
25
|
+
translatable_method :email_auth_email_subject, 'Login Link'
|
|
28
26
|
auth_value_method :email_auth_id_column, :id
|
|
29
27
|
auth_value_method :email_auth_key_column, :key
|
|
30
28
|
auth_value_method :email_auth_key_param, 'key'
|
|
31
29
|
auth_value_method :email_auth_email_last_sent_column, :email_last_sent
|
|
32
30
|
auth_value_method :email_auth_skip_resend_email_within, 300
|
|
33
31
|
auth_value_method :email_auth_table, :account_email_auth_keys
|
|
32
|
+
auth_value_method :force_email_auth?, false
|
|
34
33
|
session_key :email_auth_session_key, :email_auth_key
|
|
35
|
-
|
|
36
|
-
auth_value_methods :force_email_auth?
|
|
37
34
|
|
|
38
35
|
auth_methods(
|
|
39
36
|
:create_email_auth_email,
|
|
@@ -74,7 +71,7 @@ module Rodauth
|
|
|
74
71
|
|
|
75
72
|
r.get do
|
|
76
73
|
if key = param_or_nil(email_auth_key_param)
|
|
77
|
-
|
|
74
|
+
set_session_value(email_auth_session_key, key)
|
|
78
75
|
redirect(r.path)
|
|
79
76
|
end
|
|
80
77
|
|
|
@@ -82,7 +79,7 @@ module Rodauth
|
|
|
82
79
|
if account_from_email_auth_key(key)
|
|
83
80
|
email_auth_view
|
|
84
81
|
else
|
|
85
|
-
|
|
82
|
+
remove_session_value(email_auth_session_key)
|
|
86
83
|
set_redirect_error_flash no_matching_email_auth_key_error_flash
|
|
87
84
|
redirect require_login_redirect
|
|
88
85
|
end
|
|
@@ -97,7 +94,7 @@ module Rodauth
|
|
|
97
94
|
redirect email_auth_email_sent_redirect
|
|
98
95
|
end
|
|
99
96
|
|
|
100
|
-
_login
|
|
97
|
+
_login('email_auth')
|
|
101
98
|
end
|
|
102
99
|
end
|
|
103
100
|
|
|
@@ -148,43 +145,44 @@ module Rodauth
|
|
|
148
145
|
ds.get(email_auth_key_column)
|
|
149
146
|
end
|
|
150
147
|
|
|
151
|
-
def login_form_footer
|
|
152
|
-
footer = super
|
|
153
|
-
footer += email_auth_request_form if valid_login_entered?
|
|
154
|
-
footer
|
|
155
|
-
end
|
|
156
|
-
|
|
157
148
|
def email_auth_request_form
|
|
158
149
|
render('email-auth-request-form')
|
|
159
150
|
end
|
|
160
151
|
|
|
161
152
|
def after_login_entered_during_multi_phase_login
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
redirect email_auth_email_sent_redirect
|
|
167
|
-
else
|
|
168
|
-
# If the account has a password hash, allow password login, but
|
|
169
|
-
# we will show form below to also login via email link.
|
|
170
|
-
super
|
|
171
|
-
end
|
|
153
|
+
# If forcing email auth, just send the email link.
|
|
154
|
+
_email_auth_request_and_redirect if force_email_auth?
|
|
155
|
+
|
|
156
|
+
super
|
|
172
157
|
end
|
|
173
158
|
|
|
174
159
|
def use_multi_phase_login?
|
|
175
160
|
true
|
|
176
161
|
end
|
|
177
162
|
|
|
178
|
-
def
|
|
179
|
-
|
|
163
|
+
def possible_authentication_methods
|
|
164
|
+
methods = super
|
|
165
|
+
methods << 'email_auth' if !methods.include?('password') && allow_email_auth?
|
|
166
|
+
methods
|
|
180
167
|
end
|
|
181
168
|
|
|
182
169
|
private
|
|
183
170
|
|
|
171
|
+
def _multi_phase_login_forms
|
|
172
|
+
forms = super
|
|
173
|
+
forms << [30, email_auth_request_form, :_email_auth_request_and_redirect] if valid_login_entered? && allow_email_auth?
|
|
174
|
+
forms
|
|
175
|
+
end
|
|
176
|
+
|
|
184
177
|
def email_auth_email_recently_sent?
|
|
185
178
|
(email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
|
|
186
179
|
end
|
|
187
180
|
|
|
181
|
+
def _email_auth_request_and_redirect
|
|
182
|
+
_email_auth_request
|
|
183
|
+
redirect email_auth_email_sent_redirect
|
|
184
|
+
end
|
|
185
|
+
|
|
188
186
|
def _email_auth_request
|
|
189
187
|
if email_auth_email_recently_sent?
|
|
190
188
|
set_redirect_error_flash email_auth_email_recently_sent_error_flash
|
|
@@ -204,6 +202,10 @@ module Rodauth
|
|
|
204
202
|
|
|
205
203
|
attr_reader :email_auth_key_value
|
|
206
204
|
|
|
205
|
+
def allow_email_auth?
|
|
206
|
+
defined?(super) ? super : true
|
|
207
|
+
end
|
|
208
|
+
|
|
207
209
|
def after_login
|
|
208
210
|
# Remove the email auth key after any login, even if
|
|
209
211
|
# it is a password login. This is done to invalidate
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:email_base, :EmailBase) do
|
|
5
|
-
|
|
5
|
+
translatable_method :email_subject_prefix, ''
|
|
6
6
|
auth_value_method :require_mail?, true
|
|
7
7
|
auth_value_method :allow_raw_email_token?, false
|
|
8
8
|
|
|
@@ -43,7 +43,7 @@ module Rodauth
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def email_from
|
|
46
|
-
"webmaster@#{
|
|
46
|
+
"webmaster@#{domain}"
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def email_to
|
|
@@ -51,7 +51,7 @@ module Rodauth
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def token_link(route, param, key)
|
|
54
|
-
|
|
54
|
+
route_url(route, param => "#{account_id}#{token_separator}#{convert_email_token_key(key)}")
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def convert_email_token_key(key)
|
|
@@ -3,54 +3,61 @@
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:http_basic_auth, :HttpBasicAuth) do
|
|
5
5
|
auth_value_method :http_basic_auth_realm, "protected"
|
|
6
|
-
auth_value_method :require_http_basic_auth
|
|
6
|
+
auth_value_method :require_http_basic_auth?, false
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
username, password = token.unpack("m*").first.split(/:/, 2)
|
|
14
|
-
|
|
15
|
-
if username && password
|
|
16
|
-
catch_error do
|
|
17
|
-
unless account_from_login(username)
|
|
18
|
-
throw_basic_auth_error(login_param, no_matching_login_message)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
before_login_attempt
|
|
22
|
-
|
|
23
|
-
unless open_account?
|
|
24
|
-
throw_basic_auth_error(login_param, no_matching_login_message)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
unless password_match?(password)
|
|
28
|
-
after_login_failure
|
|
29
|
-
throw_basic_auth_error(password_param, invalid_password_message)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
transaction do
|
|
33
|
-
before_login
|
|
34
|
-
sess[session_key] = account_session_value
|
|
35
|
-
after_login
|
|
36
|
-
end
|
|
37
|
-
end
|
|
8
|
+
def require_login
|
|
9
|
+
if require_http_basic_auth?
|
|
10
|
+
require_http_basic_auth
|
|
11
|
+
elsif !logged_in?
|
|
12
|
+
http_basic_auth
|
|
38
13
|
end
|
|
39
14
|
|
|
40
|
-
|
|
15
|
+
super
|
|
41
16
|
end
|
|
42
17
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def require_login
|
|
46
|
-
if !logged_in? && require_http_basic_auth
|
|
18
|
+
def require_http_basic_auth
|
|
19
|
+
unless http_basic_auth
|
|
47
20
|
set_http_basic_auth_error_response
|
|
48
21
|
request.halt
|
|
49
22
|
end
|
|
23
|
+
end
|
|
50
24
|
|
|
51
|
-
|
|
25
|
+
def http_basic_auth
|
|
26
|
+
return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
|
|
27
|
+
|
|
28
|
+
username, password = token.unpack("m*").first.split(/:/, 2)
|
|
29
|
+
return unless username && password
|
|
30
|
+
|
|
31
|
+
catch_error do
|
|
32
|
+
unless account_from_login(username)
|
|
33
|
+
throw_basic_auth_error(login_param, no_matching_login_message)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
before_login_attempt
|
|
37
|
+
|
|
38
|
+
unless open_account?
|
|
39
|
+
throw_basic_auth_error(login_param, no_matching_login_message)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
unless password_match?(password)
|
|
43
|
+
after_login_failure
|
|
44
|
+
throw_basic_auth_error(password_param, invalid_password_message)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
transaction do
|
|
48
|
+
before_login
|
|
49
|
+
login_session('password')
|
|
50
|
+
after_login
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
return true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
nil
|
|
52
57
|
end
|
|
53
58
|
|
|
59
|
+
private
|
|
60
|
+
|
|
54
61
|
def set_http_basic_auth_error_response
|
|
55
62
|
response.status = 401
|
|
56
63
|
response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""
|
data/lib/rodauth/features/jwt.rb
CHANGED
|
@@ -4,25 +4,25 @@ require 'jwt'
|
|
|
4
4
|
|
|
5
5
|
module Rodauth
|
|
6
6
|
Feature.define(:jwt, :Jwt) do
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
translatable_method :invalid_jwt_format_error_message, "invalid JWT format or claim in Authorization header"
|
|
8
|
+
translatable_method :json_non_post_error_message, 'non-POST method used in JSON API'
|
|
9
|
+
translatable_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
|
|
10
10
|
auth_value_method :json_accept_regexp, /(?:(?:\*|\bapplication)\/\*|\bapplication\/(?:vnd\.api\+)?json\b)/i
|
|
11
11
|
auth_value_method :json_request_content_type_regexp, /\bapplication\/(?:vnd\.api\+)?json\b/i
|
|
12
12
|
auth_value_method :json_response_content_type, 'application/json'
|
|
13
13
|
auth_value_method :json_response_error_status, 400
|
|
14
|
-
auth_value_method :json_response_custom_error_status?,
|
|
14
|
+
auth_value_method :json_response_custom_error_status?, true
|
|
15
15
|
auth_value_method :json_response_error_key, "error"
|
|
16
16
|
auth_value_method :json_response_field_error_key, "field-error"
|
|
17
|
-
auth_value_method :json_response_success_key,
|
|
17
|
+
auth_value_method :json_response_success_key, "success"
|
|
18
18
|
auth_value_method :jwt_algorithm, "HS256"
|
|
19
19
|
auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
|
|
20
20
|
auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
|
|
21
|
-
auth_value_method :jwt_check_accept?,
|
|
22
|
-
auth_value_method :jwt_decode_opts, {}
|
|
21
|
+
auth_value_method :jwt_check_accept?, true
|
|
22
|
+
auth_value_method :jwt_decode_opts, {}.freeze
|
|
23
23
|
auth_value_method :jwt_session_key, nil
|
|
24
24
|
auth_value_method :jwt_symbolize_deeply?, false
|
|
25
|
-
|
|
25
|
+
translatable_method :non_json_request_error_message, 'Only JSON format requests are allowed'
|
|
26
26
|
|
|
27
27
|
auth_value_methods(
|
|
28
28
|
:only_json?,
|
|
@@ -173,6 +173,43 @@ module Rodauth
|
|
|
173
173
|
end
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
+
def before_webauthn_setup_route
|
|
177
|
+
super if defined?(super)
|
|
178
|
+
if use_jwt? && !param_or_nil(webauthn_setup_param)
|
|
179
|
+
cred = new_webauthn_credential
|
|
180
|
+
json_response[webauthn_setup_param] = cred.as_json
|
|
181
|
+
json_response[webauthn_setup_challenge_param] = cred.challenge
|
|
182
|
+
json_response[webauthn_setup_challenge_hmac_param] = compute_hmac(cred.challenge)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def before_webauthn_auth_route
|
|
187
|
+
super if defined?(super)
|
|
188
|
+
if use_jwt? && !param_or_nil(webauthn_auth_param)
|
|
189
|
+
cred = webauth_credential_options_for_get
|
|
190
|
+
json_response[webauthn_auth_param] = cred.as_json
|
|
191
|
+
json_response[webauthn_auth_challenge_param] = cred.challenge
|
|
192
|
+
json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def before_webauthn_login_route
|
|
197
|
+
super if defined?(super)
|
|
198
|
+
if use_jwt? && !param_or_nil(webauthn_auth_param) && account_from_login(param(login_param))
|
|
199
|
+
cred = webauth_credential_options_for_get
|
|
200
|
+
json_response[webauthn_auth_param] = cred.as_json
|
|
201
|
+
json_response[webauthn_auth_challenge_param] = cred.challenge
|
|
202
|
+
json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def before_webauthn_remove_route
|
|
207
|
+
super if defined?(super)
|
|
208
|
+
if use_jwt? && !param_or_nil(webauthn_remove_param)
|
|
209
|
+
json_response[webauthn_remove_param] = account_webauthn_usage
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
176
213
|
def before_otp_setup_route
|
|
177
214
|
super if defined?(super)
|
|
178
215
|
if use_jwt? && otp_keys_use_hmac? && !param_or_nil(otp_setup_raw_param)
|
|
@@ -204,6 +241,12 @@ module Rodauth
|
|
|
204
241
|
value
|
|
205
242
|
end
|
|
206
243
|
|
|
244
|
+
def remove_session_value(key)
|
|
245
|
+
value = super
|
|
246
|
+
set_jwt if use_jwt?
|
|
247
|
+
value
|
|
248
|
+
end
|
|
249
|
+
|
|
207
250
|
def json_response
|
|
208
251
|
@json_response ||= {}
|
|
209
252
|
end
|