rodauth 2.13.0 → 2.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +28 -0
- data/README.rdoc +50 -7
- data/doc/internal_request.rdoc +463 -0
- data/doc/path_class_methods.rdoc +10 -0
- data/doc/release_notes/2.14.0.txt +17 -0
- data/doc/release_notes/2.15.0.txt +48 -0
- data/doc/release_notes/2.16.0.txt +20 -0
- data/doc/release_notes/2.17.0.txt +10 -0
- data/doc/remember.rdoc +1 -0
- data/lib/rodauth/features/base.rb +11 -1
- data/lib/rodauth/features/change_login.rb +2 -0
- data/lib/rodauth/features/change_password.rb +2 -0
- data/lib/rodauth/features/close_account.rb +2 -0
- data/lib/rodauth/features/create_account.rb +2 -0
- data/lib/rodauth/features/email_auth.rb +4 -0
- data/lib/rodauth/features/internal_request.rb +371 -0
- data/lib/rodauth/features/jwt_refresh.rb +3 -5
- data/lib/rodauth/features/lockout.rb +11 -2
- data/lib/rodauth/features/login.rb +3 -0
- data/lib/rodauth/features/otp.rb +6 -0
- data/lib/rodauth/features/path_class_methods.rb +22 -0
- data/lib/rodauth/features/recovery_codes.rb +4 -0
- data/lib/rodauth/features/remember.rb +24 -9
- data/lib/rodauth/features/reset_password.rb +3 -0
- data/lib/rodauth/features/sms_codes.rb +7 -0
- data/lib/rodauth/features/two_factor_base.rb +2 -0
- data/lib/rodauth/features/verify_account.rb +5 -4
- data/lib/rodauth/features/verify_account_grace_period.rb +1 -1
- data/lib/rodauth/features/verify_login_change.rb +2 -0
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +20 -2
- metadata +17 -3
@@ -0,0 +1,48 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* An internal_request feature has been added. This feature allows
|
4
|
+
for interacting with Rodauth by calling methods, instead of having
|
5
|
+
to use a website or JSON API. This feature is designed primarily
|
6
|
+
for administrative use, so that administrators can create accounts,
|
7
|
+
change passwords or logins for accounts, and handle similar actions
|
8
|
+
without the user of the account being involved.
|
9
|
+
|
10
|
+
For example, assuming you've loaded the change_password and
|
11
|
+
internal_request features, and that your Roda class that
|
12
|
+
is loading Rodauth is named App, you can change the password
|
13
|
+
for the account with id 1 using:
|
14
|
+
|
15
|
+
App.rodauth.change_password(account_id: 1, password: 'foobar')
|
16
|
+
|
17
|
+
The internal request methods are implemented as class methods
|
18
|
+
on the Rodauth::Auth subclass (the object returned by App.rodauth).
|
19
|
+
These methods call methods on a subclass of that class specific
|
20
|
+
to internal requests.
|
21
|
+
|
22
|
+
The reason the feature is named internal_request is that these
|
23
|
+
methods are implemented by submitting a request internally, that is
|
24
|
+
processed almost exactly the same way as Rodauth would process a
|
25
|
+
web request.
|
26
|
+
|
27
|
+
See the internal_request feature documentation for details on which
|
28
|
+
internal request methods are available and the options they take.
|
29
|
+
|
30
|
+
* A path_class_methods feature has been added, that allows for calling
|
31
|
+
*_path and *_url as class methods. If you would like to call the
|
32
|
+
*_url methods as class methods, make sure to use the base_url
|
33
|
+
configuration method to set the base URL so that it does not require
|
34
|
+
request-specific information.
|
35
|
+
|
36
|
+
* Rodauth::Auth classes now have a configuration_name method that
|
37
|
+
returns the configuration name associated with the class. They also
|
38
|
+
have a configuration method that returns the configuration
|
39
|
+
associated with the class.
|
40
|
+
|
41
|
+
* Rodauth::Feature now supports an internal_request_method method for
|
42
|
+
specifying which methods are supported as internal request methods.
|
43
|
+
|
44
|
+
= Other Improvements
|
45
|
+
|
46
|
+
* The default base_url configuration method will now use the domain
|
47
|
+
method to get the domain to use, instead of getting the domain
|
48
|
+
information directly from the request environment.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Rodauth.lib has been added for using Rodauth purely as a library,
|
4
|
+
useful in non-web applications:
|
5
|
+
|
6
|
+
require 'rodauth'
|
7
|
+
rodauth = Rodauth.lib do
|
8
|
+
enable :create_account, :change_password
|
9
|
+
end
|
10
|
+
rodauth.create_account(login: 'foo@example.com', password: '...')
|
11
|
+
rodauth.change_password(account_id: 24601, password: '...')
|
12
|
+
|
13
|
+
This is built on top of the internal_request feature, and works by
|
14
|
+
creating a Roda application with the rodauth plugin, and returning
|
15
|
+
the related Rodauth::Auth class.
|
16
|
+
|
17
|
+
= Other Improvements
|
18
|
+
|
19
|
+
* The internal_request feature now works correctly for configurations
|
20
|
+
where only_json? is set to true.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
= Improvements
|
2
|
+
|
3
|
+
* The jwt_refresh feature now works for unverified accounts when using
|
4
|
+
the verify_account_grace_period feature.
|
5
|
+
|
6
|
+
* When trying to create an account that already exists but is
|
7
|
+
unverified, Rodauth now returns a 4xx response.
|
8
|
+
|
9
|
+
* When trying to login to an unverified account, Rodauth now returns a
|
10
|
+
4xx response.
|
data/doc/remember.rdoc
CHANGED
@@ -69,6 +69,7 @@ generate_remember_key_value :: A random string to use as the remember key.
|
|
69
69
|
get_remember_key :: Retrieve the remember key from the database.
|
70
70
|
load_memory :: If the remember key cookie is included in the request, and the user is not currently logged in, check the remember keys table and autologin the user if the remember key cookie matches the current remember key for the account. This method needs to be called manually inside the Roda route block to autologin users.
|
71
71
|
logged_in_via_remember_key? :: Whether the current session was logged in via a remember key.
|
72
|
+
remembered_session_id :: The session_id which is validly remembered, if any.
|
72
73
|
remember_key_value :: The current value of the remember key/token.
|
73
74
|
remember_login :: Set the cookie containing the remember token, so that future sessions will be autologged in.
|
74
75
|
remember_view :: The HTML to use for the change remember settings form.
|
@@ -115,6 +115,10 @@ module Rodauth
|
|
115
115
|
:around_rodauth
|
116
116
|
)
|
117
117
|
|
118
|
+
internal_request_method :account_exists?
|
119
|
+
internal_request_method :account_id_for_login
|
120
|
+
internal_request_method :internal_request_eval
|
121
|
+
|
118
122
|
configuration_module_eval do
|
119
123
|
def auth_class_eval(&block)
|
120
124
|
auth.class_eval(&block)
|
@@ -445,7 +449,9 @@ module Rodauth
|
|
445
449
|
end
|
446
450
|
|
447
451
|
def base_url
|
448
|
-
request.
|
452
|
+
url = String.new("#{request.scheme}://#{domain}")
|
453
|
+
url << ":#{request.port}" if request.port != Rack::Request::DEFAULT_PORTS[request.scheme]
|
454
|
+
url
|
449
455
|
end
|
450
456
|
|
451
457
|
def domain
|
@@ -734,6 +740,10 @@ module Rodauth
|
|
734
740
|
end
|
735
741
|
end
|
736
742
|
|
743
|
+
def internal_request?
|
744
|
+
false
|
745
|
+
end
|
746
|
+
|
737
747
|
def set_session_value(key, value)
|
738
748
|
session[key] = value
|
739
749
|
end
|
@@ -49,6 +49,10 @@ module Rodauth
|
|
49
49
|
|
50
50
|
auth_private_methods :account_from_email_auth_key
|
51
51
|
|
52
|
+
internal_request_method
|
53
|
+
internal_request_method :email_auth_request
|
54
|
+
internal_request_method :valid_email_auth?
|
55
|
+
|
52
56
|
route(:email_auth_request) do |r|
|
53
57
|
check_already_logged_in
|
54
58
|
before_email_auth_request_route
|
@@ -0,0 +1,371 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
INVALID_DOMAIN = "invalidurl @@.com"
|
7
|
+
|
8
|
+
class InternalRequestError < StandardError
|
9
|
+
attr_accessor :flash
|
10
|
+
attr_accessor :reason
|
11
|
+
attr_accessor :field_errors
|
12
|
+
|
13
|
+
def initialize(attrs)
|
14
|
+
return super if attrs.is_a?(String)
|
15
|
+
|
16
|
+
@flash = attrs[:flash]
|
17
|
+
@reason = attrs[:reason]
|
18
|
+
@field_errors = attrs[:field_errors] || {}
|
19
|
+
|
20
|
+
super(build_message)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def build_message
|
26
|
+
extras = []
|
27
|
+
extras << reason if reason
|
28
|
+
extras << field_errors unless field_errors.empty?
|
29
|
+
extras = (" (#{extras.join(", ")})" unless extras.empty?)
|
30
|
+
|
31
|
+
"#{flash}#{extras}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module InternalRequestMethods
|
36
|
+
attr_accessor :session
|
37
|
+
attr_accessor :params
|
38
|
+
attr_reader :flash
|
39
|
+
attr_accessor :internal_request_block
|
40
|
+
|
41
|
+
def domain
|
42
|
+
d = super
|
43
|
+
if d == INVALID_DOMAIN
|
44
|
+
raise InternalRequestError, "must set domain in configuration, as it cannot be determined from internal request"
|
45
|
+
end
|
46
|
+
d
|
47
|
+
end
|
48
|
+
|
49
|
+
def raw_param(k)
|
50
|
+
@params[k]
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_error_flash(message)
|
54
|
+
@flash = message
|
55
|
+
_handle_internal_request_error
|
56
|
+
end
|
57
|
+
alias set_redirect_error_flash set_error_flash
|
58
|
+
|
59
|
+
def set_notice_flash(message)
|
60
|
+
@flash = message
|
61
|
+
end
|
62
|
+
alias set_notice_now_flash set_notice_flash
|
63
|
+
|
64
|
+
def modifications_require_password?
|
65
|
+
false
|
66
|
+
end
|
67
|
+
alias require_login_confirmation? modifications_require_password?
|
68
|
+
alias require_password_confirmation? modifications_require_password?
|
69
|
+
alias change_login_requires_password? modifications_require_password?
|
70
|
+
alias change_password_requires_password? modifications_require_password?
|
71
|
+
alias close_account_requires_password? modifications_require_password?
|
72
|
+
alias two_factor_modifications_require_password? modifications_require_password?
|
73
|
+
|
74
|
+
def otp_setup_view
|
75
|
+
hash = {:otp_setup=>otp_user_key}
|
76
|
+
hash[:otp_setup_raw] = otp_key if hmac_secret
|
77
|
+
_return_from_internal_request(hash)
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_recovery_codes_view
|
81
|
+
_return_from_internal_request(recovery_codes)
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_internal_request(meth)
|
85
|
+
catch(:halt) do
|
86
|
+
_around_rodauth do
|
87
|
+
before_rodauth
|
88
|
+
send(meth, request)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
@internal_request_return_value
|
93
|
+
end
|
94
|
+
|
95
|
+
def only_json?
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def internal_request?
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_error_reason(reason)
|
106
|
+
@error_reason = reason
|
107
|
+
end
|
108
|
+
|
109
|
+
def after_login
|
110
|
+
super
|
111
|
+
_set_internal_request_return_value(account_id) unless @return_false_on_error
|
112
|
+
end
|
113
|
+
|
114
|
+
def after_remember
|
115
|
+
super
|
116
|
+
if params[remember_param] == remember_remember_param_value
|
117
|
+
_set_internal_request_return_value("#{account_id}_#{convert_token_key(remember_key_value)}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def after_load_memory
|
122
|
+
super
|
123
|
+
_return_from_internal_request(session_value)
|
124
|
+
end
|
125
|
+
|
126
|
+
def before_change_password_route
|
127
|
+
super
|
128
|
+
params[new_password_param] ||= params[password_param]
|
129
|
+
end
|
130
|
+
|
131
|
+
def before_email_auth_request_route
|
132
|
+
super
|
133
|
+
_set_login_param_from_account
|
134
|
+
end
|
135
|
+
|
136
|
+
def before_login_route
|
137
|
+
super
|
138
|
+
_set_login_param_from_account
|
139
|
+
end
|
140
|
+
|
141
|
+
def before_unlock_account_request_route
|
142
|
+
super
|
143
|
+
_set_login_param_from_account
|
144
|
+
end
|
145
|
+
|
146
|
+
def before_reset_password_request_route
|
147
|
+
super
|
148
|
+
_set_login_param_from_account
|
149
|
+
end
|
150
|
+
|
151
|
+
def before_verify_account_resend_route
|
152
|
+
super
|
153
|
+
_set_login_param_from_account
|
154
|
+
end
|
155
|
+
|
156
|
+
def account_from_key(token, status_id=nil)
|
157
|
+
return super unless session_value
|
158
|
+
return unless yield session_value
|
159
|
+
ds = account_ds(session_value)
|
160
|
+
ds = ds.where(account_status_column=>status_id) if status_id && !skip_status_checks?
|
161
|
+
ds.first
|
162
|
+
end
|
163
|
+
|
164
|
+
def _set_internal_request_return_value(value)
|
165
|
+
@internal_request_return_value = value
|
166
|
+
end
|
167
|
+
|
168
|
+
def _return_from_internal_request(value)
|
169
|
+
_set_internal_request_return_value(value)
|
170
|
+
throw(:halt)
|
171
|
+
end
|
172
|
+
|
173
|
+
def _handle_internal_request_error
|
174
|
+
if @return_false_on_error
|
175
|
+
_return_from_internal_request(false)
|
176
|
+
else
|
177
|
+
raise InternalRequestError.new(flash: @flash, reason: @error_reason, field_errors: @field_errors)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def _return_false_on_error!
|
182
|
+
@return_false_on_error = true
|
183
|
+
end
|
184
|
+
|
185
|
+
def _set_login_param_from_account
|
186
|
+
if session_value && !params[login_param] && (account = account_ds(session_value).first)
|
187
|
+
params[login_param] = account[login_column]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def _get_remember_cookie
|
192
|
+
params[remember_param]
|
193
|
+
end
|
194
|
+
|
195
|
+
def _handle_internal_request_eval(_)
|
196
|
+
v = instance_eval(&internal_request_block)
|
197
|
+
_set_internal_request_return_value(v) unless defined?(@internal_request_return_value)
|
198
|
+
end
|
199
|
+
|
200
|
+
def _handle_account_id_for_login(_)
|
201
|
+
raise InternalRequestError, "no login provided" unless login = param_or_nil(login_param)
|
202
|
+
raise InternalRequestError, "no account for login" unless account = account_from_login(login)
|
203
|
+
_return_from_internal_request(account[account_id_column])
|
204
|
+
end
|
205
|
+
|
206
|
+
def _handle_account_exists?(_)
|
207
|
+
raise InternalRequestError, "no login provided" unless login = param_or_nil(login_param)
|
208
|
+
_return_from_internal_request(!!account_from_login(login))
|
209
|
+
end
|
210
|
+
|
211
|
+
def _handle_lock_account(_)
|
212
|
+
raised_uniqueness_violation{account_lockouts_ds(session_value).insert(_setup_account_lockouts_hash(session_value, generate_unlock_account_key))}
|
213
|
+
end
|
214
|
+
|
215
|
+
def _handle_remember_setup(request)
|
216
|
+
params[remember_param] = remember_remember_param_value
|
217
|
+
_handle_remember(request)
|
218
|
+
end
|
219
|
+
|
220
|
+
def _handle_remember_disable(request)
|
221
|
+
params[remember_param] = remember_disable_param_value
|
222
|
+
_handle_remember(request)
|
223
|
+
end
|
224
|
+
|
225
|
+
def _handle_account_id_for_remember_key(request)
|
226
|
+
load_memory
|
227
|
+
raise InternalRequestError, "invalid remember key"
|
228
|
+
end
|
229
|
+
|
230
|
+
def _handle_otp_setup_params(request)
|
231
|
+
request.env['REQUEST_METHOD'] = 'GET'
|
232
|
+
_handle_otp_setup(request)
|
233
|
+
end
|
234
|
+
|
235
|
+
def _predicate_internal_request(meth, request)
|
236
|
+
_return_false_on_error!
|
237
|
+
_set_internal_request_return_value(true)
|
238
|
+
send(meth, request)
|
239
|
+
end
|
240
|
+
|
241
|
+
def _handle_valid_login_and_password?(request)
|
242
|
+
_predicate_internal_request(:_handle_login, request)
|
243
|
+
end
|
244
|
+
|
245
|
+
def _handle_valid_email_auth?(request)
|
246
|
+
_predicate_internal_request(:_handle_email_auth, request)
|
247
|
+
end
|
248
|
+
|
249
|
+
def _handle_valid_otp_auth?(request)
|
250
|
+
_predicate_internal_request(:_handle_otp_auth, request)
|
251
|
+
end
|
252
|
+
|
253
|
+
def _handle_valid_recovery_auth?(request)
|
254
|
+
_predicate_internal_request(:_handle_recovery_auth, request)
|
255
|
+
end
|
256
|
+
|
257
|
+
def _handle_valid_sms_auth?(request)
|
258
|
+
_predicate_internal_request(:_handle_sms_auth, request)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
module InternalRequestClassMethods
|
263
|
+
def internal_request(route, opts={}, &block)
|
264
|
+
opts = opts.dup
|
265
|
+
|
266
|
+
env = {
|
267
|
+
'REQUEST_METHOD'=>'POST',
|
268
|
+
'PATH_INFO'=>'/',
|
269
|
+
"SCRIPT_NAME" => "",
|
270
|
+
"HTTP_HOST" => INVALID_DOMAIN,
|
271
|
+
"SERVER_NAME" => INVALID_DOMAIN,
|
272
|
+
"SERVER_PORT" => 443,
|
273
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
274
|
+
"rack.input"=>StringIO.new(''),
|
275
|
+
"rack.url_scheme"=>"https"
|
276
|
+
}
|
277
|
+
env.merge!(opts.delete(:env)) if opts[:env]
|
278
|
+
|
279
|
+
session = {}
|
280
|
+
session.merge!(opts.delete(:session)) if opts[:session]
|
281
|
+
|
282
|
+
params = {}
|
283
|
+
params.merge!(opts.delete(:params)) if opts[:params]
|
284
|
+
|
285
|
+
scope = roda_class.new(env)
|
286
|
+
rodauth = new(scope)
|
287
|
+
rodauth.session = session
|
288
|
+
rodauth.params = params
|
289
|
+
rodauth.internal_request_block = block
|
290
|
+
|
291
|
+
unless account_id = opts.delete(:account_id)
|
292
|
+
if (account_login = opts.delete(:account_login))
|
293
|
+
if (account = rodauth.send(:_account_from_login, account_login))
|
294
|
+
account_id = account[rodauth.account_id_column]
|
295
|
+
else
|
296
|
+
raise InternalRequestError, "no account for login: #{account_login.inspect}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
if account_id
|
302
|
+
session[rodauth.session_key] = account_id
|
303
|
+
unless authenticated_by = opts.delete(:authenticated_by)
|
304
|
+
authenticated_by = case route
|
305
|
+
when :otp_auth, :sms_request, :sms_auth, :recovery_auth, :valid_otp_auth?, :valid_sms_auth?, :valid_recovery_auth?
|
306
|
+
['internal1']
|
307
|
+
else
|
308
|
+
['internal1', 'internal2']
|
309
|
+
end
|
310
|
+
end
|
311
|
+
session[rodauth.authenticated_by_session_key] = authenticated_by
|
312
|
+
end
|
313
|
+
|
314
|
+
opts.keys.each do |k|
|
315
|
+
meth = :"#{k}_param"
|
316
|
+
params[rodauth.public_send(meth).to_s] = opts.delete(k) if rodauth.respond_to?(meth)
|
317
|
+
end
|
318
|
+
|
319
|
+
unless opts.empty?
|
320
|
+
warn "unhandled options passed to #{route}: #{opts.inspect}"
|
321
|
+
end
|
322
|
+
|
323
|
+
rodauth.handle_internal_request(:"_handle_#{route}")
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
Feature.define(:internal_request, :InternalRequest) do
|
328
|
+
configuration_module_eval do
|
329
|
+
def internal_request_configuration(&block)
|
330
|
+
@auth.instance_exec do
|
331
|
+
(@internal_request_configuration_blocks ||= []) << block
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def post_configure
|
337
|
+
super
|
338
|
+
|
339
|
+
return if is_a?(InternalRequestMethods)
|
340
|
+
|
341
|
+
klass = self.class
|
342
|
+
internal_class = Class.new(klass) do
|
343
|
+
@roda_class = klass.roda_class
|
344
|
+
@features = klass.features.clone
|
345
|
+
@routes = klass.routes.clone
|
346
|
+
@route_hash = klass.route_hash.clone
|
347
|
+
@configuration = klass.configuration.clone
|
348
|
+
@configuration.instance_variable_set(:@auth, self)
|
349
|
+
end
|
350
|
+
|
351
|
+
if blocks = klass.instance_variable_get(:@internal_request_configuration_blocks)
|
352
|
+
configuration = internal_class.configuration
|
353
|
+
blocks.each do |block|
|
354
|
+
configuration.instance_exec(&block)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
internal_class.send(:extend, InternalRequestClassMethods)
|
358
|
+
internal_class.send(:include, InternalRequestMethods)
|
359
|
+
internal_class.allocate.post_configure
|
360
|
+
|
361
|
+
([:base] + internal_class.features).each do |feature_name|
|
362
|
+
feature = FEATURES[feature_name]
|
363
|
+
if meths = feature.internal_request_methods
|
364
|
+
meths.each do |name|
|
365
|
+
klass.define_singleton_method(name){|opts={}, &block| internal_class.internal_request(name, opts, &block)}
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
@@ -32,8 +32,6 @@ module Rodauth
|
|
32
32
|
)
|
33
33
|
|
34
34
|
route do |r|
|
35
|
-
# For backward compatibility, unused in core Rodauth
|
36
|
-
# RODAUTH3: Remove
|
37
35
|
@jwt_refresh_route = true
|
38
36
|
before_jwt_refresh_route
|
39
37
|
|
@@ -100,7 +98,7 @@ module Rodauth
|
|
100
98
|
# JWT is invalid for other reasons. Make sure the expiration is the
|
101
99
|
# only reason the JWT isn't valid before treating this as an expired token.
|
102
100
|
JWT.decode(jwt_token, jwt_secret, true, Hash[jwt_decode_opts].merge!(:verify_expiration=>false, :algorithm=>jwt_algorithm))[0]
|
103
|
-
rescue
|
101
|
+
rescue
|
104
102
|
else
|
105
103
|
json_response[json_response_error_key] = expired_jwt_access_token_message
|
106
104
|
response.status ||= expired_jwt_access_token_status
|
@@ -122,7 +120,7 @@ module Rodauth
|
|
122
120
|
end
|
123
121
|
|
124
122
|
ds = account_ds(id)
|
125
|
-
ds = ds.where(
|
123
|
+
ds = ds.where(account_session_status_filter) unless skip_status_checks?
|
126
124
|
ds.first
|
127
125
|
end
|
128
126
|
|
@@ -137,7 +135,7 @@ module Rodauth
|
|
137
135
|
end
|
138
136
|
|
139
137
|
def _jwt_decode_opts
|
140
|
-
if allow_refresh_with_expired_jwt_access_token? && request.path == jwt_refresh_path
|
138
|
+
if allow_refresh_with_expired_jwt_access_token? && (@jwt_refresh_route || request.path == jwt_refresh_path)
|
141
139
|
Hash[super].merge!(:verify_expiration=>false)
|
142
140
|
else
|
143
141
|
super
|
@@ -62,6 +62,10 @@ module Rodauth
|
|
62
62
|
)
|
63
63
|
auth_private_methods :account_from_unlock_key
|
64
64
|
|
65
|
+
internal_request_method(:lock_account)
|
66
|
+
internal_request_method(:unlock_account_request)
|
67
|
+
internal_request_method(:unlock_account)
|
68
|
+
|
65
69
|
route(:unlock_account_request) do |r|
|
66
70
|
check_already_logged_in
|
67
71
|
before_unlock_account_request_route
|
@@ -167,6 +171,12 @@ module Rodauth
|
|
167
171
|
unlock_account
|
168
172
|
end
|
169
173
|
|
174
|
+
def _setup_account_lockouts_hash(account_id, key)
|
175
|
+
hash = {account_lockouts_id_column=>account_id, account_lockouts_key_column=>key}
|
176
|
+
set_deadline_value(hash, account_lockouts_deadline_column, account_lockouts_deadline_interval)
|
177
|
+
hash
|
178
|
+
end
|
179
|
+
|
170
180
|
def invalid_login_attempted
|
171
181
|
ds = account_login_failures_ds.
|
172
182
|
where(account_login_failures_id_column=>account_id)
|
@@ -192,8 +202,7 @@ module Rodauth
|
|
192
202
|
|
193
203
|
if number >= max_invalid_logins
|
194
204
|
@unlock_account_key_value = generate_unlock_account_key
|
195
|
-
hash =
|
196
|
-
set_deadline_value(hash, account_lockouts_deadline_column, account_lockouts_deadline_interval)
|
205
|
+
hash = _setup_account_lockouts_hash(account_id, unlock_account_key_value)
|
197
206
|
|
198
207
|
if e = raised_uniqueness_violation{account_lockouts_ds.insert(hash)}
|
199
208
|
# If inserting into the lockout table raises a violation, we should just be able to pull the already inserted
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -96,6 +96,12 @@ module Rodauth
|
|
96
96
|
:otp_tmp_key
|
97
97
|
)
|
98
98
|
|
99
|
+
internal_request_method :otp_setup_params
|
100
|
+
internal_request_method :otp_setup
|
101
|
+
internal_request_method :otp_auth
|
102
|
+
internal_request_method :valid_otp_auth?
|
103
|
+
internal_request_method :otp_disable
|
104
|
+
|
99
105
|
route(:otp_auth) do |r|
|
100
106
|
require_login
|
101
107
|
require_account_session
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:path_class_methods, :PathClassMethods) do
|
5
|
+
def post_configure
|
6
|
+
super
|
7
|
+
|
8
|
+
klass = self.class
|
9
|
+
klass.features.each do |feature_name|
|
10
|
+
feature = FEATURES[feature_name]
|
11
|
+
feature.routes.each do |handle_meth|
|
12
|
+
route = handle_meth.to_s.sub(/\Ahandle_/, '')
|
13
|
+
path_meth = :"#{route}_path"
|
14
|
+
url_meth = :"#{route}_url"
|
15
|
+
instance = klass.allocate.freeze
|
16
|
+
klass.define_singleton_method(path_meth){|opts={}| instance.send(path_meth, opts)}
|
17
|
+
klass.define_singleton_method(url_meth){|opts={}| instance.send(url_meth, opts)}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -59,6 +59,10 @@ module Rodauth
|
|
59
59
|
:recovery_code_match?,
|
60
60
|
)
|
61
61
|
|
62
|
+
internal_request_method :recovery_codes
|
63
|
+
internal_request_method :recovery_auth
|
64
|
+
internal_request_method :valid_recovery_auth?
|
65
|
+
|
62
66
|
route(:recovery_auth) do |r|
|
63
67
|
require_login
|
64
68
|
require_account_session
|