rodauth 1.22.0 → 1.23.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +12 -0
  3. data/README.rdoc +5 -3
  4. data/doc/email_base.rdoc +1 -0
  5. data/doc/release_notes/1.23.0.txt +32 -0
  6. data/lib/rodauth.rb +5 -2
  7. data/lib/rodauth/features/base.rb +8 -0
  8. data/lib/rodauth/features/change_password_notify.rb +1 -1
  9. data/lib/rodauth/features/create_account.rb +1 -1
  10. data/lib/rodauth/features/email_auth.rb +3 -4
  11. data/lib/rodauth/features/email_base.rb +7 -2
  12. data/lib/rodauth/features/lockout.rb +1 -1
  13. data/lib/rodauth/features/login.rb +6 -2
  14. data/lib/rodauth/features/otp.rb +6 -3
  15. data/lib/rodauth/features/password_expiration.rb +1 -1
  16. data/lib/rodauth/features/recovery_codes.rb +3 -3
  17. data/lib/rodauth/features/reset_password.rb +2 -2
  18. data/lib/rodauth/features/sms_codes.rb +5 -5
  19. data/lib/rodauth/features/verify_account.rb +2 -2
  20. data/lib/rodauth/features/verify_login_change.rb +1 -1
  21. data/lib/rodauth/version.rb +1 -1
  22. data/templates/email-auth-request-form.str +2 -2
  23. data/templates/reset-password-request.str +3 -3
  24. data/templates/unlock-account-request.str +3 -3
  25. data/templates/verify-account-resend.str +3 -3
  26. metadata +5 -43
  27. data/Rakefile +0 -179
  28. data/spec/account_expiration_spec.rb +0 -225
  29. data/spec/all.rb +0 -1
  30. data/spec/change_login_spec.rb +0 -156
  31. data/spec/change_password_notify_spec.rb +0 -33
  32. data/spec/change_password_spec.rb +0 -202
  33. data/spec/close_account_spec.rb +0 -162
  34. data/spec/confirm_password_spec.rb +0 -70
  35. data/spec/create_account_spec.rb +0 -127
  36. data/spec/disallow_common_passwords_spec.rb +0 -93
  37. data/spec/disallow_password_reuse_spec.rb +0 -179
  38. data/spec/email_auth_spec.rb +0 -285
  39. data/spec/http_basic_auth_spec.rb +0 -143
  40. data/spec/jwt_cors_spec.rb +0 -57
  41. data/spec/jwt_refresh_spec.rb +0 -256
  42. data/spec/jwt_spec.rb +0 -235
  43. data/spec/lockout_spec.rb +0 -250
  44. data/spec/login_spec.rb +0 -328
  45. data/spec/migrate/001_tables.rb +0 -184
  46. data/spec/migrate/002_account_password_hash_column.rb +0 -11
  47. data/spec/migrate_password/001_tables.rb +0 -73
  48. data/spec/migrate_travis/001_tables.rb +0 -141
  49. data/spec/password_complexity_spec.rb +0 -109
  50. data/spec/password_expiration_spec.rb +0 -244
  51. data/spec/password_grace_period_spec.rb +0 -93
  52. data/spec/remember_spec.rb +0 -451
  53. data/spec/reset_password_spec.rb +0 -229
  54. data/spec/rodauth_spec.rb +0 -343
  55. data/spec/session_expiration_spec.rb +0 -58
  56. data/spec/single_session_spec.rb +0 -127
  57. data/spec/spec_helper.rb +0 -327
  58. data/spec/two_factor_spec.rb +0 -1462
  59. data/spec/update_password_hash_spec.rb +0 -40
  60. data/spec/verify_account_grace_period_spec.rb +0 -171
  61. data/spec/verify_account_spec.rb +0 -240
  62. data/spec/verify_change_login_spec.rb +0 -46
  63. data/spec/verify_login_change_spec.rb +0 -232
  64. data/spec/views/layout-other.str +0 -11
  65. data/spec/views/layout.str +0 -11
  66. data/spec/views/login.str +0 -21
@@ -1,1462 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- require 'rotp'
4
-
5
- describe 'Rodauth OTP feature' do
6
- secret_length = (ROTP::Base32.respond_to?(:random_base32) ? ROTP::Base32.random_base32 : ROTP::Base32.random).length
7
-
8
- def reset_otp_last_use
9
- DB[:account_otp_keys].update(:last_use=>Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, :seconds=>600))
10
- end
11
-
12
- it "should allow two factor authentication setup, login, recovery, removal" do
13
- sms_phone = sms_message = nil
14
- hmac_secret = '123'
15
- rodauth do
16
- enable :login, :logout, :otp, :recovery_codes, :sms_codes
17
- otp_drift 10
18
- hmac_secret do
19
- hmac_secret
20
- end
21
- sms_send do |phone, msg|
22
- proc{super(phone, msg)}.must_raise NotImplementedError
23
- sms_phone = phone
24
- sms_message = msg
25
- end
26
- end
27
- roda do |r|
28
- r.rodauth
29
-
30
- r.redirect '/login' unless rodauth.logged_in?
31
-
32
- if rodauth.two_factor_authentication_setup?
33
- r.redirect '/otp-auth' unless rodauth.authenticated?
34
- view :content=>"With OTP"
35
- else
36
- view :content=>"Without OTP"
37
- end
38
- end
39
-
40
- login
41
- page.html.must_include('Without OTP')
42
-
43
- %w'/otp-disable /recovery-auth /recovery-codes /sms-setup /sms-disable /sms-confirm /sms-request /sms-auth /otp-auth'.each do |path|
44
- visit path
45
- page.find('#error_flash').text.must_equal 'This account has not been setup for two factor authentication'
46
- page.current_path.must_equal '/otp-setup'
47
- end
48
-
49
- page.title.must_equal 'Setup Two Factor Authentication'
50
- page.html.must_include '<svg'
51
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
52
- totp = ROTP::TOTP.new(secret)
53
- fill_in 'Password', :with=>'asdf'
54
- click_button 'Setup Two Factor Authentication'
55
- page.find('#error_flash').text.must_equal 'Error setting up two factor authentication'
56
- page.html.must_include 'invalid password'
57
-
58
- fill_in 'Password', :with=>'0123456789'
59
- fill_in 'Authentication Code', :with=>"asdf"
60
- click_button 'Setup Two Factor Authentication'
61
- page.find('#error_flash').text.must_equal 'Error setting up two factor authentication'
62
- page.html.must_include 'Invalid authentication code'
63
-
64
- hmac_secret = "321"
65
- fill_in 'Password', :with=>'0123456789'
66
- fill_in 'Authentication Code', :with=>totp.now
67
- click_button 'Setup Two Factor Authentication'
68
- page.find('#error_flash').text.must_equal 'Error setting up two factor authentication'
69
-
70
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
71
- totp = ROTP::TOTP.new(secret)
72
- fill_in 'Password', :with=>'0123456789'
73
- fill_in 'Authentication Code', :with=>totp.now
74
- click_button 'Setup Two Factor Authentication'
75
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
76
- page.current_path.must_equal '/'
77
- page.html.must_include 'With OTP'
78
-
79
- logout
80
- login
81
- page.current_path.must_equal '/otp-auth'
82
-
83
- page.find_by_id('otp-auth-code')[:autocomplete].must_equal 'off'
84
-
85
- %w'/otp-disable /recovery-codes /otp-setup /sms-setup /sms-disable /sms-confirm'.each do |path|
86
- visit path
87
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
88
- page.current_path.must_equal '/otp-auth'
89
- end
90
-
91
- page.title.must_equal 'Enter Authentication Code'
92
- fill_in 'Authentication Code', :with=>"asdf"
93
- click_button 'Authenticate via 2nd Factor'
94
- page.find('#error_flash').text.must_equal 'Error logging in via two factor authentication'
95
- page.html.must_include 'Invalid authentication code'
96
-
97
- fill_in 'Authentication Code', :with=>"#{totp.now[0..2]} #{totp.now[3..-1]}"
98
- click_button 'Authenticate via 2nd Factor'
99
- page.find('#error_flash').text.must_equal 'Error logging in via two factor authentication'
100
- page.html.must_include 'Invalid authentication code'
101
- reset_otp_last_use
102
-
103
- hmac_secret = '123'
104
- fill_in 'Authentication Code', :with=>"#{totp.now[0..2]} #{totp.now[3..-1]}"
105
- click_button 'Authenticate via 2nd Factor'
106
- page.find('#error_flash').text.must_equal 'Error logging in via two factor authentication'
107
- page.html.must_include 'Invalid authentication code'
108
- reset_otp_last_use
109
-
110
- hmac_secret = '321'
111
- fill_in 'Authentication Code', :with=>"#{totp.now[0..2]} #{totp.now[3..-1]}"
112
- click_button 'Authenticate via 2nd Factor'
113
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
114
- page.html.must_include 'With OTP'
115
- reset_otp_last_use
116
-
117
- visit '/otp-setup'
118
- page.find('#error_flash').text.must_equal 'You have already setup two factor authentication'
119
-
120
- %w'/otp-auth /recovery-auth /sms-request /sms-auth'.each do |path|
121
- visit path
122
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
123
- end
124
-
125
- visit '/sms-disable'
126
- page.find('#error_flash').text.must_equal 'SMS authentication has not been setup yet.'
127
-
128
- visit '/sms-setup'
129
- page.title.must_equal 'Setup SMS Backup Number'
130
- fill_in 'Password', :with=>'012345678'
131
- fill_in 'Phone Number', :with=>'(123) 456'
132
- click_button 'Setup SMS Backup Number'
133
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
134
- page.html.must_include 'invalid password'
135
-
136
- fill_in 'Password', :with=>'0123456789'
137
- click_button 'Setup SMS Backup Number'
138
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
139
- page.html.must_include 'invalid SMS phone number'
140
-
141
- fill_in 'Password', :with=>'0123456789'
142
- fill_in 'Phone Number', :with=>'(123) 456-7890'
143
- click_button 'Setup SMS Backup Number'
144
- page.find('#notice_flash').text.must_equal 'SMS authentication needs confirmation.'
145
- sms_phone.must_equal '1234567890'
146
- sms_message.must_match(/\ASMS confirmation code for www\.example\.com is \d{12}\z/)
147
-
148
- page.title.must_equal 'Confirm SMS Backup Number'
149
- fill_in 'SMS Code', :with=>"asdf"
150
- click_button 'Confirm SMS Backup Number'
151
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
152
-
153
- fill_in 'Password', :with=>'0123456789'
154
- fill_in 'Phone Number', :with=>'(123) 456-7890'
155
- click_button 'Setup SMS Backup Number'
156
-
157
- visit '/sms-setup'
158
- page.find('#error_flash').text.must_equal 'SMS authentication needs confirmation.'
159
- page.title.must_equal 'Confirm SMS Backup Number'
160
-
161
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
162
- sms_code = sms_message[/\d{12}\z/]
163
- fill_in 'SMS Code', :with=>sms_code
164
- click_button 'Confirm SMS Backup Number'
165
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
166
-
167
- fill_in 'Password', :with=>'0123456789'
168
- fill_in 'Phone Number', :with=>'(123) 456-7890'
169
- click_button 'Setup SMS Backup Number'
170
- sms_code = sms_message[/\d{12}\z/]
171
- fill_in 'SMS Code', :with=>sms_code
172
- click_button 'Confirm SMS Backup Number'
173
- page.find('#notice_flash').text.must_equal 'SMS authentication has been setup.'
174
-
175
- %w'/sms-setup /sms-confirm'.each do |path|
176
- visit path
177
- page.find('#error_flash').text.must_equal 'SMS authentication has already been setup.'
178
- page.current_path.must_equal '/'
179
- end
180
-
181
- logout
182
- login
183
-
184
- visit '/sms-auth'
185
- page.current_path.must_equal '/sms-request'
186
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
187
-
188
- sms_phone = sms_message = nil
189
- page.title.must_equal 'Send SMS Code'
190
- click_button 'Send SMS Code'
191
- sms_phone.must_equal '1234567890'
192
- sms_message.must_match(/\ASMS authentication code for www\.example\.com is \d{6}\z/)
193
- sms_code = sms_message[/\d{6}\z/]
194
-
195
- fill_in 'SMS Code', :with=>"asdf"
196
- click_button 'Authenticate via SMS Code'
197
- page.html.must_include 'invalid SMS code'
198
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
199
-
200
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
201
- fill_in 'SMS Code', :with=>sms_code
202
- click_button 'Authenticate via SMS Code'
203
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
204
-
205
- click_button 'Send SMS Code'
206
- sms_code = sms_message[/\d{6}\z/]
207
- fill_in 'SMS Code', :with=>sms_code
208
- click_button 'Authenticate via SMS Code'
209
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
210
-
211
- logout
212
- login
213
-
214
- visit '/sms-request'
215
- click_button 'Send SMS Code'
216
-
217
- 5.times do
218
- click_button 'Authenticate via SMS Code'
219
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
220
- page.current_path.must_equal '/sms-auth'
221
- end
222
-
223
- click_button 'Authenticate via SMS Code'
224
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
225
- page.current_path.must_equal '/otp-auth'
226
-
227
- visit '/sms-request'
228
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
229
- page.current_path.must_equal '/otp-auth'
230
-
231
- fill_in 'Authentication Code', :with=>totp.now
232
- click_button 'Authenticate via 2nd Factor'
233
-
234
- visit '/sms-disable'
235
- page.title.must_equal 'Disable Backup SMS Authentication'
236
- fill_in 'Password', :with=>'012345678'
237
- click_button 'Disable Backup SMS Authentication'
238
- page.find('#error_flash').text.must_equal 'Error disabling SMS authentication'
239
- page.html.must_include 'invalid password'
240
-
241
- fill_in 'Password', :with=>'0123456789'
242
- click_button 'Disable Backup SMS Authentication'
243
- page.find('#notice_flash').text.must_equal 'SMS authentication has been disabled.'
244
- page.current_path.must_equal '/'
245
-
246
- visit '/sms-setup'
247
- page.title.must_equal 'Setup SMS Backup Number'
248
- fill_in 'Password', :with=>'0123456789'
249
- fill_in 'Phone Number', :with=>'(123) 456-7890'
250
- click_button 'Setup SMS Backup Number'
251
- sms_code = sms_message[/\d{12}\z/]
252
- fill_in 'SMS Code', :with=>sms_code
253
- click_button 'Confirm SMS Backup Number'
254
-
255
- visit '/recovery-codes'
256
- page.title.must_equal 'View Authentication Recovery Codes'
257
- fill_in 'Password', :with=>'012345678'
258
- click_button 'View Authentication Recovery Codes'
259
- page.find('#error_flash').text.must_equal 'Unable to view recovery codes.'
260
- page.html.must_include 'invalid password'
261
-
262
- fill_in 'Password', :with=>'0123456789'
263
- click_button 'View Authentication Recovery Codes'
264
- page.title.must_equal 'Authentication Recovery Codes'
265
- recovery_codes = find('#recovery-codes').text.split
266
- recovery_codes.length.must_equal 16
267
- recovery_code = recovery_codes.first
268
-
269
- logout
270
- login
271
-
272
- 5.times do
273
- page.title.must_equal 'Enter Authentication Code'
274
- fill_in 'Authentication Code', :with=>"asdf"
275
- click_button 'Authenticate via 2nd Factor'
276
- page.find('#error_flash').text.must_equal 'Error logging in via two factor authentication'
277
- page.html.must_include 'Invalid authentication code'
278
- end
279
-
280
- page.title.must_equal 'Enter Authentication Code'
281
- fill_in 'Authentication Code', :with=>"asdf"
282
- click_button 'Authenticate via 2nd Factor'
283
- page.find('#error_flash').text.must_equal 'Authentication code use locked out due to numerous failures. Can use recovery code to unlock. Can use SMS code to unlock.'
284
-
285
- click_button 'Send SMS Code'
286
-
287
- 5.times do
288
- click_button 'Authenticate via SMS Code'
289
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
290
- end
291
-
292
- click_button 'Authenticate via SMS Code'
293
- page.find('#error_flash').text.must_equal 'Authentication code use locked out due to numerous failures. Can use recovery code to unlock.'
294
-
295
- page.title.must_equal 'Enter Authentication Recovery Code'
296
- fill_in 'Recovery Code', :with=>"asdf"
297
- click_button 'Authenticate via Recovery Code'
298
- page.find('#error_flash').text.must_equal 'Error authenticating via recovery code.'
299
- page.html.must_include 'Invalid recovery code'
300
-
301
- fill_in 'Recovery Code', :with=>recovery_code
302
- click_button 'Authenticate via Recovery Code'
303
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
304
- page.html.must_include 'With OTP'
305
-
306
- visit '/recovery-codes'
307
- fill_in 'Password', :with=>'0123456789'
308
- click_button 'View Authentication Recovery Codes'
309
- page.title.must_equal 'Authentication Recovery Codes'
310
- page.html.wont_include(recovery_code)
311
- find('#recovery-codes').text.split.length.must_equal 15
312
-
313
- click_button 'Add Authentication Recovery Codes'
314
- page.find('#error_flash').text.must_equal 'Unable to add recovery codes.'
315
- page.html.must_include 'invalid password'
316
-
317
- fill_in 'Password', :with=>'0123456789'
318
- click_button 'View Authentication Recovery Codes'
319
- find('#recovery-codes').text.split.length.must_equal 15
320
- fill_in 'Password', :with=>'0123456789'
321
- click_button 'Add Authentication Recovery Codes'
322
- page.find('#notice_flash').text.must_equal 'Additional authentication recovery codes have been added.'
323
- find('#recovery-codes').text.split.length.must_equal 16
324
- page.html.wont_include('Add Additional Authentication Recovery Codes')
325
-
326
- visit '/otp-disable'
327
- fill_in 'Password', :with=>'012345678'
328
- click_button 'Disable Two Factor Authentication'
329
- page.find('#error_flash').text.must_equal 'Error disabling up two factor authentication'
330
- page.html.must_include 'invalid password'
331
-
332
- fill_in 'Password', :with=>'0123456789'
333
- click_button 'Disable Two Factor Authentication'
334
- page.find('#notice_flash').text.must_equal 'Two factor authentication has been disabled'
335
- page.html.must_include 'Without OTP'
336
- [:account_otp_keys, :account_recovery_codes, :account_sms_codes].each do |t|
337
- DB[t].count.must_equal 0
338
- end
339
- end
340
-
341
- it "should allow namespaced two factor authentication without password requirements" do
342
- rodauth do
343
- enable :login, :logout, :otp, :recovery_codes
344
- otp_drift 10
345
- two_factor_modifications_require_password? false
346
- otp_digits 8
347
- prefix "/auth"
348
- end
349
- roda do |r|
350
- r.on "auth" do
351
- r.rodauth
352
- end
353
-
354
- r.redirect '/auth/login' unless rodauth.logged_in?
355
-
356
- if rodauth.two_factor_authentication_setup?
357
- r.redirect '/auth/otp-auth' unless rodauth.two_factor_authenticated?
358
- view :content=>"With OTP"
359
- else
360
- view :content=>"Without OTP"
361
- end
362
- end
363
-
364
- login
365
- page.html.must_include('Without OTP')
366
-
367
- %w'/auth/otp-disable /auth/recovery-auth /auth/recovery-codes /auth/otp-auth'.each do
368
- visit '/auth/otp-disable'
369
- page.find('#error_flash').text.must_equal 'This account has not been setup for two factor authentication'
370
- page.current_path.must_equal '/auth/otp-setup'
371
- end
372
-
373
- page.title.must_equal 'Setup Two Factor Authentication'
374
- page.html.must_include '<svg'
375
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
376
- totp = ROTP::TOTP.new(secret, :digits=>8)
377
- fill_in 'Authentication Code', :with=>"asdf"
378
- click_button 'Setup Two Factor Authentication'
379
- page.find('#error_flash').text.must_equal 'Error setting up two factor authentication'
380
- page.html.must_include 'Invalid authentication code'
381
-
382
- fill_in 'Authentication Code', :with=>totp.now
383
- click_button 'Setup Two Factor Authentication'
384
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
385
- page.current_path.must_equal '/'
386
- page.html.must_include 'With OTP'
387
- reset_otp_last_use
388
-
389
- visit '/auth/logout'
390
- click_button 'Logout'
391
- login(:visit=>false)
392
-
393
- page.current_path.must_equal '/auth/otp-auth'
394
-
395
- visit '/auth/otp-disable'
396
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
397
- page.current_path.must_equal '/auth/otp-auth'
398
-
399
- visit '/auth/recovery-codes'
400
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
401
- page.current_path.must_equal '/auth/otp-auth'
402
-
403
- visit '/auth/otp-setup'
404
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
405
- page.current_path.must_equal '/auth/otp-auth'
406
-
407
- page.title.must_equal 'Enter Authentication Code'
408
- fill_in 'Authentication Code', :with=>"asdf"
409
- click_button 'Authenticate via 2nd Factor'
410
- page.find('#error_flash').text.must_equal 'Error logging in via two factor authentication'
411
- page.html.must_include 'Invalid authentication code'
412
-
413
- fill_in 'Authentication Code', :with=>totp.now
414
- click_button 'Authenticate via 2nd Factor'
415
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
416
- page.html.must_include 'With OTP'
417
- reset_otp_last_use
418
-
419
- visit '/auth/otp-auth'
420
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
421
-
422
- visit '/auth/otp-setup'
423
- page.find('#error_flash').text.must_equal 'You have already setup two factor authentication'
424
-
425
- visit '/auth/recovery-auth'
426
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
427
-
428
- visit '/auth/recovery-codes'
429
- page.title.must_equal 'View Authentication Recovery Codes'
430
- click_button 'View Authentication Recovery Codes'
431
- page.title.must_equal 'Authentication Recovery Codes'
432
- recovery_codes = find('#recovery-codes').text.split
433
- recovery_codes.length.must_equal 16
434
- recovery_code = recovery_codes.first
435
-
436
- visit '/auth/logout'
437
- click_button 'Logout'
438
- login(:visit=>false)
439
-
440
- 5.times do
441
- page.title.must_equal 'Enter Authentication Code'
442
- fill_in 'Authentication Code', :with=>"asdf"
443
- click_button 'Authenticate via 2nd Factor'
444
- page.find('#error_flash').text.must_equal 'Error logging in via two factor authentication'
445
- page.html.must_include 'Invalid authentication code'
446
- end
447
-
448
- page.title.must_equal 'Enter Authentication Code'
449
- fill_in 'Authentication Code', :with=>"asdf"
450
- click_button 'Authenticate via 2nd Factor'
451
-
452
- page.find('#error_flash').text.must_equal 'Authentication code use locked out due to numerous failures. Can use recovery code to unlock.'
453
- page.title.must_equal 'Enter Authentication Recovery Code'
454
- fill_in 'Recovery Code', :with=>"asdf"
455
- click_button 'Authenticate via Recovery Code'
456
- page.find('#error_flash').text.must_equal 'Error authenticating via recovery code.'
457
- page.html.must_include 'Invalid recovery code'
458
- fill_in 'Recovery Code', :with=>recovery_code
459
- click_button 'Authenticate via Recovery Code'
460
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
461
- page.html.must_include 'With OTP'
462
-
463
- visit '/auth/recovery-codes'
464
- click_button 'View Authentication Recovery Codes'
465
- page.title.must_equal 'Authentication Recovery Codes'
466
- page.html.wont_include(recovery_code)
467
- find('#recovery-codes').text.split.length.must_equal 15
468
- click_button 'Add Authentication Recovery Codes'
469
- page.find('#notice_flash').text.must_equal 'Additional authentication recovery codes have been added.'
470
- find('#recovery-codes').text.split.length.must_equal 16
471
- page.html.wont_include('Add Additional Authentication Recovery Codes')
472
-
473
- visit '/auth/otp-disable'
474
- click_button 'Disable Two Factor Authentication'
475
- page.find('#notice_flash').text.must_equal 'Two factor authentication has been disabled'
476
- page.html.must_include 'Without OTP'
477
- [:account_otp_keys, :account_recovery_codes].each do |t|
478
- DB[t].count.must_equal 0
479
- end
480
- end
481
-
482
- it "should require login and OTP authentication to perform certain actions if user signed up for OTP" do
483
- rodauth do
484
- enable :login, :logout, :change_password, :change_login, :close_account, :otp, :recovery_codes
485
- otp_drift 10
486
- end
487
- roda do |r|
488
- r.rodauth
489
-
490
- r.is "a" do
491
- rodauth.require_authentication
492
- view(:content=>"a")
493
- end
494
-
495
- view(:content=>"b")
496
- end
497
-
498
- %w'/change-password /change-login /close-account /a'.each do |path|
499
- visit '/change-password'
500
- page.current_path.must_equal '/login'
501
- end
502
-
503
- login
504
-
505
- %w'/change-password /change-login /close-account /a'.each do |path|
506
- visit path
507
- page.current_path.must_equal path
508
- end
509
-
510
- visit '/otp-setup'
511
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
512
- totp = ROTP::TOTP.new(secret)
513
- fill_in 'Password', :with=>'0123456789'
514
- fill_in 'Authentication Code', :with=>totp.now
515
- click_button 'Setup Two Factor Authentication'
516
- page.current_path.must_equal '/'
517
-
518
- logout
519
- login
520
-
521
- %w'/change-password /change-login /close-account /a'.each do |path|
522
- visit path
523
- page.current_path.must_equal '/otp-auth'
524
- end
525
- end
526
-
527
- it "should handle attempts to insert a duplicate recovery code" do
528
- keys = ['a', 'a', 'b']
529
- interval = 1000000
530
- rodauth do
531
- enable :login, :logout, :otp, :recovery_codes
532
- otp_interval interval
533
- recovery_codes_limit 2
534
- new_recovery_code{keys.shift}
535
- end
536
- roda do |r|
537
- r.rodauth
538
-
539
- r.redirect '/login' unless rodauth.logged_in?
540
-
541
- if rodauth.two_factor_authentication_setup?
542
- r.redirect '/otp-auth' unless rodauth.authenticated?
543
- view :content=>"With OTP"
544
- else
545
- view :content=>"Without OTP"
546
- end
547
- end
548
-
549
- login
550
- page.html.must_include('Without OTP')
551
-
552
- visit '/otp-auth'
553
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
554
- totp = ROTP::TOTP.new(secret, :interval=>interval)
555
- fill_in 'Password', :with=>'0123456789'
556
- fill_in 'Authentication Code', :with=>totp.now
557
- click_button 'Setup Two Factor Authentication'
558
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
559
- page.current_path.must_equal '/'
560
- DB[:account_recovery_codes].select_order_map(:code).must_equal ['a', 'b']
561
- end
562
-
563
- it "should handle two factor lockout when using rodauth.require_two_factor_setup and rodauth.require_authentication" do
564
- rodauth do
565
- enable :login, :logout, :otp
566
- otp_drift 10
567
- end
568
- roda do |r|
569
- r.rodauth
570
- rodauth.require_authentication
571
- rodauth.require_two_factor_setup
572
-
573
- view :content=>"Logged in"
574
- end
575
-
576
- login
577
- page.title.must_equal 'Setup Two Factor Authentication'
578
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
579
- totp = ROTP::TOTP.new(secret)
580
- fill_in 'Password', :with=>'0123456789'
581
- fill_in 'Authentication Code', :with=>totp.now
582
- click_button 'Setup Two Factor Authentication'
583
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
584
- page.current_path.must_equal '/'
585
- page.html.must_include 'Logged in'
586
- reset_otp_last_use
587
-
588
- logout
589
- login
590
-
591
- 6.times do
592
- page.title.must_equal 'Enter Authentication Code'
593
- fill_in 'Authentication Code', :with=>'foo'
594
- click_button 'Authenticate via 2nd Factor'
595
- end
596
- page.find('#error_flash').text.must_equal 'Authentication code use locked out due to numerous failures.'
597
- page.title.must_include 'Login'
598
- page.current_path.must_equal '/login'
599
- end
600
-
601
- it "should allow two factor authentication setup, login, removal without recovery" do
602
- rodauth do
603
- enable :login, :logout, :otp
604
- otp_drift 10
605
- otp_lockout_redirect '/'
606
- end
607
- roda do |r|
608
- r.rodauth
609
-
610
- r.redirect '/login' unless rodauth.logged_in?
611
-
612
- if rodauth.two_factor_authentication_setup?
613
- if rodauth.otp_locked_out?
614
- view :content=>"OTP Locked Out"
615
- else
616
- r.redirect '/otp-auth' unless rodauth.authenticated?
617
- view :content=>"With OTP"
618
- end
619
- else
620
- view :content=>"Without OTP"
621
- end
622
- end
623
-
624
- visit '/recovery-auth'
625
- page.current_path.must_equal '/login'
626
- visit '/recovery-codes'
627
- page.current_path.must_equal '/login'
628
-
629
- login
630
- page.html.must_include('Without OTP')
631
-
632
- visit '/otp-setup'
633
- page.title.must_equal 'Setup Two Factor Authentication'
634
- page.html.must_include '<svg'
635
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
636
- totp = ROTP::TOTP.new(secret)
637
- fill_in 'Password', :with=>'0123456789'
638
- fill_in 'Authentication Code', :with=>totp.now
639
- click_button 'Setup Two Factor Authentication'
640
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
641
- page.current_path.must_equal '/'
642
- page.html.must_include 'With OTP'
643
- reset_otp_last_use
644
-
645
- logout
646
- login
647
-
648
- visit '/otp-auth'
649
- 6.times do
650
- page.title.must_equal 'Enter Authentication Code'
651
- fill_in 'Authentication Code', :with=>'foo'
652
- click_button 'Authenticate via 2nd Factor'
653
- end
654
- page.find('#error_flash').text.must_equal 'Authentication code use locked out due to numerous failures.'
655
- page.body.must_include 'OTP Locked Out'
656
- page.current_path.must_equal '/'
657
- DB[:account_otp_keys].update(:num_failures=>0)
658
-
659
- visit '/otp-auth'
660
- page.title.must_equal 'Enter Authentication Code'
661
- page.html.wont_include 'Authenticate using recovery code'
662
- fill_in 'Authentication Code', :with=>totp.now
663
- click_button 'Authenticate via 2nd Factor'
664
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
665
- page.html.must_include 'With OTP'
666
-
667
- visit '/otp-disable'
668
- fill_in 'Password', :with=>'0123456789'
669
- click_button 'Disable Two Factor Authentication'
670
- page.find('#notice_flash').text.must_equal 'Two factor authentication has been disabled'
671
- page.html.must_include 'Without OTP'
672
- DB[:account_otp_keys].count.must_equal 0
673
- end
674
-
675
- it "should remove otp data when closing accounts" do
676
- rodauth do
677
- enable :login, :logout, :otp, :recovery_codes, :sms_codes, :close_account
678
- otp_drift 10
679
- two_factor_modifications_require_password? false
680
- close_account_requires_password? false
681
- sms_send{|*|}
682
- end
683
- roda do |r|
684
- r.rodauth
685
- r.root{view :content=>"With OTP"}
686
- end
687
-
688
- login
689
-
690
- visit '/otp-setup'
691
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
692
- totp = ROTP::TOTP.new(secret)
693
- fill_in 'Authentication Code', :with=>totp.now
694
- click_button 'Setup Two Factor Authentication'
695
-
696
- visit '/sms-setup'
697
- fill_in 'Phone Number', :with=>'(123) 456-7890'
698
- click_button 'Setup SMS Backup Number'
699
-
700
- DB[:account_otp_keys].count.must_equal 1
701
- DB[:account_recovery_codes].count.must_equal 16
702
- DB[:account_sms_codes].count.must_equal 1
703
- visit '/close-account'
704
- click_button 'Close Account'
705
- [:account_otp_keys, :account_recovery_codes, :account_sms_codes].each do |t|
706
- DB[t].count.must_equal 0
707
- end
708
- end
709
-
710
- it "should have recovery_codes and sms_codes work when used without otp" do
711
- sms_code, sms_phone, sms_message = nil
712
- rodauth do
713
- enable :login, :logout, :recovery_codes, :sms_codes
714
- sms_send do |phone, msg|
715
- sms_phone = phone
716
- sms_message = msg
717
- sms_code = msg[/\d+\z/]
718
- end
719
- end
720
- roda do |r|
721
- r.rodauth
722
-
723
- r.redirect '/login' unless rodauth.logged_in?
724
-
725
- if rodauth.two_factor_authentication_setup?
726
- r.redirect '/sms-request' unless rodauth.authenticated?
727
- view :content=>"With OTP"
728
- else
729
- view :content=>"Without OTP"
730
- end
731
- end
732
-
733
- login
734
- page.html.must_include('Without OTP')
735
-
736
- %w'/recovery-auth /recovery-codes'.each do |path|
737
- visit path
738
- page.find('#error_flash').text.must_equal 'This account has not been setup for two factor authentication'
739
- page.current_path.must_equal '/sms-setup'
740
- end
741
-
742
- %w'/sms-disable /sms-request /sms-auth'.each do |path|
743
- visit path
744
- page.find('#error_flash').text.must_equal 'SMS authentication has not been setup yet.'
745
- page.current_path.must_equal '/sms-setup'
746
- end
747
-
748
- visit '/sms-setup'
749
- page.title.must_equal 'Setup SMS Backup Number'
750
- fill_in 'Password', :with=>'012345678'
751
- fill_in 'Phone Number', :with=>'(123) 456'
752
- click_button 'Setup SMS Backup Number'
753
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
754
- page.html.must_include 'invalid password'
755
-
756
- fill_in 'Password', :with=>'0123456789'
757
- click_button 'Setup SMS Backup Number'
758
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
759
- page.html.must_include 'invalid SMS phone number'
760
-
761
- fill_in 'Password', :with=>'0123456789'
762
- fill_in 'Phone Number', :with=>'(123) 456-7890'
763
- click_button 'Setup SMS Backup Number'
764
- page.find('#notice_flash').text.must_equal 'SMS authentication needs confirmation.'
765
- sms_phone.must_equal '1234567890'
766
- sms_message.must_match(/\ASMS confirmation code for www\.example\.com is \d{12}\z/)
767
-
768
- page.title.must_equal 'Confirm SMS Backup Number'
769
- fill_in 'SMS Code', :with=>"asdf"
770
- click_button 'Confirm SMS Backup Number'
771
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
772
-
773
- fill_in 'Password', :with=>'0123456789'
774
- fill_in 'Phone Number', :with=>'(123) 456-7890'
775
- click_button 'Setup SMS Backup Number'
776
-
777
- visit '/sms-setup'
778
- page.find('#error_flash').text.must_equal 'SMS authentication needs confirmation.'
779
- page.title.must_equal 'Confirm SMS Backup Number'
780
-
781
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
782
- fill_in 'SMS Code', :with=>sms_code
783
- click_button 'Confirm SMS Backup Number'
784
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
785
-
786
- fill_in 'Password', :with=>'0123456789'
787
- fill_in 'Phone Number', :with=>'(123) 456-7890'
788
- click_button 'Setup SMS Backup Number'
789
- fill_in 'SMS Code', :with=>sms_code
790
- click_button 'Confirm SMS Backup Number'
791
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
792
-
793
- visit '/recovery-codes'
794
- page.title.must_equal 'View Authentication Recovery Codes'
795
- fill_in 'Password', :with=>'012345678'
796
- click_button 'View Authentication Recovery Codes'
797
- page.find('#error_flash').text.must_equal 'Unable to view recovery codes.'
798
- page.html.must_include 'invalid password'
799
-
800
- fill_in 'Password', :with=>'0123456789'
801
- click_button 'View Authentication Recovery Codes'
802
- page.title.must_equal 'Authentication Recovery Codes'
803
- recovery_codes = find('#recovery-codes').text.split
804
- recovery_codes.length.must_equal 16
805
- recovery_code = recovery_codes.first
806
-
807
- logout
808
- login
809
- page.current_path.must_equal '/sms-request'
810
-
811
- %w'/recovery-codes /sms-setup /sms-disable /sms-confirm'.each do |path|
812
- visit path
813
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
814
- page.current_path.must_equal '/sms-request'
815
- end
816
-
817
- visit '/sms-auth'
818
- page.current_path.must_equal '/sms-request'
819
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
820
-
821
- sms_phone = sms_message = nil
822
- page.title.must_equal 'Send SMS Code'
823
- click_button 'Send SMS Code'
824
- sms_phone.must_equal '1234567890'
825
- sms_message.must_match(/\ASMS authentication code for www\.example\.com is \d{6}\z/)
826
-
827
- fill_in 'SMS Code', :with=>"asdf"
828
- click_button 'Authenticate via SMS Code'
829
- page.html.must_include 'invalid SMS code'
830
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
831
-
832
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
833
- fill_in 'SMS Code', :with=>sms_code
834
- click_button 'Authenticate via SMS Code'
835
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
836
-
837
- click_button 'Send SMS Code'
838
- fill_in 'SMS Code', :with=>sms_code
839
- click_button 'Authenticate via SMS Code'
840
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
841
-
842
- %w'/recovery-auth /sms-request /sms-auth'.each do |path|
843
- visit path
844
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
845
- end
846
-
847
- %w'/sms-setup /sms-confirm'.each do |path|
848
- visit path
849
- page.find('#error_flash').text.must_equal 'SMS authentication has already been setup.'
850
- page.current_path.must_equal '/'
851
- end
852
-
853
- logout
854
- login
855
-
856
- click_button 'Send SMS Code'
857
-
858
- 5.times do
859
- click_button 'Authenticate via SMS Code'
860
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
861
- page.current_path.must_equal '/sms-auth'
862
- end
863
-
864
- click_button 'Authenticate via SMS Code'
865
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
866
- page.current_path.must_equal '/recovery-auth'
867
-
868
- visit '/sms-request'
869
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
870
- page.current_path.must_equal '/recovery-auth'
871
-
872
- page.title.must_equal 'Enter Authentication Recovery Code'
873
- fill_in 'Recovery Code', :with=>"asdf"
874
- click_button 'Authenticate via Recovery Code'
875
- page.find('#error_flash').text.must_equal 'Error authenticating via recovery code.'
876
- page.html.must_include 'Invalid recovery code'
877
-
878
- fill_in 'Recovery Code', :with=>recovery_code
879
- click_button 'Authenticate via Recovery Code'
880
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
881
- page.html.must_include 'With OTP'
882
-
883
- visit '/recovery-codes'
884
- fill_in 'Password', :with=>'0123456789'
885
- click_button 'View Authentication Recovery Codes'
886
- page.title.must_equal 'Authentication Recovery Codes'
887
- page.html.wont_include(recovery_code)
888
- find('#recovery-codes').text.split.length.must_equal 15
889
-
890
- click_button 'Add Authentication Recovery Codes'
891
- page.find('#error_flash').text.must_equal 'Unable to add recovery codes.'
892
- page.html.must_include 'invalid password'
893
-
894
- visit '/sms-disable'
895
- page.title.must_equal 'Disable Backup SMS Authentication'
896
- fill_in 'Password', :with=>'012345678'
897
- click_button 'Disable Backup SMS Authentication'
898
- page.find('#error_flash').text.must_equal 'Error disabling SMS authentication'
899
- page.html.must_include 'invalid password'
900
-
901
- fill_in 'Password', :with=>'0123456789'
902
- click_button 'Disable Backup SMS Authentication'
903
- page.find('#notice_flash').text.must_equal 'SMS authentication has been disabled.'
904
- page.current_path.must_equal '/'
905
-
906
- [:account_recovery_codes, :account_sms_codes].each do |t|
907
- DB[t].count.must_equal 0
908
- end
909
- end
910
-
911
- it "should have recovery_codes work when used by itself" do
912
- rodauth do
913
- enable :login, :logout, :recovery_codes
914
- end
915
- roda do |r|
916
- r.rodauth
917
-
918
- r.redirect '/login' unless rodauth.logged_in?
919
-
920
- if rodauth.two_factor_authentication_setup?
921
- r.redirect '/recovery-auth' unless rodauth.authenticated?
922
- view :content=>"With OTP"
923
- else
924
- view :content=>"Without OTP"
925
- end
926
- end
927
-
928
- login
929
- page.html.must_include('Without OTP')
930
-
931
- visit '/recovery-auth'
932
- page.find('#error_flash').text.must_equal 'This account has not been setup for two factor authentication'
933
- page.current_path.must_equal '/recovery-codes'
934
-
935
- page.title.must_equal 'View Authentication Recovery Codes'
936
- fill_in 'Password', :with=>'012345678'
937
- click_button 'View Authentication Recovery Codes'
938
- page.find('#error_flash').text.must_equal 'Unable to view recovery codes.'
939
- page.html.must_include 'invalid password'
940
-
941
- fill_in 'Password', :with=>'0123456789'
942
- click_button 'View Authentication Recovery Codes'
943
- page.title.must_equal 'Authentication Recovery Codes'
944
- recovery_codes = find('#recovery-codes').text.split
945
- recovery_codes.length.must_equal 0
946
- fill_in 'Password', :with=>'0123456789'
947
- click_button 'Add Authentication Recovery Codes'
948
- recovery_codes = find('#recovery-codes').text.split
949
- recovery_codes.length.must_equal 16
950
- recovery_code = recovery_codes.first
951
-
952
- logout
953
- login
954
- page.current_path.must_equal '/recovery-auth'
955
-
956
- visit '/recovery-codes'
957
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
958
- page.current_path.must_equal '/recovery-auth'
959
-
960
- page.title.must_equal 'Enter Authentication Recovery Code'
961
- fill_in 'Recovery Code', :with=>"asdf"
962
- click_button 'Authenticate via Recovery Code'
963
- page.find('#error_flash').text.must_equal 'Error authenticating via recovery code.'
964
- page.html.must_include 'Invalid recovery code'
965
-
966
- fill_in 'Recovery Code', :with=>recovery_code
967
- click_button 'Authenticate via Recovery Code'
968
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
969
- page.html.must_include 'With OTP'
970
-
971
- visit '/recovery-codes'
972
- fill_in 'Password', :with=>'0123456789'
973
- click_button 'View Authentication Recovery Codes'
974
- page.title.must_equal 'Authentication Recovery Codes'
975
- page.html.wont_include(recovery_code)
976
- page.html.wont_include('Add Authentication Recovery Codes')
977
- find('#recovery-codes').text.split.length.must_equal 16
978
- end
979
-
980
- it "should have sms_codes work when used by itself" do
981
- sms_code, sms_phone, sms_message = nil
982
- rodauth do
983
- enable :login, :logout, :sms_codes
984
- sms_send do |phone, msg|
985
- sms_phone = phone
986
- sms_message = msg
987
- sms_code = msg[/\d+\z/]
988
- end
989
- end
990
- roda do |r|
991
- r.rodauth
992
-
993
- r.redirect '/login' unless rodauth.logged_in?
994
-
995
- if rodauth.two_factor_authentication_setup?
996
- if rodauth.sms_locked_out?
997
- view :content=>"With SMS Locked Out"
998
- else
999
- rodauth.require_two_factor_authenticated
1000
- view :content=>"With OTP"
1001
- end
1002
- else
1003
- view :content=>"Without OTP"
1004
- end
1005
- end
1006
-
1007
- login
1008
- page.html.must_include('Without OTP')
1009
-
1010
- %w'/sms-disable /sms-request /sms-auth'.each do |path|
1011
- visit path
1012
- page.find('#error_flash').text.must_equal 'SMS authentication has not been setup yet.'
1013
- page.current_path.must_equal '/sms-setup'
1014
- end
1015
-
1016
- visit '/sms-setup'
1017
- page.title.must_equal 'Setup SMS Backup Number'
1018
- fill_in 'Password', :with=>'012345678'
1019
- fill_in 'Phone Number', :with=>'(123) 456'
1020
- click_button 'Setup SMS Backup Number'
1021
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
1022
- page.html.must_include 'invalid password'
1023
-
1024
- fill_in 'Password', :with=>'0123456789'
1025
- click_button 'Setup SMS Backup Number'
1026
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
1027
- page.html.must_include 'invalid SMS phone number'
1028
-
1029
- fill_in 'Password', :with=>'0123456789'
1030
- fill_in 'Phone Number', :with=>'(123) 456-7890'
1031
- click_button 'Setup SMS Backup Number'
1032
- page.find('#notice_flash').text.must_equal 'SMS authentication needs confirmation.'
1033
- sms_phone.must_equal '1234567890'
1034
- sms_message.must_match(/\ASMS confirmation code for www\.example\.com is \d{12}\z/)
1035
-
1036
- page.title.must_equal 'Confirm SMS Backup Number'
1037
- fill_in 'SMS Code', :with=>"asdf"
1038
- click_button 'Confirm SMS Backup Number'
1039
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
1040
-
1041
- fill_in 'Password', :with=>'0123456789'
1042
- fill_in 'Phone Number', :with=>'(123) 456-7890'
1043
- click_button 'Setup SMS Backup Number'
1044
-
1045
- visit '/sms-setup'
1046
- page.find('#error_flash').text.must_equal 'SMS authentication needs confirmation.'
1047
- page.title.must_equal 'Confirm SMS Backup Number'
1048
-
1049
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1050
- fill_in 'SMS Code', :with=>sms_code
1051
- click_button 'Confirm SMS Backup Number'
1052
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
1053
-
1054
- fill_in 'Password', :with=>'0123456789'
1055
- fill_in 'Phone Number', :with=>'(123) 456-7890'
1056
- click_button 'Setup SMS Backup Number'
1057
- fill_in 'SMS Code', :with=>sms_code
1058
- click_button 'Confirm SMS Backup Number'
1059
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
1060
-
1061
- logout
1062
- login
1063
- page.current_path.must_equal '/sms-request'
1064
-
1065
- %w'/sms-setup /sms-disable /sms-confirm'.each do |path|
1066
- visit path
1067
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
1068
- page.current_path.must_equal '/sms-request'
1069
- end
1070
-
1071
- visit '/sms-auth'
1072
- page.current_path.must_equal '/sms-request'
1073
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
1074
-
1075
- sms_phone = sms_message = nil
1076
- page.title.must_equal 'Send SMS Code'
1077
- click_button 'Send SMS Code'
1078
- sms_phone.must_equal '1234567890'
1079
- sms_message.must_match(/\ASMS authentication code for www\.example\.com is \d{6}\z/)
1080
-
1081
- fill_in 'SMS Code', :with=>"asdf"
1082
- click_button 'Authenticate via SMS Code'
1083
- page.html.must_include 'invalid SMS code'
1084
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
1085
-
1086
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1087
- fill_in 'SMS Code', :with=>sms_code
1088
- click_button 'Authenticate via SMS Code'
1089
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
1090
-
1091
- click_button 'Send SMS Code'
1092
- fill_in 'SMS Code', :with=>sms_code
1093
- click_button 'Authenticate via SMS Code'
1094
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
1095
-
1096
- %w'/sms-request /sms-auth'.each do |path|
1097
- visit path
1098
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
1099
- end
1100
-
1101
- %w'/sms-setup /sms-confirm'.each do |path|
1102
- visit path
1103
- page.find('#error_flash').text.must_equal 'SMS authentication has already been setup.'
1104
- page.current_path.must_equal '/'
1105
- end
1106
-
1107
- logout
1108
- login
1109
-
1110
- click_button 'Send SMS Code'
1111
-
1112
- 5.times do
1113
- click_button 'Authenticate via SMS Code'
1114
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
1115
- page.current_path.must_equal '/sms-auth'
1116
- end
1117
-
1118
- click_button 'Authenticate via SMS Code'
1119
- page.body.must_include "With SMS Locked Out"
1120
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
1121
- page.current_path.must_equal '/'
1122
-
1123
- visit '/sms-request'
1124
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
1125
- page.current_path.must_equal '/'
1126
-
1127
- DB[:account_sms_codes].update(:num_failures=>0)
1128
- visit '/sms-request'
1129
- click_button 'Send SMS Code'
1130
- fill_in 'SMS Code', :with=>sms_code
1131
- click_button 'Authenticate via SMS Code'
1132
-
1133
- visit '/sms-disable'
1134
- page.title.must_equal 'Disable Backup SMS Authentication'
1135
- fill_in 'Password', :with=>'012345678'
1136
- click_button 'Disable Backup SMS Authentication'
1137
- page.find('#error_flash').text.must_equal 'Error disabling SMS authentication'
1138
- page.html.must_include 'invalid password'
1139
-
1140
- fill_in 'Password', :with=>'0123456789'
1141
- click_button 'Disable Backup SMS Authentication'
1142
- page.find('#notice_flash').text.must_equal 'SMS authentication has been disabled.'
1143
- page.current_path.must_equal '/'
1144
-
1145
- DB[:account_sms_codes].count.must_equal 0
1146
- end
1147
-
1148
- it "should allow two factor authentication via jwt" do
1149
- hmac_secret = sms_phone = sms_message = sms_code = nil
1150
- rodauth do
1151
- enable :login, :logout, :otp, :recovery_codes, :sms_codes
1152
- otp_drift 10
1153
- hmac_secret do
1154
- hmac_secret
1155
- end
1156
- sms_send do |phone, msg|
1157
- sms_phone = phone
1158
- sms_message = msg
1159
- sms_code = msg[/\d+\z/]
1160
- end
1161
- end
1162
- roda(:jwt) do |r|
1163
- r.rodauth
1164
-
1165
- if rodauth.logged_in?
1166
- if rodauth.two_factor_authentication_setup?
1167
- if rodauth.authenticated?
1168
- [1]
1169
- else
1170
- [2]
1171
- end
1172
- else
1173
- [3]
1174
- end
1175
- else
1176
- [4]
1177
- end
1178
- end
1179
-
1180
- json_request.must_equal [200, [4]]
1181
- json_login
1182
- json_request.must_equal [200, [3]]
1183
-
1184
- %w'/otp-disable /recovery-auth /recovery-codes /sms-setup /sms-confirm /otp-auth'.each do |path|
1185
- json_request(path).must_equal [403, {'error'=>'This account has not been setup for two factor authentication'}]
1186
- end
1187
- %w'/sms-disable /sms-request /sms-auth'.each do |path|
1188
- json_request(path).must_equal [403, {'error'=>'SMS authentication has not been setup yet.'}]
1189
- end
1190
-
1191
- secret = (ROTP::Base32.respond_to?(:random_base32) ? ROTP::Base32.random_base32 : ROTP::Base32.random).downcase
1192
- totp = ROTP::TOTP.new(secret)
1193
-
1194
- res = json_request('/otp-setup', :password=>'123456', :otp_secret=>secret)
1195
- res.must_equal [401, {'error'=>'Error setting up two factor authentication', "field-error"=>["password", 'invalid password']}]
1196
-
1197
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>'adsf', :otp_secret=>secret)
1198
- res.must_equal [401, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1199
-
1200
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>'adsf', :otp_secret=>'asdf')
1201
- res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1202
-
1203
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret)
1204
- res.must_equal [200, {'success'=>'Two factor authentication is now setup'}]
1205
- reset_otp_last_use
1206
-
1207
- json_logout
1208
- json_login
1209
- json_request.must_equal [200, [2]]
1210
-
1211
- %w'/otp-disable /recovery-codes /otp-setup /sms-setup /sms-disable /sms-confirm'.each do |path|
1212
- json_request(path).must_equal [401, {'error'=>'You need to authenticate via 2nd factor before continuing.'}]
1213
- end
1214
-
1215
- res = json_request('/otp-auth', :otp=>'adsf')
1216
- res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1217
-
1218
- res = json_request('/otp-auth', :otp=>totp.now)
1219
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1220
- json_request.must_equal [200, [1]]
1221
- reset_otp_last_use
1222
-
1223
- res = json_request('/otp-setup')
1224
- res.must_equal [400, {'error'=>'You have already setup two factor authentication'}]
1225
-
1226
- %w'/otp-auth /recovery-auth /sms-request /sms-auth'.each do |path|
1227
- res = json_request(path)
1228
- res.must_equal [403, {'error'=>'Already authenticated via 2nd factor'}]
1229
- end
1230
-
1231
- res = json_request('/sms-disable')
1232
- res.must_equal [403, {'error'=>'SMS authentication has not been setup yet.'}]
1233
-
1234
- res = json_request('/sms-setup', :password=>'012345678', "sms-phone"=>'(123) 456')
1235
- res.must_equal [401, {'error'=>'Error setting up SMS authentication', "field-error"=>["password", 'invalid password']}]
1236
-
1237
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 456')
1238
- res.must_equal [422, {'error'=>'Error setting up SMS authentication', "field-error"=>["sms-phone", 'invalid SMS phone number']}]
1239
-
1240
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1241
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1242
-
1243
- sms_phone.must_equal '1234567890'
1244
- sms_message.must_match(/\ASMS confirmation code for example\.com: is \d{12}\z/)
1245
-
1246
- res = json_request('/sms-confirm', :sms_code=>'asdf')
1247
- res.must_equal [401, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1248
-
1249
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1250
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1251
-
1252
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1253
- res = json_request('/sms-confirm', :sms_code=>sms_code)
1254
- res.must_equal [401, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1255
-
1256
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1257
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1258
-
1259
- res = json_request('/sms-confirm', "sms-code"=>sms_code)
1260
- res.must_equal [200, {'success'=>'SMS authentication has been setup.'}]
1261
-
1262
- %w'/sms-setup /sms-confirm'.each do |path|
1263
- res = json_request(path)
1264
- res.must_equal [403, {'error'=>'SMS authentication has already been setup.'}]
1265
- end
1266
-
1267
- json_logout
1268
- json_login
1269
-
1270
- res = json_request('/sms-auth')
1271
- res.must_equal [401, {'error'=>'No current SMS code for this account'}]
1272
-
1273
- sms_phone = sms_message = nil
1274
- res = json_request('/sms-request')
1275
- res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
1276
- sms_phone.must_equal '1234567890'
1277
- sms_message.must_match(/\ASMS authentication code for example\.com: is \d{6}\z/)
1278
-
1279
- res = json_request('/sms-auth')
1280
- res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1281
-
1282
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1283
- res = json_request('/sms-auth')
1284
- res.must_equal [401, {'error'=>'No current SMS code for this account'}]
1285
-
1286
- res = json_request('/sms-request')
1287
- res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
1288
-
1289
- res = json_request('/sms-auth', 'sms-code'=>sms_code)
1290
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1291
- json_request.must_equal [200, [1]]
1292
-
1293
- json_logout
1294
- json_login
1295
-
1296
- res = json_request('/sms-request')
1297
- res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
1298
-
1299
- 5.times do
1300
- res = json_request('/sms-auth')
1301
- res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1302
- end
1303
-
1304
- res = json_request('/sms-auth')
1305
- res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1306
-
1307
- res = json_request('/sms-request')
1308
- res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1309
-
1310
- res = json_request('/otp-auth', :otp=>totp.now)
1311
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1312
- json_request.must_equal [200, [1]]
1313
-
1314
- res = json_request('/sms-disable', :password=>'012345678')
1315
- res.must_equal [401, {'error'=>'Error disabling SMS authentication', "field-error"=>["password", 'invalid password']}]
1316
-
1317
- res = json_request('/sms-disable', :password=>'0123456789')
1318
- res.must_equal [200, {'success'=>'SMS authentication has been disabled.'}]
1319
-
1320
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1321
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1322
-
1323
- res = json_request('/sms-confirm', "sms-code"=>sms_code)
1324
- res.must_equal [200, {'success'=>'SMS authentication has been setup.'}]
1325
-
1326
- res = json_request('/recovery-codes', :password=>'asdf')
1327
- res.must_equal [401, {'error'=>'Unable to view recovery codes.', "field-error"=>["password", 'invalid password']}]
1328
-
1329
- res = json_request('/recovery-codes', :password=>'0123456789')
1330
- codes = res[1].delete('codes')
1331
- codes.sort.must_equal DB[:account_recovery_codes].select_map(:code).sort
1332
- res.must_equal [200, {'success'=>''}]
1333
-
1334
- json_logout
1335
- json_login
1336
-
1337
- 5.times do
1338
- res = json_request('/otp-auth', :otp=>'asdf')
1339
- res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1340
- end
1341
-
1342
- res = json_request('/otp-auth', :otp=>'asdf')
1343
- res.must_equal [403, {'error'=>'Authentication code use locked out due to numerous failures. Can use recovery code to unlock. Can use SMS code to unlock.'}]
1344
-
1345
- res = json_request('/sms-request')
1346
- 5.times do
1347
- res = json_request('/sms-auth')
1348
- res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1349
- end
1350
-
1351
- res = json_request('/otp-auth', :otp=>'asdf')
1352
- res.must_equal [403, {'error'=>'Authentication code use locked out due to numerous failures. Can use recovery code to unlock.'}]
1353
-
1354
- res = json_request('/sms-auth')
1355
- res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1356
-
1357
- res = json_request('/recovery-auth', 'recovery-code'=>'adsf')
1358
- res.must_equal [401, {'error'=>'Error authenticating via recovery code.', "field-error"=>["recovery-code", "Invalid recovery code"]}]
1359
-
1360
- res = json_request('/recovery-auth', 'recovery-code'=>codes.first)
1361
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1362
- json_request.must_equal [200, [1]]
1363
-
1364
- res = json_request('/recovery-codes', :password=>'0123456789')
1365
- codes2 = res[1].delete('codes')
1366
- codes2.sort.must_equal codes[1..-1].sort
1367
- res.must_equal [200, {'success'=>''}]
1368
-
1369
- res = json_request('/recovery-codes', :password=>'012345678', :add=>'1')
1370
- res.must_equal [401, {'error'=>'Unable to add recovery codes.', "field-error"=>["password", 'invalid password']}]
1371
-
1372
- res = json_request('/recovery-codes', :password=>'0123456789', :add=>'1')
1373
- codes3 = res[1].delete('codes')
1374
- (codes3 - codes2).length.must_equal 1
1375
- res.must_equal [200, {'success'=>'Additional authentication recovery codes have been added.'}]
1376
-
1377
- res = json_request('/otp-disable', :password=>'012345678')
1378
- res.must_equal [401, {'error'=>'Error disabling up two factor authentication', "field-error"=>["password", 'invalid password']}]
1379
-
1380
- res = json_request('/otp-disable', :password=>'0123456789')
1381
- res.must_equal [200, {'success'=>'Two factor authentication has been disabled'}]
1382
-
1383
- [:account_otp_keys, :account_recovery_codes, :account_sms_codes].each do |t|
1384
- DB[t].count.must_equal 0
1385
- end
1386
-
1387
- hmac_secret = "123"
1388
- res = json_request('/otp-setup')
1389
- secret = res[1].delete("otp_secret")
1390
- raw_secret = res[1].delete("otp_raw_secret")
1391
- res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1392
-
1393
- totp = ROTP::TOTP.new(secret)
1394
- hmac_secret = "321"
1395
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret, :otp_raw_secret=>raw_secret)
1396
- res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1397
-
1398
- reset_otp_last_use
1399
- hmac_secret = "123"
1400
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret, :otp_raw_secret=>raw_secret)
1401
- res.must_equal [200, {'success'=>'Two factor authentication is now setup'}]
1402
- reset_otp_last_use
1403
-
1404
- json_logout
1405
- json_login
1406
-
1407
- hmac_secret = "321"
1408
- res = json_request('/otp-auth', :otp=>totp.now)
1409
- res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1410
-
1411
- hmac_secret = "123"
1412
- res = json_request('/otp-auth', :otp=>totp.now)
1413
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1414
- json_request.must_equal [200, [1]]
1415
- end
1416
-
1417
- it "should allow two factor authentication setup, login, recovery, removal" do
1418
- warning = nil
1419
- before_called = false
1420
- rodauth do
1421
- enable :login, :otp, :logout
1422
- (class << self; self end).send(:define_method, :warn){|w| warning = w}
1423
- before_otp_authentication_route{before_called = true}
1424
- warning.must_equal "before_otp_authentication_route is deprecated, switch to before_otp_auth_route"
1425
- otp_drift 10
1426
- end
1427
- roda do |r|
1428
- r.rodauth
1429
-
1430
- r.redirect '/login' unless rodauth.logged_in?
1431
-
1432
- if rodauth.two_factor_authentication_setup?
1433
- r.redirect '/otp-auth' unless rodauth.authenticated?
1434
- view :content=>"With OTP"
1435
- else
1436
- view :content=>"Without OTP"
1437
- end
1438
- end
1439
-
1440
- login
1441
- page.html.must_include('Without OTP')
1442
-
1443
- visit '/otp-auth'
1444
- before_called.must_equal false
1445
- page.current_path.must_equal '/otp-setup'
1446
-
1447
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
1448
- totp = ROTP::TOTP.new(secret)
1449
- fill_in 'Password', :with=>'0123456789'
1450
- fill_in 'Authentication Code', :with=>totp.now
1451
- click_button 'Setup Two Factor Authentication'
1452
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
1453
- page.current_path.must_equal '/'
1454
- page.html.must_include 'With OTP'
1455
-
1456
- logout
1457
- before_called.must_equal false
1458
- login
1459
- page.current_path.must_equal '/otp-auth'
1460
- before_called.must_equal true
1461
- end
1462
- end