rodauth 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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