rodauth 0.10.0 → 1.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 +146 -0
- data/README.rdoc +644 -220
- data/Rakefile +99 -11
- data/doc/account_expiration.rdoc +55 -0
- data/doc/base.rdoc +104 -0
- data/doc/change_login.rdoc +29 -0
- data/doc/change_password.rdoc +26 -0
- data/doc/close_account.rdoc +31 -0
- data/doc/confirm_password.rdoc +22 -0
- data/doc/create_account.rdoc +34 -0
- data/doc/disallow_password_reuse.rdoc +37 -0
- data/doc/email_base.rdoc +19 -0
- data/doc/jwt.rdoc +35 -0
- data/doc/lockout.rdoc +83 -0
- data/doc/login.rdoc +27 -0
- data/doc/login_password_requirements_base.rdoc +50 -0
- data/doc/logout.rdoc +21 -0
- data/doc/otp.rdoc +100 -0
- data/doc/password_complexity.rdoc +50 -0
- data/doc/password_expiration.rdoc +52 -0
- data/doc/password_grace_period.rdoc +10 -0
- data/doc/recovery_codes.rdoc +60 -0
- data/doc/release_notes/1.0.0.txt +443 -0
- data/doc/remember.rdoc +82 -0
- data/doc/reset_password.rdoc +70 -0
- data/doc/session_expiration.rdoc +27 -0
- data/doc/single_session.rdoc +43 -0
- data/doc/sms_codes.rdoc +119 -0
- data/doc/two_factor_base.rdoc +27 -0
- data/doc/verify_account.rdoc +70 -0
- data/doc/verify_account_grace_period.rdoc +15 -0
- data/doc/verify_change_login.rdoc +9 -0
- data/lib/roda/plugins/rodauth.rb +3 -262
- data/lib/rodauth.rb +260 -0
- data/lib/rodauth/features/account_expiration.rb +108 -0
- data/lib/rodauth/features/base.rb +479 -0
- data/lib/rodauth/features/change_login.rb +77 -0
- data/lib/rodauth/features/change_password.rb +66 -0
- data/lib/rodauth/features/close_account.rb +82 -0
- data/lib/rodauth/features/confirm_password.rb +51 -0
- data/lib/rodauth/features/create_account.rb +128 -0
- data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
- data/lib/rodauth/features/email_base.rb +63 -0
- data/lib/rodauth/features/jwt.rb +151 -0
- data/lib/rodauth/features/lockout.rb +262 -0
- data/lib/rodauth/features/login.rb +61 -0
- data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
- data/lib/rodauth/features/logout.rb +37 -0
- data/lib/rodauth/features/otp.rb +338 -0
- data/lib/rodauth/features/password_complexity.rb +89 -0
- data/lib/rodauth/features/password_expiration.rb +111 -0
- data/lib/rodauth/features/password_grace_period.rb +46 -0
- data/lib/rodauth/features/recovery_codes.rb +240 -0
- data/lib/rodauth/features/remember.rb +200 -0
- data/lib/rodauth/features/reset_password.rb +207 -0
- data/lib/rodauth/features/session_expiration.rb +55 -0
- data/lib/rodauth/features/single_session.rb +87 -0
- data/lib/rodauth/features/sms_codes.rb +498 -0
- data/lib/rodauth/features/two_factor_base.rb +135 -0
- data/lib/rodauth/features/verify_account.rb +232 -0
- data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
- data/lib/rodauth/features/verify_change_login.rb +20 -0
- data/lib/rodauth/migrations.rb +130 -0
- data/lib/rodauth/version.rb +9 -0
- data/spec/account_expiration_spec.rb +90 -0
- data/spec/all.rb +1 -0
- data/spec/change_login_spec.rb +149 -0
- data/spec/change_password_spec.rb +177 -0
- data/spec/close_account_spec.rb +162 -0
- data/spec/confirm_password_spec.rb +70 -0
- data/spec/create_account_spec.rb +127 -0
- data/spec/disallow_password_reuse_spec.rb +84 -0
- data/spec/lockout_spec.rb +228 -0
- data/spec/login_spec.rb +188 -0
- data/spec/migrate/001_tables.rb +103 -16
- data/spec/migrate/002_account_password_hash_column.rb +11 -0
- data/spec/migrate_password/001_tables.rb +60 -42
- data/spec/migrate_travis/001_tables.rb +116 -0
- data/spec/password_complexity_spec.rb +108 -0
- data/spec/password_expiration_spec.rb +243 -0
- data/spec/password_grace_period_spec.rb +93 -0
- data/spec/remember_spec.rb +424 -0
- data/spec/reset_password_spec.rb +185 -0
- data/spec/rodauth_spec.rb +57 -980
- data/spec/session_expiration_spec.rb +58 -0
- data/spec/single_session_spec.rb +107 -0
- data/spec/spec_helper.rb +202 -0
- data/spec/two_factor_spec.rb +1310 -0
- data/spec/verify_account_grace_period_spec.rb +135 -0
- data/spec/verify_account_spec.rb +142 -0
- data/spec/verify_change_login_spec.rb +46 -0
- data/spec/views/login.str +2 -2
- data/templates/add-recovery-codes.str +2 -0
- data/templates/button.str +5 -0
- data/templates/change-login.str +5 -18
- data/templates/change-password.str +6 -14
- data/templates/close-account.str +3 -6
- data/templates/confirm-password.str +4 -14
- data/templates/create-account.str +6 -30
- data/templates/login-confirm-field.str +6 -0
- data/templates/login-field.str +6 -0
- data/templates/login.str +5 -19
- data/templates/logout.str +2 -6
- data/templates/otp-auth-code-field.str +6 -0
- data/templates/otp-auth.str +8 -0
- data/templates/otp-disable.str +6 -0
- data/templates/otp-setup.str +21 -0
- data/templates/password-confirm-field.str +6 -0
- data/templates/password-field.str +6 -0
- data/templates/recovery-auth.str +12 -0
- data/templates/recovery-codes.str +6 -0
- data/templates/remember.str +8 -12
- data/templates/reset-password-request.str +2 -2
- data/templates/reset-password.str +4 -18
- data/templates/sms-auth.str +6 -0
- data/templates/sms-code-field.str +6 -0
- data/templates/sms-confirm.str +7 -0
- data/templates/sms-disable.str +7 -0
- data/templates/sms-request.str +5 -0
- data/templates/sms-setup.str +12 -0
- data/templates/unlock-account-request.str +3 -7
- data/templates/unlock-account.str +4 -7
- data/templates/verify-account-resend.str +2 -2
- data/templates/verify-account.str +2 -6
- metadata +191 -29
- data/lib/roda/plugins/rodauth/base.rb +0 -428
- data/lib/roda/plugins/rodauth/change_login.rb +0 -48
- data/lib/roda/plugins/rodauth/change_password.rb +0 -42
- data/lib/roda/plugins/rodauth/close_account.rb +0 -42
- data/lib/roda/plugins/rodauth/create_account.rb +0 -92
- data/lib/roda/plugins/rodauth/lockout.rb +0 -292
- data/lib/roda/plugins/rodauth/login.rb +0 -81
- data/lib/roda/plugins/rodauth/logout.rb +0 -36
- data/lib/roda/plugins/rodauth/remember.rb +0 -226
- data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
- data/lib/roda/plugins/rodauth/verify_account.rb +0 -228
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Rodauth
|
|
4
|
+
SingleSession = Feature.define(:single_session) do
|
|
5
|
+
error_flash 'This session has been logged out as another session has become active'
|
|
6
|
+
redirect
|
|
7
|
+
|
|
8
|
+
auth_value_method :single_session_id_column, :id
|
|
9
|
+
auth_value_method :single_session_key_column, :key
|
|
10
|
+
auth_value_method :single_session_session_key, :single_session_key
|
|
11
|
+
auth_value_method :single_session_table, :account_session_keys
|
|
12
|
+
|
|
13
|
+
auth_methods(
|
|
14
|
+
:currently_active_session?,
|
|
15
|
+
:no_longer_active_session,
|
|
16
|
+
:reset_single_session_key,
|
|
17
|
+
:update_single_session_key
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def reset_single_session_key
|
|
21
|
+
if logged_in?
|
|
22
|
+
single_session_ds.update(single_session_key_column=>random_key)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def currently_active_session?
|
|
27
|
+
single_session_key = session[single_session_session_key]
|
|
28
|
+
current_key = single_session_ds.get(single_session_key_column)
|
|
29
|
+
if single_session_key.nil?
|
|
30
|
+
unless current_key
|
|
31
|
+
# No row exists for this user, indicating the feature has never
|
|
32
|
+
# been used, so it is OK to treat the current session as a new
|
|
33
|
+
# session.
|
|
34
|
+
update_single_session_key
|
|
35
|
+
end
|
|
36
|
+
true
|
|
37
|
+
elsif current_key
|
|
38
|
+
timing_safe_eql?(single_session_key, current_key)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_single_session
|
|
43
|
+
if logged_in? && !currently_active_session?
|
|
44
|
+
no_longer_active_session
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def no_longer_active_session
|
|
49
|
+
clear_session
|
|
50
|
+
set_redirect_error_flash single_session_error_flash
|
|
51
|
+
redirect single_session_redirect
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update_single_session_key
|
|
55
|
+
key = random_key
|
|
56
|
+
set_session_value(single_session_session_key, key)
|
|
57
|
+
if single_session_ds.update(single_session_key_column=>key) == 0
|
|
58
|
+
# Don't handle uniqueness violations here. While we could get the stored key from the
|
|
59
|
+
# database, it could lead to two sessions sharing the same key, which this feature is
|
|
60
|
+
# designed to prevent.
|
|
61
|
+
single_session_ds.insert(single_session_id_column=>session_value, single_session_key_column=>key)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def after_close_account
|
|
68
|
+
super if defined?(super)
|
|
69
|
+
single_session_ds.delete
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def before_logout
|
|
73
|
+
reset_single_session_key if request.post?
|
|
74
|
+
super if defined?(super)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def update_session
|
|
78
|
+
super
|
|
79
|
+
update_single_session_key
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def single_session_ds
|
|
83
|
+
db[single_session_table].
|
|
84
|
+
where(single_session_id_column=>session_value)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Rodauth
|
|
4
|
+
SmsCodes = Feature.define(:sms_codes) do
|
|
5
|
+
depends :two_factor_base
|
|
6
|
+
|
|
7
|
+
additional_form_tags 'sms_auth'
|
|
8
|
+
additional_form_tags 'sms_confirm'
|
|
9
|
+
additional_form_tags 'sms_disable'
|
|
10
|
+
additional_form_tags 'sms_request'
|
|
11
|
+
additional_form_tags 'sms_setup'
|
|
12
|
+
|
|
13
|
+
before 'sms_auth'
|
|
14
|
+
before 'sms_confirm'
|
|
15
|
+
before 'sms_disable'
|
|
16
|
+
before 'sms_request'
|
|
17
|
+
before 'sms_setup'
|
|
18
|
+
|
|
19
|
+
after 'sms_confirm'
|
|
20
|
+
after 'sms_disable'
|
|
21
|
+
after 'sms_failure'
|
|
22
|
+
after 'sms_request'
|
|
23
|
+
after 'sms_setup'
|
|
24
|
+
|
|
25
|
+
button 'Authenticate via SMS Code', 'sms_auth'
|
|
26
|
+
button 'Confirm SMS Backup Number', 'sms_confirm'
|
|
27
|
+
button 'Disable Backup SMS Authentication', 'sms_disable'
|
|
28
|
+
button 'Send SMS Code', 'sms_request'
|
|
29
|
+
button 'Setup SMS Backup Number', 'sms_setup'
|
|
30
|
+
|
|
31
|
+
error_flash "Error authenticating via SMS code.", 'sms_invalid_code'
|
|
32
|
+
error_flash "Error disabling SMS authentication", 'sms_disable'
|
|
33
|
+
error_flash "Error setting up SMS authentication", 'sms_setup'
|
|
34
|
+
error_flash "Invalid or out of date SMS confirmation code used, must setup SMS authentication again.", 'sms_invalid_confirmation_code'
|
|
35
|
+
error_flash "No current SMS code for this account", 'no_current_sms_code'
|
|
36
|
+
error_flash "SMS authentication has been locked out.", 'sms_lockout'
|
|
37
|
+
error_flash "SMS authentication has already been setup.", 'sms_already_setup'
|
|
38
|
+
error_flash "SMS authentication has not been setup yet.", 'sms_not_setup'
|
|
39
|
+
error_flash "SMS authentication needs confirmation.", 'sms_needs_confirmation'
|
|
40
|
+
|
|
41
|
+
notice_flash "SMS authentication code has been sent.", 'sms_request'
|
|
42
|
+
notice_flash "SMS authentication has been disabled.", 'sms_disable'
|
|
43
|
+
notice_flash "SMS authentication has been setup.", 'sms_confirm'
|
|
44
|
+
|
|
45
|
+
redirect :sms_already_setup
|
|
46
|
+
redirect :sms_confirm
|
|
47
|
+
redirect :sms_disable
|
|
48
|
+
redirect(:sms_auth){"#{prefix}/#{sms_auth_route}"}
|
|
49
|
+
redirect(:sms_needs_confirmation){"#{prefix}/#{sms_confirm_route}"}
|
|
50
|
+
redirect(:sms_needs_setup){"#{prefix}/#{sms_setup_route}"}
|
|
51
|
+
redirect(:sms_request){"#{prefix}/#{sms_request_route}"}
|
|
52
|
+
|
|
53
|
+
view 'sms-auth', 'Authenticate via SMS Code', 'sms_auth'
|
|
54
|
+
view 'sms-confirm', 'Confirm SMS Backup Number', 'sms_confirm'
|
|
55
|
+
view 'sms-disable', 'Disable Backup SMS Authentication', 'sms_disable'
|
|
56
|
+
view 'sms-request', 'Send SMS Code', 'sms_request'
|
|
57
|
+
view 'sms-setup', 'Setup SMS Backup Number', 'sms_setup'
|
|
58
|
+
|
|
59
|
+
auth_value_method :sms_auth_code_length, 6
|
|
60
|
+
auth_value_method :sms_code_allowed_seconds, 300
|
|
61
|
+
auth_value_method :sms_code_column, :code
|
|
62
|
+
auth_value_method :sms_code_label, 'SMS Code'
|
|
63
|
+
auth_value_method :sms_code_param, 'sms-code'
|
|
64
|
+
auth_value_method :sms_codes_table, :account_sms_codes
|
|
65
|
+
auth_value_method :sms_confirm_code_length, 12
|
|
66
|
+
auth_value_method :sms_failure_limit, 5
|
|
67
|
+
auth_value_method :sms_failures_column, :num_failures
|
|
68
|
+
auth_value_method :sms_id_column, :id
|
|
69
|
+
auth_value_method :sms_invalid_code_message, "invalid SMS code"
|
|
70
|
+
auth_value_method :sms_invalid_phone_message, "invalid SMS phone number"
|
|
71
|
+
auth_value_method :sms_issued_at_column, :code_issued_at
|
|
72
|
+
auth_value_method :sms_phone_column, :phone_number
|
|
73
|
+
auth_value_method :sms_phone_label, 'Phone Number'
|
|
74
|
+
auth_value_method :sms_phone_min_length, 7
|
|
75
|
+
auth_value_method :sms_phone_param, 'sms-phone'
|
|
76
|
+
|
|
77
|
+
auth_cached_method :sms
|
|
78
|
+
|
|
79
|
+
auth_value_methods(
|
|
80
|
+
:sms_lockout_redirect,
|
|
81
|
+
:sms_codes_primary?
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
auth_methods(
|
|
85
|
+
:sms_auth_message,
|
|
86
|
+
:sms_available?,
|
|
87
|
+
:sms_code_issued_at,
|
|
88
|
+
:sms_code_match?,
|
|
89
|
+
:sms_confirm_message,
|
|
90
|
+
:sms_confirmation_match?,
|
|
91
|
+
:sms_current_auth?,
|
|
92
|
+
:sms_disable,
|
|
93
|
+
:sms_failures,
|
|
94
|
+
:sms_locked_out?,
|
|
95
|
+
:sms_needs_confirmation?,
|
|
96
|
+
:sms_new_auth_code,
|
|
97
|
+
:sms_new_confirm_code,
|
|
98
|
+
:sms_normalize_phone,
|
|
99
|
+
:sms_record_failure,
|
|
100
|
+
:sms_remove_failures,
|
|
101
|
+
:sms_send,
|
|
102
|
+
:sms_set_code,
|
|
103
|
+
:sms_setup,
|
|
104
|
+
:sms_setup?,
|
|
105
|
+
:sms_valid_phone?
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
route(:sms_request) do |r|
|
|
109
|
+
require_login
|
|
110
|
+
require_account_session
|
|
111
|
+
require_two_factor_not_authenticated
|
|
112
|
+
require_sms_available
|
|
113
|
+
before_sms_request_route
|
|
114
|
+
|
|
115
|
+
r.get do
|
|
116
|
+
sms_request_view
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
r.post do
|
|
120
|
+
transaction do
|
|
121
|
+
before_sms_request
|
|
122
|
+
sms_send_auth_code
|
|
123
|
+
after_sms_request
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
set_notice_flash sms_request_notice_flash
|
|
127
|
+
redirect sms_auth_redirect
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
route(:sms_auth) do |r|
|
|
132
|
+
require_login
|
|
133
|
+
require_account_session
|
|
134
|
+
require_two_factor_not_authenticated
|
|
135
|
+
require_sms_available
|
|
136
|
+
|
|
137
|
+
unless sms_current_auth?
|
|
138
|
+
if sms_code
|
|
139
|
+
sms_set_code(nil)
|
|
140
|
+
end
|
|
141
|
+
set_redirect_error_flash no_current_sms_code_error_flash
|
|
142
|
+
redirect sms_request_redirect
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
before_sms_auth_route
|
|
146
|
+
|
|
147
|
+
r.get do
|
|
148
|
+
sms_auth_view
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
r.post do
|
|
152
|
+
transaction do
|
|
153
|
+
if sms_code_match?(param(sms_code_param))
|
|
154
|
+
before_sms_auth
|
|
155
|
+
sms_remove_failures
|
|
156
|
+
two_factor_authenticate(:sms_code)
|
|
157
|
+
else
|
|
158
|
+
sms_record_failure
|
|
159
|
+
after_sms_failure
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
set_field_error(sms_code_param, sms_invalid_code_message)
|
|
164
|
+
set_error_flash sms_invalid_code_error_flash
|
|
165
|
+
sms_auth_view
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
route(:sms_setup) do |r|
|
|
170
|
+
require_account
|
|
171
|
+
unless sms_codes_primary?
|
|
172
|
+
require_two_factor_setup
|
|
173
|
+
require_two_factor_authenticated
|
|
174
|
+
end
|
|
175
|
+
require_sms_not_setup
|
|
176
|
+
|
|
177
|
+
if sms_needs_confirmation?
|
|
178
|
+
set_redirect_error_flash sms_needs_confirmation_error_flash
|
|
179
|
+
redirect sms_needs_confirmation_redirect
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
before_sms_setup_route
|
|
183
|
+
|
|
184
|
+
r.get do
|
|
185
|
+
sms_setup_view
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
r.post do
|
|
189
|
+
catch_error do
|
|
190
|
+
unless two_factor_password_match?(param(password_param))
|
|
191
|
+
throw_error(password_param, invalid_password_message)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
phone = sms_normalize_phone(param(sms_phone_param))
|
|
195
|
+
|
|
196
|
+
unless sms_valid_phone?(phone)
|
|
197
|
+
throw_error(sms_phone_param, sms_invalid_phone_message)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
transaction do
|
|
201
|
+
before_sms_setup
|
|
202
|
+
sms_setup(phone)
|
|
203
|
+
sms_send_confirm_code
|
|
204
|
+
after_sms_setup
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
set_notice_flash sms_needs_confirmation_error_flash
|
|
208
|
+
redirect sms_needs_confirmation_redirect
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
set_error_flash sms_setup_error_flash
|
|
212
|
+
sms_setup_view
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
route(:sms_confirm) do |r|
|
|
217
|
+
require_account
|
|
218
|
+
unless sms_codes_primary?
|
|
219
|
+
require_two_factor_setup
|
|
220
|
+
require_two_factor_authenticated
|
|
221
|
+
end
|
|
222
|
+
require_sms_not_setup
|
|
223
|
+
before_sms_confirm_route
|
|
224
|
+
|
|
225
|
+
r.get do
|
|
226
|
+
sms_confirm_view
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
r.post do
|
|
230
|
+
if sms_confirmation_match?(param(sms_code_param))
|
|
231
|
+
transaction do
|
|
232
|
+
before_sms_confirm
|
|
233
|
+
sms_confirm
|
|
234
|
+
after_sms_confirm
|
|
235
|
+
if sms_codes_primary?
|
|
236
|
+
two_factor_authenticate(:sms_code)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
set_notice_flash sms_confirm_notice_flash
|
|
241
|
+
redirect sms_confirm_redirect
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
sms_confirm_failure
|
|
245
|
+
set_redirect_error_flash sms_invalid_confirmation_code_error_flash
|
|
246
|
+
redirect sms_needs_setup_redirect
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
route(:sms_disable) do |r|
|
|
251
|
+
require_account
|
|
252
|
+
require_sms_setup
|
|
253
|
+
before_sms_disable_route
|
|
254
|
+
|
|
255
|
+
r.get do
|
|
256
|
+
sms_disable_view
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
r.post do
|
|
260
|
+
if two_factor_password_match?(param(password_param))
|
|
261
|
+
transaction do
|
|
262
|
+
before_sms_disable
|
|
263
|
+
sms_disable
|
|
264
|
+
if sms_codes_primary?
|
|
265
|
+
two_factor_remove_session
|
|
266
|
+
end
|
|
267
|
+
after_sms_disable
|
|
268
|
+
end
|
|
269
|
+
set_notice_flash sms_disable_notice_flash
|
|
270
|
+
redirect sms_disable_redirect
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
set_field_error(password_param, invalid_password_message)
|
|
274
|
+
set_error_flash sms_disable_error_flash
|
|
275
|
+
sms_disable_view
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def two_factor_need_setup_redirect
|
|
280
|
+
super || (sms_needs_setup_redirect if sms_codes_primary?)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def two_factor_auth_required_redirect
|
|
284
|
+
super || (sms_request_redirect if sms_codes_primary? && sms_available?)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def two_factor_auth_fallback_redirect
|
|
288
|
+
sms_available? ? sms_request_redirect : super
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def two_factor_remove
|
|
292
|
+
super
|
|
293
|
+
sms_disable
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def two_factor_remove_auth_failures
|
|
297
|
+
super
|
|
298
|
+
sms_remove_failures
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def two_factor_authentication_setup?
|
|
302
|
+
super || (sms_codes_primary? && sms_setup?)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def otp_auth_form_footer
|
|
306
|
+
"#{super if defined?(super)}#{"<p><a href=\"#{sms_request_route}\">Authenticate using SMS code</a></p>" if sms_available?}"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def otp_lockout_redirect
|
|
310
|
+
if sms_available?
|
|
311
|
+
sms_request_redirect
|
|
312
|
+
else
|
|
313
|
+
super if defined?(super)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def otp_lockout_error_flash
|
|
318
|
+
msg = super if defined?(super)
|
|
319
|
+
if sms_available?
|
|
320
|
+
msg = "#{msg} Can use SMS code to unlock."
|
|
321
|
+
end
|
|
322
|
+
msg
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def otp_remove
|
|
326
|
+
super if defined?(super)
|
|
327
|
+
unless sms_codes_primary?
|
|
328
|
+
sms_disable
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def require_sms_setup
|
|
333
|
+
unless sms_setup?
|
|
334
|
+
set_redirect_error_flash sms_not_setup_error_flash
|
|
335
|
+
redirect sms_needs_setup_redirect
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def require_sms_not_setup
|
|
340
|
+
if sms_setup?
|
|
341
|
+
set_redirect_error_flash sms_already_setup_error_flash
|
|
342
|
+
redirect sms_already_setup_redirect
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def require_sms_available
|
|
347
|
+
require_sms_setup
|
|
348
|
+
|
|
349
|
+
if sms_locked_out?
|
|
350
|
+
set_redirect_error_flash sms_lockout_error_flash
|
|
351
|
+
redirect sms_lockout_redirect
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def sms_code_match?(code)
|
|
356
|
+
return false unless sms_current_auth?
|
|
357
|
+
timing_safe_eql?(code, sms_code)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def sms_confirmation_match?(code)
|
|
361
|
+
sms_needs_confirmation? && sms_code_match?(code)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def sms_disable
|
|
365
|
+
sms_ds.delete
|
|
366
|
+
@sms = nil
|
|
367
|
+
super if defined?(super)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def sms_confirm_failure
|
|
371
|
+
sms_ds.delete
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def sms_setup(phone_number)
|
|
375
|
+
# Cannot handle uniqueness violation here, as the phone number given may not match the
|
|
376
|
+
# one in the table.
|
|
377
|
+
sms_ds.insert(sms_id_column=>session_value, sms_phone_column=>phone_number)
|
|
378
|
+
remove_instance_variable(:@sms) if instance_variable_defined?(:@sms)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def sms_remove_failures
|
|
382
|
+
update_sms(sms_failures_column => 0, sms_code_column => nil)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def sms_confirm
|
|
386
|
+
sms_remove_failures
|
|
387
|
+
super if defined?(super)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def sms_send_auth_code
|
|
391
|
+
code = sms_new_auth_code
|
|
392
|
+
sms_set_code(code)
|
|
393
|
+
sms_send(sms_phone, sms_auth_message(code))
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def sms_send_confirm_code
|
|
397
|
+
code = sms_new_confirm_code
|
|
398
|
+
sms_set_code(code)
|
|
399
|
+
sms_send(sms_phone, sms_confirm_message(code))
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def sms_valid_phone?(phone)
|
|
403
|
+
phone.length >= sms_phone_min_length
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def sms_lockout_redirect
|
|
407
|
+
_two_factor_auth_required_redirect
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def sms_auth_message(code)
|
|
411
|
+
"SMS authentication code for #{request.host} is #{code}"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def sms_confirm_message(code)
|
|
415
|
+
"SMS confirmation code for #{request.host} is #{code}"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def sms_set_code(code)
|
|
419
|
+
update_sms(sms_code_column=>code, sms_issued_at_column=>Sequel::CURRENT_TIMESTAMP)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def sms_record_failure
|
|
423
|
+
update_sms(sms_failures_column=>Sequel.expr(sms_failures_column)+1)
|
|
424
|
+
sms[sms_failures_column] = sms_ds.get(sms_failures_column)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def sms_phone
|
|
428
|
+
sms[sms_phone_column]
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def sms_code
|
|
432
|
+
sms[sms_code_column]
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def sms_code_issued_at
|
|
436
|
+
convert_timestamp(sms[sms_issued_at_column])
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def sms_failures
|
|
440
|
+
sms[sms_failures_column]
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def sms_setup?
|
|
444
|
+
return false unless sms
|
|
445
|
+
!sms_needs_confirmation?
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def sms_needs_confirmation?
|
|
449
|
+
sms && sms_failures.nil?
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def sms_available?
|
|
453
|
+
sms && !sms_needs_confirmation? && !sms_locked_out?
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def sms_locked_out?
|
|
457
|
+
sms_failures >= sms_failure_limit
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def sms_current_auth?
|
|
461
|
+
sms_code && sms_code_issued_at + sms_code_allowed_seconds > Time.now
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
private
|
|
465
|
+
|
|
466
|
+
def sms_codes_primary?
|
|
467
|
+
!features.include?(:otp)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def sms_normalize_phone(phone)
|
|
471
|
+
phone.to_s.gsub(/\D+/, '')
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def sms_new_auth_code
|
|
475
|
+
SecureRandom.random_number(10**sms_auth_code_length).to_s.rjust(sms_auth_code_length, "0")
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def sms_new_confirm_code
|
|
479
|
+
SecureRandom.random_number(10**sms_confirm_code_length).to_s.rjust(sms_confirm_code_length, "0")
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def sms_send(phone, message)
|
|
483
|
+
raise NotImplementedError, "sms_send needs to be defined in the Rodauth configuration for SMS sending to work"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def update_sms(values)
|
|
487
|
+
update_hash_ds(sms, sms_ds, values)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def _sms
|
|
491
|
+
sms_ds.first
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def sms_ds
|
|
495
|
+
db[sms_codes_table].where(sms_id_column=>session_value)
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|