rodauth 1.22.0 → 2.3.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 +190 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +210 -80
- 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/guides/admin_activation.rdoc +46 -0
- data/doc/guides/already_authenticated.rdoc +10 -0
- data/doc/guides/alternative_login.rdoc +46 -0
- data/doc/guides/create_account_programmatically.rdoc +38 -0
- data/doc/guides/delay_password.rdoc +25 -0
- data/doc/guides/email_only.rdoc +16 -0
- data/doc/guides/i18n.rdoc +26 -0
- data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
- data/doc/guides/links.rdoc +12 -0
- data/doc/guides/login_return.rdoc +37 -0
- data/doc/guides/password_column.rdoc +25 -0
- data/doc/guides/password_confirmation.rdoc +37 -0
- data/doc/guides/password_requirements.rdoc +30 -0
- data/doc/guides/paths.rdoc +36 -0
- data/doc/guides/query_params.rdoc +9 -0
- data/doc/guides/redirects.rdoc +17 -0
- data/doc/guides/registration_field.rdoc +68 -0
- data/doc/guides/require_mfa.rdoc +30 -0
- data/doc/guides/reset_password_autologin.rdoc +21 -0
- data/doc/guides/status_column.rdoc +28 -0
- data/doc/guides/totp_or_recovery.rdoc +16 -0
- 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 +23 -8
- data/doc/lockout.rdoc +17 -15
- data/doc/login.rdoc +17 -2
- data/doc/login_password_requirements_base.rdoc +18 -37
- data/doc/logout.rdoc +2 -2
- data/doc/otp.rdoc +25 -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.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/release_notes/2.2.0.txt +39 -0
- data/doc/release_notes/2.3.0.txt +37 -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 +36 -28
- data/lib/rodauth/features/account_expiration.rb +5 -5
- data/lib/rodauth/features/active_sessions.rb +158 -0
- data/lib/rodauth/features/audit_logging.rb +98 -0
- data/lib/rodauth/features/base.rb +144 -43
- data/lib/rodauth/features/change_password_notify.rb +2 -2
- data/lib/rodauth/features/close_account.rb +8 -6
- 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 +31 -30
- 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 +63 -16
- data/lib/rodauth/features/jwt_cors.rb +15 -15
- data/lib/rodauth/features/jwt_refresh.rb +42 -13
- data/lib/rodauth/features/lockout.rb +12 -14
- data/lib/rodauth/features/login.rb +64 -15
- data/lib/rodauth/features/login_password_requirements_base.rb +13 -8
- data/lib/rodauth/features/otp.rb +77 -80
- data/lib/rodauth/features/password_complexity.rb +8 -13
- 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 +7 -10
- data/lib/rodauth/features/single_session.rb +8 -6
- data/lib/rodauth/features/sms_codes.rb +62 -72
- data/lib/rodauth/features/two_factor_base.rb +134 -30
- data/lib/rodauth/features/verify_account.rb +29 -21
- data/lib/rodauth/features/verify_account_grace_period.rb +18 -9
- data/lib/rodauth/features/verify_login_change.rb +12 -11
- data/lib/rodauth/features/webauthn.rb +505 -0
- data/lib/rodauth/features/webauthn_login.rb +70 -0
- data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
- data/lib/rodauth/migrations.rb +16 -5
- 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 +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-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-request.str +4 -4
- data/templates/unlock-account.str +1 -1
- data/templates/verify-account-resend.str +3 -3
- 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 +94 -54
- 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_cors_spec.rb +0 -57
- 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 -1462
- 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,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
|
|
@@ -17,7 +17,7 @@ module Rodauth
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def send_password_changed_email
|
|
20
|
-
create_password_changed_email
|
|
20
|
+
send_email(create_password_changed_email)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def create_password_changed_email
|
|
@@ -33,7 +33,11 @@ module Rodauth
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
r.post do
|
|
36
|
-
|
|
36
|
+
catch_error do
|
|
37
|
+
if close_account_requires_password? && !password_match?(param(password_param))
|
|
38
|
+
throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
|
|
39
|
+
end
|
|
40
|
+
|
|
37
41
|
transaction do
|
|
38
42
|
before_close_account
|
|
39
43
|
close_account
|
|
@@ -46,12 +50,10 @@ module Rodauth
|
|
|
46
50
|
|
|
47
51
|
set_notice_flash close_account_notice_flash
|
|
48
52
|
redirect close_account_redirect
|
|
49
|
-
else
|
|
50
|
-
set_response_error_status(invalid_password_error_status)
|
|
51
|
-
set_field_error(password_param, invalid_password_message)
|
|
52
|
-
set_error_flash close_account_error_flash
|
|
53
|
-
close_account_view
|
|
54
53
|
end
|
|
54
|
+
|
|
55
|
+
set_error_flash close_account_error_flash
|
|
56
|
+
close_account_view
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
|
|
@@ -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
|
|
@@ -32,6 +30,7 @@ module Rodauth
|
|
|
32
30
|
route do |r|
|
|
33
31
|
check_already_logged_in
|
|
34
32
|
before_create_account_route
|
|
33
|
+
@password_field_autocomplete_value = 'new-password'
|
|
35
34
|
|
|
36
35
|
r.get do
|
|
37
36
|
create_account_view
|
|
@@ -76,7 +75,7 @@ module Rodauth
|
|
|
76
75
|
end
|
|
77
76
|
after_create_account
|
|
78
77
|
if create_account_autologin?
|
|
79
|
-
|
|
78
|
+
autologin_session('create_account')
|
|
80
79
|
end
|
|
81
80
|
set_notice_flash create_account_notice_flash
|
|
82
81
|
redirect create_account_redirect
|
|
@@ -88,14 +87,6 @@ module Rodauth
|
|
|
88
87
|
end
|
|
89
88
|
end
|
|
90
89
|
|
|
91
|
-
def create_account_link
|
|
92
|
-
"<p><a href=\"#{prefix}/#{create_account_route}\">Create a New Account</a></p>"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def login_form_footer
|
|
96
|
-
super + create_account_link
|
|
97
|
-
end
|
|
98
|
-
|
|
99
90
|
def set_new_account_password(password)
|
|
100
91
|
account[account_password_hash_column] = password_hash(password)
|
|
101
92
|
end
|
|
@@ -121,6 +112,10 @@ module Rodauth
|
|
|
121
112
|
|
|
122
113
|
private
|
|
123
114
|
|
|
115
|
+
def _login_form_footer_links
|
|
116
|
+
super << [10, create_account_path, create_account_link_text]
|
|
117
|
+
end
|
|
118
|
+
|
|
124
119
|
def _new_account(login)
|
|
125
120
|
acc = {login_column=>login}
|
|
126
121
|
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
|
-
|
|
97
|
+
login('email_auth')
|
|
101
98
|
end
|
|
102
99
|
end
|
|
103
100
|
|
|
@@ -135,7 +132,7 @@ module Rodauth
|
|
|
135
132
|
end
|
|
136
133
|
|
|
137
134
|
def send_email_auth_email
|
|
138
|
-
create_email_auth_email
|
|
135
|
+
send_email(create_email_auth_email)
|
|
139
136
|
end
|
|
140
137
|
|
|
141
138
|
def email_auth_email_link
|
|
@@ -148,44 +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 @email_auth_request_form
|
|
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
|
-
# show form below to also login via email link.
|
|
170
|
-
super
|
|
171
|
-
@email_auth_request_form = email_auth_request_form
|
|
172
|
-
end
|
|
153
|
+
# If forcing email auth, just send the email link.
|
|
154
|
+
_email_auth_request_and_redirect if force_email_auth?
|
|
155
|
+
|
|
156
|
+
super
|
|
173
157
|
end
|
|
174
158
|
|
|
175
159
|
def use_multi_phase_login?
|
|
176
160
|
true
|
|
177
161
|
end
|
|
178
162
|
|
|
179
|
-
def
|
|
180
|
-
|
|
163
|
+
def possible_authentication_methods
|
|
164
|
+
methods = super
|
|
165
|
+
methods << 'email_auth' if !methods.include?('password') && allow_email_auth?
|
|
166
|
+
methods
|
|
181
167
|
end
|
|
182
168
|
|
|
183
169
|
private
|
|
184
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
|
+
|
|
185
177
|
def email_auth_email_recently_sent?
|
|
186
178
|
(email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
|
|
187
179
|
end
|
|
188
180
|
|
|
181
|
+
def _email_auth_request_and_redirect
|
|
182
|
+
_email_auth_request
|
|
183
|
+
redirect email_auth_email_sent_redirect
|
|
184
|
+
end
|
|
185
|
+
|
|
189
186
|
def _email_auth_request
|
|
190
187
|
if email_auth_email_recently_sent?
|
|
191
188
|
set_redirect_error_flash email_auth_email_recently_sent_error_flash
|
|
@@ -205,6 +202,10 @@ module Rodauth
|
|
|
205
202
|
|
|
206
203
|
attr_reader :email_auth_key_value
|
|
207
204
|
|
|
205
|
+
def allow_email_auth?
|
|
206
|
+
defined?(super) ? super : true
|
|
207
|
+
end
|
|
208
|
+
|
|
208
209
|
def after_login
|
|
209
210
|
# Remove the email auth key after any login, even if
|
|
210
211
|
# it is a password login. This is done to invalidate
|
|
@@ -214,7 +215,7 @@ module Rodauth
|
|
|
214
215
|
# that allows login access to the account becomes a
|
|
215
216
|
# security liability, and it is best to remove it.
|
|
216
217
|
remove_email_auth_key
|
|
217
|
-
super
|
|
218
|
+
super
|
|
218
219
|
end
|
|
219
220
|
|
|
220
221
|
def after_close_account
|
|
@@ -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
|
|
|
@@ -14,7 +14,8 @@ module Rodauth
|
|
|
14
14
|
|
|
15
15
|
auth_methods(
|
|
16
16
|
:create_email,
|
|
17
|
-
:email_to
|
|
17
|
+
:email_to,
|
|
18
|
+
:send_email
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
def post_configure
|
|
@@ -24,6 +25,10 @@ module Rodauth
|
|
|
24
25
|
|
|
25
26
|
private
|
|
26
27
|
|
|
28
|
+
def send_email(email)
|
|
29
|
+
email.deliver!
|
|
30
|
+
end
|
|
31
|
+
|
|
27
32
|
def create_email(subject, body)
|
|
28
33
|
create_email_to(email_to, subject, body)
|
|
29
34
|
end
|
|
@@ -38,7 +43,7 @@ module Rodauth
|
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def email_from
|
|
41
|
-
"webmaster@#{
|
|
46
|
+
"webmaster@#{domain}"
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def email_to
|
|
@@ -46,7 +51,7 @@ module Rodauth
|
|
|
46
51
|
end
|
|
47
52
|
|
|
48
53
|
def token_link(route, param, key)
|
|
49
|
-
"#{
|
|
54
|
+
route_url(route, param => "#{account_id}#{token_separator}#{convert_email_token_key(key)}")
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
def convert_email_token_key(key)
|
|
@@ -3,54 +3,74 @@
|
|
|
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
|
-
sess = super
|
|
11
|
-
return sess if sess[session_key]
|
|
12
|
-
return sess unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
|
|
13
|
-
username, password = token.unpack("m*").first.split(/:/, 2)
|
|
8
|
+
def logged_in?
|
|
9
|
+
ret = super
|
|
14
10
|
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
11
|
+
if !ret && !defined?(@checked_http_basic_auth)
|
|
12
|
+
http_basic_auth
|
|
13
|
+
ret = super
|
|
38
14
|
end
|
|
39
15
|
|
|
40
|
-
|
|
16
|
+
ret
|
|
41
17
|
end
|
|
42
18
|
|
|
43
|
-
private
|
|
44
|
-
|
|
45
19
|
def require_login
|
|
46
|
-
if
|
|
20
|
+
if require_http_basic_auth?
|
|
21
|
+
require_http_basic_auth
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def require_http_basic_auth
|
|
28
|
+
unless http_basic_auth
|
|
47
29
|
set_http_basic_auth_error_response
|
|
48
30
|
request.halt
|
|
49
31
|
end
|
|
32
|
+
end
|
|
50
33
|
|
|
51
|
-
|
|
34
|
+
def http_basic_auth
|
|
35
|
+
return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
|
|
36
|
+
|
|
37
|
+
@checked_http_basic_auth = nil
|
|
38
|
+
return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
|
|
39
|
+
|
|
40
|
+
username, password = token.unpack("m*").first.split(/:/, 2)
|
|
41
|
+
return unless username && password
|
|
42
|
+
|
|
43
|
+
catch_error do
|
|
44
|
+
unless account_from_login(username)
|
|
45
|
+
throw_basic_auth_error(login_param, no_matching_login_message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
before_login_attempt
|
|
49
|
+
|
|
50
|
+
unless open_account?
|
|
51
|
+
throw_basic_auth_error(login_param, no_matching_login_message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
unless password_match?(password)
|
|
55
|
+
after_login_failure
|
|
56
|
+
throw_basic_auth_error(password_param, invalid_password_message)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
transaction do
|
|
60
|
+
before_login
|
|
61
|
+
login_session('password')
|
|
62
|
+
after_login
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@checked_http_basic_auth = true
|
|
66
|
+
return true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
nil
|
|
52
70
|
end
|
|
53
71
|
|
|
72
|
+
private
|
|
73
|
+
|
|
54
74
|
def set_http_basic_auth_error_response
|
|
55
75
|
response.status = 401
|
|
56
76
|
response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""
|