rodauth 2.12.0 → 2.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +30 -0
- data/README.rdoc +50 -7
- data/doc/base.rdoc +1 -0
- data/doc/error_reasons.rdoc +73 -0
- data/doc/internal_request.rdoc +463 -0
- data/doc/path_class_methods.rdoc +10 -0
- data/doc/release_notes/2.13.0.txt +19 -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/remember.rdoc +1 -0
- data/lib/rodauth/features/active_sessions.rb +1 -1
- data/lib/rodauth/features/base.rb +26 -1
- data/lib/rodauth/features/change_login.rb +6 -4
- data/lib/rodauth/features/change_password.rb +5 -3
- data/lib/rodauth/features/close_account.rb +3 -1
- data/lib/rodauth/features/confirm_password.rb +2 -2
- data/lib/rodauth/features/create_account.rb +6 -4
- 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 +6 -0
- data/lib/rodauth/features/internal_request.rb +371 -0
- data/lib/rodauth/features/jwt_refresh.rb +1 -1
- data/lib/rodauth/features/lockout.rb +15 -4
- data/lib/rodauth/features/login.rb +6 -3
- data/lib/rodauth/features/login_password_requirements_base.rb +15 -6
- data/lib/rodauth/features/otp.rb +13 -6
- data/lib/rodauth/features/password_complexity.rb +4 -4
- data/lib/rodauth/features/path_class_methods.rb +22 -0
- data/lib/rodauth/features/recovery_codes.rb +6 -2
- data/lib/rodauth/features/remember.rb +25 -10
- data/lib/rodauth/features/reset_password.rb +8 -4
- data/lib/rodauth/features/session_expiration.rb +1 -0
- data/lib/rodauth/features/single_session.rb +1 -0
- data/lib/rodauth/features/sms_codes.rb +17 -5
- data/lib/rodauth/features/two_factor_base.rb +6 -1
- data/lib/rodauth/features/verify_account.rb +8 -1
- data/lib/rodauth/features/verify_account_grace_period.rb +1 -1
- data/lib/rodauth/features/verify_login_change.rb +5 -2
- data/lib/rodauth/features/webauthn.rb +15 -14
- data/lib/rodauth/features/webauthn_login.rb +1 -1
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +20 -2
- data/templates/button.str +1 -1
- data/templates/change-password.str +2 -2
- data/templates/global-logout-field.str +1 -1
- data/templates/login-confirm-field.str +2 -2
- data/templates/login-display.str +2 -2
- data/templates/login-field.str +2 -2
- data/templates/otp-auth-code-field.str +2 -2
- data/templates/otp-setup.str +2 -2
- data/templates/password-confirm-field.str +2 -2
- data/templates/password-field.str +2 -2
- data/templates/recovery-auth.str +2 -2
- data/templates/remember.str +1 -1
- data/templates/sms-code-field.str +2 -2
- data/templates/sms-setup.str +2 -2
- data/templates/webauthn-remove.str +1 -1
- metadata +19 -3
@@ -0,0 +1,10 @@
|
|
1
|
+
= Documentation for Path Class Methods Feature
|
2
|
+
|
3
|
+
The path class methods feature allows for calling the *_path and *_url
|
4
|
+
methods directly on the class, as opposed to an instance of the class.
|
5
|
+
|
6
|
+
In order for the *_url methods to be used, you must use the base_url
|
7
|
+
configuration so that determining the base URL doesn't depend on the
|
8
|
+
submitted request, as the request will not be set when using the
|
9
|
+
class method. Failure to do this will probably result in a NoMethodError
|
10
|
+
being raised.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A set_error_reason configuration method has been added. This method
|
4
|
+
is called whenever a error occurs in Rodauth, with a symbol
|
5
|
+
describing the error. The default implementation of this method does
|
6
|
+
nothing, it has been added to make it easier for Rodauth users to
|
7
|
+
implement custom handling for specific error types. See the Rodauth
|
8
|
+
documentation for this method to see the list of symbols this method
|
9
|
+
can be called with.
|
10
|
+
|
11
|
+
= Other Improvements
|
12
|
+
|
13
|
+
* When using active_sessions and jwt_refresh together, and allowing for
|
14
|
+
expired JWTs when refreshing, you can now call
|
15
|
+
rodauth.check_active_session before r.rodauth. Previously, this
|
16
|
+
did not work, and you had to call rodauth.check_active_session
|
17
|
+
after r.rodauth.
|
18
|
+
|
19
|
+
* The default templates now also support Bootstrap 5.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A remembered_session_id method has been added for getting the
|
4
|
+
account id from a valid remember token, without modifying the
|
5
|
+
session to log the account in.
|
6
|
+
|
7
|
+
= Other Improvements
|
8
|
+
|
9
|
+
* The jwt_refresh feature's support for allowing refresh with
|
10
|
+
an expired access token now works even if the Rodauth
|
11
|
+
configuration uses an incorrect prefix.
|
12
|
+
|
13
|
+
* The internal account_in_unverified_grace_period? method now
|
14
|
+
returns false if an account has not been loaded and the
|
15
|
+
session has not been logged in. Previously, calling this
|
16
|
+
method in such cases would result in an exception being
|
17
|
+
raised.
|
@@ -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.
|
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.
|
@@ -57,6 +57,7 @@ module Rodauth
|
|
57
57
|
def no_longer_active_session
|
58
58
|
clear_session
|
59
59
|
set_redirect_error_status inactive_session_error_status
|
60
|
+
set_error_reason :inactive_session
|
60
61
|
set_redirect_error_flash active_sessions_error_flash
|
61
62
|
redirect active_sessions_redirect
|
62
63
|
end
|
@@ -176,4 +177,3 @@ module Rodauth
|
|
176
177
|
end
|
177
178
|
end
|
178
179
|
end
|
179
|
-
|
@@ -100,6 +100,7 @@ module Rodauth
|
|
100
100
|
:set_notice_flash,
|
101
101
|
:set_notice_now_flash,
|
102
102
|
:set_redirect_error_flash,
|
103
|
+
:set_error_reason,
|
103
104
|
:set_title,
|
104
105
|
:translate,
|
105
106
|
:update_session
|
@@ -114,6 +115,10 @@ module Rodauth
|
|
114
115
|
:around_rodauth
|
115
116
|
)
|
116
117
|
|
118
|
+
internal_request_method :account_exists?
|
119
|
+
internal_request_method :account_id_for_login
|
120
|
+
internal_request_method :internal_request_eval
|
121
|
+
|
117
122
|
configuration_module_eval do
|
118
123
|
def auth_class_eval(&block)
|
119
124
|
auth.class_eval(&block)
|
@@ -294,6 +299,7 @@ module Rodauth
|
|
294
299
|
|
295
300
|
def login_required
|
296
301
|
set_redirect_error_status(login_required_error_status)
|
302
|
+
set_error_reason :login_required
|
297
303
|
set_redirect_error_flash require_login_error_flash
|
298
304
|
redirect require_login_redirect
|
299
305
|
end
|
@@ -443,7 +449,9 @@ module Rodauth
|
|
443
449
|
end
|
444
450
|
|
445
451
|
def base_url
|
446
|
-
request.
|
452
|
+
url = String.new("#{request.scheme}://#{domain}")
|
453
|
+
url << ":#{request.port}" if request.port != Rack::Request::DEFAULT_PORTS[request.scheme]
|
454
|
+
url
|
447
455
|
end
|
448
456
|
|
449
457
|
def domain
|
@@ -539,6 +547,11 @@ module Rodauth
|
|
539
547
|
def set_response_error_status(status)
|
540
548
|
response.status = status
|
541
549
|
end
|
550
|
+
|
551
|
+
def set_response_error_reason_status(reason, status)
|
552
|
+
set_error_reason(reason)
|
553
|
+
set_response_error_status(status)
|
554
|
+
end
|
542
555
|
|
543
556
|
def throw_error(field, error)
|
544
557
|
set_field_error(field, error)
|
@@ -550,6 +563,14 @@ module Rodauth
|
|
550
563
|
throw_error(field, error)
|
551
564
|
end
|
552
565
|
|
566
|
+
def set_error_reason(reason)
|
567
|
+
end
|
568
|
+
|
569
|
+
def throw_error_reason(reason, status, field, message)
|
570
|
+
set_error_reason(reason)
|
571
|
+
throw_error_status(status, field, message)
|
572
|
+
end
|
573
|
+
|
553
574
|
def use_date_arithmetic?
|
554
575
|
set_deadline_values?
|
555
576
|
end
|
@@ -719,6 +740,10 @@ module Rodauth
|
|
719
740
|
end
|
720
741
|
end
|
721
742
|
|
743
|
+
def internal_request?
|
744
|
+
false
|
745
|
+
end
|
746
|
+
|
722
747
|
def set_session_value(key, value)
|
723
748
|
session[key] = value
|
724
749
|
end
|
@@ -19,6 +19,8 @@ module Rodauth
|
|
19
19
|
|
20
20
|
auth_methods :change_login
|
21
21
|
|
22
|
+
internal_request_method
|
23
|
+
|
22
24
|
route do |r|
|
23
25
|
require_account
|
24
26
|
before_change_login_route
|
@@ -30,7 +32,7 @@ module Rodauth
|
|
30
32
|
r.post do
|
31
33
|
catch_error do
|
32
34
|
if change_login_requires_password? && !password_match?(param(password_param))
|
33
|
-
|
35
|
+
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
|
34
36
|
end
|
35
37
|
|
36
38
|
login = param(login_param)
|
@@ -39,7 +41,7 @@ module Rodauth
|
|
39
41
|
end
|
40
42
|
|
41
43
|
if require_login_confirmation? && login != param(login_confirm_param)
|
42
|
-
|
44
|
+
throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
|
43
45
|
end
|
44
46
|
|
45
47
|
transaction do
|
@@ -65,7 +67,7 @@ module Rodauth
|
|
65
67
|
|
66
68
|
def change_login(login)
|
67
69
|
if account_ds.get(login_column).downcase == login.downcase
|
68
|
-
|
70
|
+
set_login_requirement_error_message(:same_as_current_login, same_as_current_login_message)
|
69
71
|
return false
|
70
72
|
end
|
71
73
|
|
@@ -82,7 +84,7 @@ module Rodauth
|
|
82
84
|
updated = nil
|
83
85
|
raised = raises_uniqueness_violation?{updated = update_account({login_column=>login}, account_ds.exclude(login_column=>login)) == 1}
|
84
86
|
if raised
|
85
|
-
|
87
|
+
set_login_requirement_error_message(:already_an_account_with_this_login, already_an_account_with_this_login_message)
|
86
88
|
end
|
87
89
|
updated && !raised
|
88
90
|
end
|
@@ -22,6 +22,8 @@ module Rodauth
|
|
22
22
|
:invalid_previous_password_message
|
23
23
|
)
|
24
24
|
|
25
|
+
internal_request_method
|
26
|
+
|
25
27
|
route do |r|
|
26
28
|
require_account
|
27
29
|
before_change_password_route
|
@@ -33,16 +35,16 @@ module Rodauth
|
|
33
35
|
r.post do
|
34
36
|
catch_error do
|
35
37
|
if change_password_requires_password? && !password_match?(param(password_param))
|
36
|
-
|
38
|
+
throw_error_reason(:invalid_previous_password, invalid_password_error_status, password_param, invalid_previous_password_message)
|
37
39
|
end
|
38
40
|
|
39
41
|
password = param(new_password_param)
|
40
42
|
if require_password_confirmation? && password != param(password_confirm_param)
|
41
|
-
|
43
|
+
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, new_password_param, passwords_do_not_match_message)
|
42
44
|
end
|
43
45
|
|
44
46
|
if password_match?(password)
|
45
|
-
|
47
|
+
throw_error_reason(:same_as_existing_password, invalid_field_error_status, new_password_param, same_as_existing_password_message)
|
46
48
|
end
|
47
49
|
|
48
50
|
unless password_meets_requirements?(password)
|
@@ -24,6 +24,8 @@ module Rodauth
|
|
24
24
|
:delete_account
|
25
25
|
)
|
26
26
|
|
27
|
+
internal_request_method
|
28
|
+
|
27
29
|
route do |r|
|
28
30
|
require_account
|
29
31
|
before_close_account_route
|
@@ -35,7 +37,7 @@ module Rodauth
|
|
35
37
|
r.post do
|
36
38
|
catch_error do
|
37
39
|
if close_account_requires_password? && !password_match?(param(password_param))
|
38
|
-
|
40
|
+
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
|
39
41
|
end
|
40
42
|
|
41
43
|
transaction do
|
@@ -40,7 +40,7 @@ module Rodauth
|
|
40
40
|
set_notice_flash confirm_password_notice_flash
|
41
41
|
redirect confirm_password_redirect
|
42
42
|
else
|
43
|
-
|
43
|
+
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
|
44
44
|
set_field_error(password_param, invalid_password_message)
|
45
45
|
set_error_flash confirm_password_error_flash
|
46
46
|
confirm_password_view
|
@@ -53,6 +53,7 @@ module Rodauth
|
|
53
53
|
|
54
54
|
if require_password_authentication? && has_password?
|
55
55
|
set_redirect_error_status(password_authentication_required_error_status)
|
56
|
+
set_error_reason :password_authentication_required
|
56
57
|
set_redirect_error_flash password_authentication_required_error_flash
|
57
58
|
set_session_value(confirm_password_redirect_session_key, request.fullpath)
|
58
59
|
redirect password_authentication_required_redirect
|
@@ -89,4 +90,3 @@ module Rodauth
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
end
|
92
|
-
|
@@ -27,6 +27,8 @@ module Rodauth
|
|
27
27
|
:new_account
|
28
28
|
)
|
29
29
|
|
30
|
+
internal_request_method
|
31
|
+
|
30
32
|
route do |r|
|
31
33
|
check_already_logged_in
|
32
34
|
before_create_account_route
|
@@ -43,7 +45,7 @@ module Rodauth
|
|
43
45
|
|
44
46
|
catch_error do
|
45
47
|
if require_login_confirmation? && login != param(login_confirm_param)
|
46
|
-
|
48
|
+
throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
|
47
49
|
end
|
48
50
|
|
49
51
|
unless login_meets_requirements?(login)
|
@@ -52,11 +54,11 @@ module Rodauth
|
|
52
54
|
|
53
55
|
if create_account_set_password?
|
54
56
|
if require_password_confirmation? && password != param(password_confirm_param)
|
55
|
-
|
57
|
+
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
|
56
58
|
end
|
57
59
|
|
58
60
|
unless password_meets_requirements?(password)
|
59
|
-
|
61
|
+
throw_error_reason(:password_does_not_meet_requirements, invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
|
60
62
|
end
|
61
63
|
|
62
64
|
if account_password_hash_column
|
@@ -100,7 +102,7 @@ module Rodauth
|
|
100
102
|
raised = raises_uniqueness_violation?{id = db[accounts_table].insert(account)}
|
101
103
|
|
102
104
|
if raised
|
103
|
-
|
105
|
+
set_login_requirement_error_message(:already_an_account_with_this_login, already_an_account_with_this_login_message)
|
104
106
|
end
|
105
107
|
|
106
108
|
if id
|
@@ -32,7 +32,7 @@ module Rodauth
|
|
32
32
|
|
33
33
|
def password_not_one_of_the_most_common?(password)
|
34
34
|
return true unless password_one_of_most_common?(password)
|
35
|
-
|
35
|
+
set_password_requirement_error_message(:password_is_one_of_the_most_common, password_is_one_of_the_most_common_message)
|
36
36
|
false
|
37
37
|
end
|
38
38
|
end
|
@@ -65,7 +65,7 @@ module Rodauth
|
|
65
65
|
end
|
66
66
|
|
67
67
|
return true unless match
|
68
|
-
|
68
|
+
set_password_requirement_error_message(:password_same_as_previous_password, password_same_as_previous_password_message)
|
69
69
|
false
|
70
70
|
end
|
71
71
|
|
@@ -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
|
@@ -58,6 +62,7 @@ module Rodauth
|
|
58
62
|
_email_auth_request
|
59
63
|
else
|
60
64
|
set_redirect_error_status(no_matching_login_error_status)
|
65
|
+
set_error_reason :no_matching_login
|
61
66
|
set_redirect_error_flash email_auth_request_error_flash
|
62
67
|
end
|
63
68
|
|
@@ -90,6 +95,7 @@ module Rodauth
|
|
90
95
|
key = session[email_auth_session_key] || param(email_auth_key_param)
|
91
96
|
unless account_from_email_auth_key(key)
|
92
97
|
set_redirect_error_status(invalid_key_error_status)
|
98
|
+
set_error_reason :invalid_email_auth_key
|
93
99
|
set_redirect_error_flash email_auth_error_flash
|
94
100
|
redirect email_auth_email_sent_redirect
|
95
101
|
end
|
@@ -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
|