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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +146 -0
  3. data/README.rdoc +644 -220
  4. data/Rakefile +99 -11
  5. data/doc/account_expiration.rdoc +55 -0
  6. data/doc/base.rdoc +104 -0
  7. data/doc/change_login.rdoc +29 -0
  8. data/doc/change_password.rdoc +26 -0
  9. data/doc/close_account.rdoc +31 -0
  10. data/doc/confirm_password.rdoc +22 -0
  11. data/doc/create_account.rdoc +34 -0
  12. data/doc/disallow_password_reuse.rdoc +37 -0
  13. data/doc/email_base.rdoc +19 -0
  14. data/doc/jwt.rdoc +35 -0
  15. data/doc/lockout.rdoc +83 -0
  16. data/doc/login.rdoc +27 -0
  17. data/doc/login_password_requirements_base.rdoc +50 -0
  18. data/doc/logout.rdoc +21 -0
  19. data/doc/otp.rdoc +100 -0
  20. data/doc/password_complexity.rdoc +50 -0
  21. data/doc/password_expiration.rdoc +52 -0
  22. data/doc/password_grace_period.rdoc +10 -0
  23. data/doc/recovery_codes.rdoc +60 -0
  24. data/doc/release_notes/1.0.0.txt +443 -0
  25. data/doc/remember.rdoc +82 -0
  26. data/doc/reset_password.rdoc +70 -0
  27. data/doc/session_expiration.rdoc +27 -0
  28. data/doc/single_session.rdoc +43 -0
  29. data/doc/sms_codes.rdoc +119 -0
  30. data/doc/two_factor_base.rdoc +27 -0
  31. data/doc/verify_account.rdoc +70 -0
  32. data/doc/verify_account_grace_period.rdoc +15 -0
  33. data/doc/verify_change_login.rdoc +9 -0
  34. data/lib/roda/plugins/rodauth.rb +3 -262
  35. data/lib/rodauth.rb +260 -0
  36. data/lib/rodauth/features/account_expiration.rb +108 -0
  37. data/lib/rodauth/features/base.rb +479 -0
  38. data/lib/rodauth/features/change_login.rb +77 -0
  39. data/lib/rodauth/features/change_password.rb +66 -0
  40. data/lib/rodauth/features/close_account.rb +82 -0
  41. data/lib/rodauth/features/confirm_password.rb +51 -0
  42. data/lib/rodauth/features/create_account.rb +128 -0
  43. data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
  44. data/lib/rodauth/features/email_base.rb +63 -0
  45. data/lib/rodauth/features/jwt.rb +151 -0
  46. data/lib/rodauth/features/lockout.rb +262 -0
  47. data/lib/rodauth/features/login.rb +61 -0
  48. data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
  49. data/lib/rodauth/features/logout.rb +37 -0
  50. data/lib/rodauth/features/otp.rb +338 -0
  51. data/lib/rodauth/features/password_complexity.rb +89 -0
  52. data/lib/rodauth/features/password_expiration.rb +111 -0
  53. data/lib/rodauth/features/password_grace_period.rb +46 -0
  54. data/lib/rodauth/features/recovery_codes.rb +240 -0
  55. data/lib/rodauth/features/remember.rb +200 -0
  56. data/lib/rodauth/features/reset_password.rb +207 -0
  57. data/lib/rodauth/features/session_expiration.rb +55 -0
  58. data/lib/rodauth/features/single_session.rb +87 -0
  59. data/lib/rodauth/features/sms_codes.rb +498 -0
  60. data/lib/rodauth/features/two_factor_base.rb +135 -0
  61. data/lib/rodauth/features/verify_account.rb +232 -0
  62. data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
  63. data/lib/rodauth/features/verify_change_login.rb +20 -0
  64. data/lib/rodauth/migrations.rb +130 -0
  65. data/lib/rodauth/version.rb +9 -0
  66. data/spec/account_expiration_spec.rb +90 -0
  67. data/spec/all.rb +1 -0
  68. data/spec/change_login_spec.rb +149 -0
  69. data/spec/change_password_spec.rb +177 -0
  70. data/spec/close_account_spec.rb +162 -0
  71. data/spec/confirm_password_spec.rb +70 -0
  72. data/spec/create_account_spec.rb +127 -0
  73. data/spec/disallow_password_reuse_spec.rb +84 -0
  74. data/spec/lockout_spec.rb +228 -0
  75. data/spec/login_spec.rb +188 -0
  76. data/spec/migrate/001_tables.rb +103 -16
  77. data/spec/migrate/002_account_password_hash_column.rb +11 -0
  78. data/spec/migrate_password/001_tables.rb +60 -42
  79. data/spec/migrate_travis/001_tables.rb +116 -0
  80. data/spec/password_complexity_spec.rb +108 -0
  81. data/spec/password_expiration_spec.rb +243 -0
  82. data/spec/password_grace_period_spec.rb +93 -0
  83. data/spec/remember_spec.rb +424 -0
  84. data/spec/reset_password_spec.rb +185 -0
  85. data/spec/rodauth_spec.rb +57 -980
  86. data/spec/session_expiration_spec.rb +58 -0
  87. data/spec/single_session_spec.rb +107 -0
  88. data/spec/spec_helper.rb +202 -0
  89. data/spec/two_factor_spec.rb +1310 -0
  90. data/spec/verify_account_grace_period_spec.rb +135 -0
  91. data/spec/verify_account_spec.rb +142 -0
  92. data/spec/verify_change_login_spec.rb +46 -0
  93. data/spec/views/login.str +2 -2
  94. data/templates/add-recovery-codes.str +2 -0
  95. data/templates/button.str +5 -0
  96. data/templates/change-login.str +5 -18
  97. data/templates/change-password.str +6 -14
  98. data/templates/close-account.str +3 -6
  99. data/templates/confirm-password.str +4 -14
  100. data/templates/create-account.str +6 -30
  101. data/templates/login-confirm-field.str +6 -0
  102. data/templates/login-field.str +6 -0
  103. data/templates/login.str +5 -19
  104. data/templates/logout.str +2 -6
  105. data/templates/otp-auth-code-field.str +6 -0
  106. data/templates/otp-auth.str +8 -0
  107. data/templates/otp-disable.str +6 -0
  108. data/templates/otp-setup.str +21 -0
  109. data/templates/password-confirm-field.str +6 -0
  110. data/templates/password-field.str +6 -0
  111. data/templates/recovery-auth.str +12 -0
  112. data/templates/recovery-codes.str +6 -0
  113. data/templates/remember.str +8 -12
  114. data/templates/reset-password-request.str +2 -2
  115. data/templates/reset-password.str +4 -18
  116. data/templates/sms-auth.str +6 -0
  117. data/templates/sms-code-field.str +6 -0
  118. data/templates/sms-confirm.str +7 -0
  119. data/templates/sms-disable.str +7 -0
  120. data/templates/sms-request.str +5 -0
  121. data/templates/sms-setup.str +12 -0
  122. data/templates/unlock-account-request.str +3 -7
  123. data/templates/unlock-account.str +4 -7
  124. data/templates/verify-account-resend.str +2 -2
  125. data/templates/verify-account.str +2 -6
  126. metadata +191 -29
  127. data/lib/roda/plugins/rodauth/base.rb +0 -428
  128. data/lib/roda/plugins/rodauth/change_login.rb +0 -48
  129. data/lib/roda/plugins/rodauth/change_password.rb +0 -42
  130. data/lib/roda/plugins/rodauth/close_account.rb +0 -42
  131. data/lib/roda/plugins/rodauth/create_account.rb +0 -92
  132. data/lib/roda/plugins/rodauth/lockout.rb +0 -292
  133. data/lib/roda/plugins/rodauth/login.rb +0 -81
  134. data/lib/roda/plugins/rodauth/logout.rb +0 -36
  135. data/lib/roda/plugins/rodauth/remember.rb +0 -226
  136. data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
  137. 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