rodauth 1.20.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +170 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +211 -79
  5. data/doc/account_expiration.rdoc +12 -26
  6. data/doc/active_sessions.rdoc +49 -0
  7. data/doc/audit_logging.rdoc +44 -0
  8. data/doc/base.rdoc +75 -128
  9. data/doc/change_login.rdoc +7 -14
  10. data/doc/change_password.rdoc +9 -13
  11. data/doc/change_password_notify.rdoc +2 -2
  12. data/doc/close_account.rdoc +9 -16
  13. data/doc/confirm_password.rdoc +12 -5
  14. data/doc/create_account.rdoc +11 -22
  15. data/doc/disallow_password_reuse.rdoc +6 -13
  16. data/doc/email_auth.rdoc +15 -14
  17. data/doc/email_base.rdoc +6 -15
  18. data/doc/http_basic_auth.rdoc +10 -1
  19. data/doc/internals.rdoc +1 -1
  20. data/doc/jwt.rdoc +22 -22
  21. data/doc/jwt_cors.rdoc +22 -0
  22. data/doc/jwt_refresh.rdoc +12 -8
  23. data/doc/lockout.rdoc +17 -15
  24. data/doc/login.rdoc +10 -2
  25. data/doc/login_password_requirements_base.rdoc +15 -37
  26. data/doc/logout.rdoc +2 -2
  27. data/doc/otp.rdoc +24 -19
  28. data/doc/password_complexity.rdoc +10 -26
  29. data/doc/password_expiration.rdoc +11 -25
  30. data/doc/password_grace_period.rdoc +16 -2
  31. data/doc/recovery_codes.rdoc +18 -12
  32. data/doc/release_notes/1.21.0.txt +12 -0
  33. data/doc/release_notes/1.22.0.txt +11 -0
  34. data/doc/release_notes/1.23.0.txt +32 -0
  35. data/doc/release_notes/2.0.0.txt +361 -0
  36. data/doc/release_notes/2.1.0.txt +31 -0
  37. data/doc/remember.rdoc +40 -64
  38. data/doc/reset_password.rdoc +12 -9
  39. data/doc/session_expiration.rdoc +1 -0
  40. data/doc/single_session.rdoc +16 -25
  41. data/doc/sms_codes.rdoc +24 -14
  42. data/doc/two_factor_base.rdoc +60 -22
  43. data/doc/verify_account.rdoc +14 -12
  44. data/doc/verify_account_grace_period.rdoc +6 -2
  45. data/doc/verify_login_change.rdoc +9 -8
  46. data/doc/webauthn.rdoc +115 -0
  47. data/doc/webauthn_login.rdoc +15 -0
  48. data/doc/webauthn_verify_account.rdoc +9 -0
  49. data/javascript/webauthn_auth.js +45 -0
  50. data/javascript/webauthn_setup.js +35 -0
  51. data/lib/roda/plugins/rodauth.rb +1 -1
  52. data/lib/rodauth.rb +32 -24
  53. data/lib/rodauth/features/account_expiration.rb +5 -5
  54. data/lib/rodauth/features/active_sessions.rb +160 -0
  55. data/lib/rodauth/features/audit_logging.rb +96 -0
  56. data/lib/rodauth/features/base.rb +144 -43
  57. data/lib/rodauth/features/change_password_notify.rb +2 -2
  58. data/lib/rodauth/features/confirm_password.rb +40 -2
  59. data/lib/rodauth/features/create_account.rb +8 -13
  60. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  61. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  62. data/lib/rodauth/features/email_auth.rb +30 -29
  63. data/lib/rodauth/features/email_base.rb +9 -4
  64. data/lib/rodauth/features/http_basic_auth.rb +55 -35
  65. data/lib/rodauth/features/jwt.rb +58 -10
  66. data/lib/rodauth/features/jwt_cors.rb +53 -0
  67. data/lib/rodauth/features/jwt_refresh.rb +3 -3
  68. data/lib/rodauth/features/lockout.rb +12 -14
  69. data/lib/rodauth/features/login.rb +54 -10
  70. data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
  71. data/lib/rodauth/features/otp.rb +72 -74
  72. data/lib/rodauth/features/password_complexity.rb +4 -11
  73. data/lib/rodauth/features/password_expiration.rb +2 -2
  74. data/lib/rodauth/features/password_grace_period.rb +17 -10
  75. data/lib/rodauth/features/recovery_codes.rb +49 -53
  76. data/lib/rodauth/features/remember.rb +11 -27
  77. data/lib/rodauth/features/reset_password.rb +26 -26
  78. data/lib/rodauth/features/session_expiration.rb +6 -4
  79. data/lib/rodauth/features/single_session.rb +7 -5
  80. data/lib/rodauth/features/sms_codes.rb +62 -71
  81. data/lib/rodauth/features/two_factor_base.rb +132 -28
  82. data/lib/rodauth/features/verify_account.rb +25 -21
  83. data/lib/rodauth/features/verify_account_grace_period.rb +20 -9
  84. data/lib/rodauth/features/verify_login_change.rb +12 -11
  85. data/lib/rodauth/features/webauthn.rb +507 -0
  86. data/lib/rodauth/features/webauthn_login.rb +70 -0
  87. data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
  88. data/lib/rodauth/version.rb +2 -2
  89. data/templates/button.str +1 -3
  90. data/templates/change-login.str +1 -2
  91. data/templates/change-password.str +3 -5
  92. data/templates/close-account.str +2 -2
  93. data/templates/confirm-password.str +1 -1
  94. data/templates/create-account.str +1 -1
  95. data/templates/email-auth-email.str +1 -1
  96. data/templates/email-auth-request-form.str +2 -3
  97. data/templates/email-auth.str +1 -1
  98. data/templates/global-logout-field.str +6 -0
  99. data/templates/login-confirm-field.str +2 -4
  100. data/templates/login-display.str +3 -2
  101. data/templates/login-field.str +2 -4
  102. data/templates/login-form-footer.str +6 -0
  103. data/templates/login-form.str +7 -0
  104. data/templates/login.str +1 -9
  105. data/templates/logout.str +1 -1
  106. data/templates/multi-phase-login.str +3 -0
  107. data/templates/otp-auth-code-field.str +5 -3
  108. data/templates/otp-auth.str +1 -1
  109. data/templates/otp-disable.str +1 -1
  110. data/templates/otp-setup.str +3 -3
  111. data/templates/password-confirm-field.str +2 -4
  112. data/templates/password-field.str +2 -4
  113. data/templates/recovery-auth.str +3 -6
  114. data/templates/recovery-codes.str +1 -1
  115. data/templates/remember.str +15 -20
  116. data/templates/reset-password-email.str +1 -1
  117. data/templates/reset-password-request.str +3 -3
  118. data/templates/reset-password.str +1 -2
  119. data/templates/sms-auth.str +1 -1
  120. data/templates/sms-code-field.str +5 -3
  121. data/templates/sms-confirm.str +1 -2
  122. data/templates/sms-disable.str +1 -2
  123. data/templates/sms-request.str +1 -1
  124. data/templates/sms-setup.str +6 -4
  125. data/templates/two-factor-auth.str +5 -0
  126. data/templates/two-factor-disable.str +6 -0
  127. data/templates/two-factor-manage.str +16 -0
  128. data/templates/unlock-account-email.str +1 -1
  129. data/templates/unlock-account-request.str +4 -4
  130. data/templates/unlock-account.str +1 -1
  131. data/templates/verify-account-email.str +1 -1
  132. data/templates/verify-account-resend.str +3 -3
  133. data/templates/verify-account.str +1 -2
  134. data/templates/verify-login-change-email.str +2 -1
  135. data/templates/verify-login-change.str +1 -1
  136. data/templates/webauthn-auth.str +11 -0
  137. data/templates/webauthn-remove.str +14 -0
  138. data/templates/webauthn-setup.str +12 -0
  139. metadata +89 -50
  140. data/Rakefile +0 -179
  141. data/doc/verify_change_login.rdoc +0 -11
  142. data/lib/rodauth/features/verify_change_login.rb +0 -20
  143. data/spec/account_expiration_spec.rb +0 -225
  144. data/spec/all.rb +0 -1
  145. data/spec/change_login_spec.rb +0 -156
  146. data/spec/change_password_notify_spec.rb +0 -33
  147. data/spec/change_password_spec.rb +0 -202
  148. data/spec/close_account_spec.rb +0 -162
  149. data/spec/confirm_password_spec.rb +0 -70
  150. data/spec/create_account_spec.rb +0 -127
  151. data/spec/disallow_common_passwords_spec.rb +0 -93
  152. data/spec/disallow_password_reuse_spec.rb +0 -179
  153. data/spec/email_auth_spec.rb +0 -285
  154. data/spec/http_basic_auth_spec.rb +0 -143
  155. data/spec/jwt_refresh_spec.rb +0 -256
  156. data/spec/jwt_spec.rb +0 -235
  157. data/spec/lockout_spec.rb +0 -250
  158. data/spec/login_spec.rb +0 -328
  159. data/spec/migrate/001_tables.rb +0 -184
  160. data/spec/migrate/002_account_password_hash_column.rb +0 -11
  161. data/spec/migrate_password/001_tables.rb +0 -73
  162. data/spec/migrate_travis/001_tables.rb +0 -141
  163. data/spec/password_complexity_spec.rb +0 -109
  164. data/spec/password_expiration_spec.rb +0 -244
  165. data/spec/password_grace_period_spec.rb +0 -93
  166. data/spec/remember_spec.rb +0 -451
  167. data/spec/reset_password_spec.rb +0 -229
  168. data/spec/rodauth_spec.rb +0 -343
  169. data/spec/session_expiration_spec.rb +0 -58
  170. data/spec/single_session_spec.rb +0 -127
  171. data/spec/spec_helper.rb +0 -327
  172. data/spec/two_factor_spec.rb +0 -1423
  173. data/spec/update_password_hash_spec.rb +0 -40
  174. data/spec/verify_account_grace_period_spec.rb +0 -171
  175. data/spec/verify_account_spec.rb +0 -240
  176. data/spec/verify_change_login_spec.rb +0 -46
  177. data/spec/verify_login_change_spec.rb +0 -232
  178. data/spec/views/layout-other.str +0 -11
  179. data/spec/views/layout.str +0 -11
  180. data/spec/views/login.str +0 -21
@@ -1,1423 +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 allow two factor authentication setup, login, removal without recovery" do
564
- rodauth do
565
- enable :login, :logout, :otp
566
- otp_drift 10
567
- end
568
- roda do |r|
569
- r.rodauth
570
-
571
- r.redirect '/login' unless rodauth.logged_in?
572
-
573
- if rodauth.two_factor_authentication_setup?
574
- if rodauth.otp_locked_out?
575
- view :content=>"OTP Locked Out"
576
- else
577
- r.redirect '/otp-auth' unless rodauth.authenticated?
578
- view :content=>"With OTP"
579
- end
580
- else
581
- view :content=>"Without OTP"
582
- end
583
- end
584
-
585
- visit '/recovery-auth'
586
- page.current_path.must_equal '/login'
587
- visit '/recovery-codes'
588
- page.current_path.must_equal '/login'
589
-
590
- login
591
- page.html.must_include('Without OTP')
592
-
593
- visit '/otp-setup'
594
- page.title.must_equal 'Setup Two Factor Authentication'
595
- page.html.must_include '<svg'
596
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
597
- totp = ROTP::TOTP.new(secret)
598
- fill_in 'Password', :with=>'0123456789'
599
- fill_in 'Authentication Code', :with=>totp.now
600
- click_button 'Setup Two Factor Authentication'
601
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
602
- page.current_path.must_equal '/'
603
- page.html.must_include 'With OTP'
604
- reset_otp_last_use
605
-
606
- logout
607
- login
608
-
609
- visit '/otp-auth'
610
- 6.times do
611
- page.title.must_equal 'Enter Authentication Code'
612
- fill_in 'Authentication Code', :with=>'foo'
613
- click_button 'Authenticate via 2nd Factor'
614
- end
615
- page.find('#error_flash').text.must_equal 'Authentication code use locked out due to numerous failures.'
616
- page.body.must_include 'OTP Locked Out'
617
- page.current_path.must_equal '/'
618
- DB[:account_otp_keys].update(:num_failures=>0)
619
-
620
- visit '/otp-auth'
621
- page.title.must_equal 'Enter Authentication Code'
622
- page.html.wont_include 'Authenticate using recovery code'
623
- fill_in 'Authentication Code', :with=>totp.now
624
- click_button 'Authenticate via 2nd Factor'
625
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
626
- page.html.must_include 'With OTP'
627
-
628
- visit '/otp-disable'
629
- fill_in 'Password', :with=>'0123456789'
630
- click_button 'Disable Two Factor Authentication'
631
- page.find('#notice_flash').text.must_equal 'Two factor authentication has been disabled'
632
- page.html.must_include 'Without OTP'
633
- DB[:account_otp_keys].count.must_equal 0
634
- end
635
-
636
- it "should remove otp data when closing accounts" do
637
- rodauth do
638
- enable :login, :logout, :otp, :recovery_codes, :sms_codes, :close_account
639
- otp_drift 10
640
- two_factor_modifications_require_password? false
641
- close_account_requires_password? false
642
- sms_send{|*|}
643
- end
644
- roda do |r|
645
- r.rodauth
646
- r.root{view :content=>"With OTP"}
647
- end
648
-
649
- login
650
-
651
- visit '/otp-setup'
652
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
653
- totp = ROTP::TOTP.new(secret)
654
- fill_in 'Authentication Code', :with=>totp.now
655
- click_button 'Setup Two Factor Authentication'
656
-
657
- visit '/sms-setup'
658
- fill_in 'Phone Number', :with=>'(123) 456-7890'
659
- click_button 'Setup SMS Backup Number'
660
-
661
- DB[:account_otp_keys].count.must_equal 1
662
- DB[:account_recovery_codes].count.must_equal 16
663
- DB[:account_sms_codes].count.must_equal 1
664
- visit '/close-account'
665
- click_button 'Close Account'
666
- [:account_otp_keys, :account_recovery_codes, :account_sms_codes].each do |t|
667
- DB[t].count.must_equal 0
668
- end
669
- end
670
-
671
- it "should have recovery_codes and sms_codes work when used without otp" do
672
- sms_code, sms_phone, sms_message = nil
673
- rodauth do
674
- enable :login, :logout, :recovery_codes, :sms_codes
675
- sms_send do |phone, msg|
676
- sms_phone = phone
677
- sms_message = msg
678
- sms_code = msg[/\d+\z/]
679
- end
680
- end
681
- roda do |r|
682
- r.rodauth
683
-
684
- r.redirect '/login' unless rodauth.logged_in?
685
-
686
- if rodauth.two_factor_authentication_setup?
687
- r.redirect '/sms-request' unless rodauth.authenticated?
688
- view :content=>"With OTP"
689
- else
690
- view :content=>"Without OTP"
691
- end
692
- end
693
-
694
- login
695
- page.html.must_include('Without OTP')
696
-
697
- %w'/recovery-auth /recovery-codes'.each do |path|
698
- visit path
699
- page.find('#error_flash').text.must_equal 'This account has not been setup for two factor authentication'
700
- page.current_path.must_equal '/sms-setup'
701
- end
702
-
703
- %w'/sms-disable /sms-request /sms-auth'.each do |path|
704
- visit path
705
- page.find('#error_flash').text.must_equal 'SMS authentication has not been setup yet.'
706
- page.current_path.must_equal '/sms-setup'
707
- end
708
-
709
- visit '/sms-setup'
710
- page.title.must_equal 'Setup SMS Backup Number'
711
- fill_in 'Password', :with=>'012345678'
712
- fill_in 'Phone Number', :with=>'(123) 456'
713
- click_button 'Setup SMS Backup Number'
714
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
715
- page.html.must_include 'invalid password'
716
-
717
- fill_in 'Password', :with=>'0123456789'
718
- click_button 'Setup SMS Backup Number'
719
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
720
- page.html.must_include 'invalid SMS phone number'
721
-
722
- fill_in 'Password', :with=>'0123456789'
723
- fill_in 'Phone Number', :with=>'(123) 456-7890'
724
- click_button 'Setup SMS Backup Number'
725
- page.find('#notice_flash').text.must_equal 'SMS authentication needs confirmation.'
726
- sms_phone.must_equal '1234567890'
727
- sms_message.must_match(/\ASMS confirmation code for www\.example\.com is \d{12}\z/)
728
-
729
- page.title.must_equal 'Confirm SMS Backup Number'
730
- fill_in 'SMS Code', :with=>"asdf"
731
- click_button 'Confirm SMS Backup Number'
732
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
733
-
734
- fill_in 'Password', :with=>'0123456789'
735
- fill_in 'Phone Number', :with=>'(123) 456-7890'
736
- click_button 'Setup SMS Backup Number'
737
-
738
- visit '/sms-setup'
739
- page.find('#error_flash').text.must_equal 'SMS authentication needs confirmation.'
740
- page.title.must_equal 'Confirm SMS Backup Number'
741
-
742
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
743
- fill_in 'SMS Code', :with=>sms_code
744
- click_button 'Confirm SMS Backup Number'
745
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
746
-
747
- fill_in 'Password', :with=>'0123456789'
748
- fill_in 'Phone Number', :with=>'(123) 456-7890'
749
- click_button 'Setup SMS Backup Number'
750
- fill_in 'SMS Code', :with=>sms_code
751
- click_button 'Confirm SMS Backup Number'
752
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
753
-
754
- visit '/recovery-codes'
755
- page.title.must_equal 'View Authentication Recovery Codes'
756
- fill_in 'Password', :with=>'012345678'
757
- click_button 'View Authentication Recovery Codes'
758
- page.find('#error_flash').text.must_equal 'Unable to view recovery codes.'
759
- page.html.must_include 'invalid password'
760
-
761
- fill_in 'Password', :with=>'0123456789'
762
- click_button 'View Authentication Recovery Codes'
763
- page.title.must_equal 'Authentication Recovery Codes'
764
- recovery_codes = find('#recovery-codes').text.split
765
- recovery_codes.length.must_equal 16
766
- recovery_code = recovery_codes.first
767
-
768
- logout
769
- login
770
- page.current_path.must_equal '/sms-request'
771
-
772
- %w'/recovery-codes /sms-setup /sms-disable /sms-confirm'.each do |path|
773
- visit path
774
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
775
- page.current_path.must_equal '/sms-request'
776
- end
777
-
778
- visit '/sms-auth'
779
- page.current_path.must_equal '/sms-request'
780
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
781
-
782
- sms_phone = sms_message = nil
783
- page.title.must_equal 'Send SMS Code'
784
- click_button 'Send SMS Code'
785
- sms_phone.must_equal '1234567890'
786
- sms_message.must_match(/\ASMS authentication code for www\.example\.com is \d{6}\z/)
787
-
788
- fill_in 'SMS Code', :with=>"asdf"
789
- click_button 'Authenticate via SMS Code'
790
- page.html.must_include 'invalid SMS code'
791
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
792
-
793
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
794
- fill_in 'SMS Code', :with=>sms_code
795
- click_button 'Authenticate via SMS Code'
796
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
797
-
798
- click_button 'Send SMS Code'
799
- fill_in 'SMS Code', :with=>sms_code
800
- click_button 'Authenticate via SMS Code'
801
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
802
-
803
- %w'/recovery-auth /sms-request /sms-auth'.each do |path|
804
- visit path
805
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
806
- end
807
-
808
- %w'/sms-setup /sms-confirm'.each do |path|
809
- visit path
810
- page.find('#error_flash').text.must_equal 'SMS authentication has already been setup.'
811
- page.current_path.must_equal '/'
812
- end
813
-
814
- logout
815
- login
816
-
817
- click_button 'Send SMS Code'
818
-
819
- 5.times do
820
- click_button 'Authenticate via SMS Code'
821
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
822
- page.current_path.must_equal '/sms-auth'
823
- end
824
-
825
- click_button 'Authenticate via SMS Code'
826
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
827
- page.current_path.must_equal '/recovery-auth'
828
-
829
- visit '/sms-request'
830
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
831
- page.current_path.must_equal '/recovery-auth'
832
-
833
- page.title.must_equal 'Enter Authentication Recovery Code'
834
- fill_in 'Recovery Code', :with=>"asdf"
835
- click_button 'Authenticate via Recovery Code'
836
- page.find('#error_flash').text.must_equal 'Error authenticating via recovery code.'
837
- page.html.must_include 'Invalid recovery code'
838
-
839
- fill_in 'Recovery Code', :with=>recovery_code
840
- click_button 'Authenticate via Recovery Code'
841
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
842
- page.html.must_include 'With OTP'
843
-
844
- visit '/recovery-codes'
845
- fill_in 'Password', :with=>'0123456789'
846
- click_button 'View Authentication Recovery Codes'
847
- page.title.must_equal 'Authentication Recovery Codes'
848
- page.html.wont_include(recovery_code)
849
- find('#recovery-codes').text.split.length.must_equal 15
850
-
851
- click_button 'Add Authentication Recovery Codes'
852
- page.find('#error_flash').text.must_equal 'Unable to add recovery codes.'
853
- page.html.must_include 'invalid password'
854
-
855
- visit '/sms-disable'
856
- page.title.must_equal 'Disable Backup SMS Authentication'
857
- fill_in 'Password', :with=>'012345678'
858
- click_button 'Disable Backup SMS Authentication'
859
- page.find('#error_flash').text.must_equal 'Error disabling SMS authentication'
860
- page.html.must_include 'invalid password'
861
-
862
- fill_in 'Password', :with=>'0123456789'
863
- click_button 'Disable Backup SMS Authentication'
864
- page.find('#notice_flash').text.must_equal 'SMS authentication has been disabled.'
865
- page.current_path.must_equal '/'
866
-
867
- [:account_recovery_codes, :account_sms_codes].each do |t|
868
- DB[t].count.must_equal 0
869
- end
870
- end
871
-
872
- it "should have recovery_codes work when used by itself" do
873
- rodauth do
874
- enable :login, :logout, :recovery_codes
875
- end
876
- roda do |r|
877
- r.rodauth
878
-
879
- r.redirect '/login' unless rodauth.logged_in?
880
-
881
- if rodauth.two_factor_authentication_setup?
882
- r.redirect '/recovery-auth' unless rodauth.authenticated?
883
- view :content=>"With OTP"
884
- else
885
- view :content=>"Without OTP"
886
- end
887
- end
888
-
889
- login
890
- page.html.must_include('Without OTP')
891
-
892
- visit '/recovery-auth'
893
- page.find('#error_flash').text.must_equal 'This account has not been setup for two factor authentication'
894
- page.current_path.must_equal '/recovery-codes'
895
-
896
- page.title.must_equal 'View Authentication Recovery Codes'
897
- fill_in 'Password', :with=>'012345678'
898
- click_button 'View Authentication Recovery Codes'
899
- page.find('#error_flash').text.must_equal 'Unable to view recovery codes.'
900
- page.html.must_include 'invalid password'
901
-
902
- fill_in 'Password', :with=>'0123456789'
903
- click_button 'View Authentication Recovery Codes'
904
- page.title.must_equal 'Authentication Recovery Codes'
905
- recovery_codes = find('#recovery-codes').text.split
906
- recovery_codes.length.must_equal 0
907
- fill_in 'Password', :with=>'0123456789'
908
- click_button 'Add Authentication Recovery Codes'
909
- recovery_codes = find('#recovery-codes').text.split
910
- recovery_codes.length.must_equal 16
911
- recovery_code = recovery_codes.first
912
-
913
- logout
914
- login
915
- page.current_path.must_equal '/recovery-auth'
916
-
917
- visit '/recovery-codes'
918
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
919
- page.current_path.must_equal '/recovery-auth'
920
-
921
- page.title.must_equal 'Enter Authentication Recovery Code'
922
- fill_in 'Recovery Code', :with=>"asdf"
923
- click_button 'Authenticate via Recovery Code'
924
- page.find('#error_flash').text.must_equal 'Error authenticating via recovery code.'
925
- page.html.must_include 'Invalid recovery code'
926
-
927
- fill_in 'Recovery Code', :with=>recovery_code
928
- click_button 'Authenticate via Recovery Code'
929
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
930
- page.html.must_include 'With OTP'
931
-
932
- visit '/recovery-codes'
933
- fill_in 'Password', :with=>'0123456789'
934
- click_button 'View Authentication Recovery Codes'
935
- page.title.must_equal 'Authentication Recovery Codes'
936
- page.html.wont_include(recovery_code)
937
- page.html.wont_include('Add Authentication Recovery Codes')
938
- find('#recovery-codes').text.split.length.must_equal 16
939
- end
940
-
941
- it "should have sms_codes work when used by itself" do
942
- sms_code, sms_phone, sms_message = nil
943
- rodauth do
944
- enable :login, :logout, :sms_codes
945
- sms_send do |phone, msg|
946
- sms_phone = phone
947
- sms_message = msg
948
- sms_code = msg[/\d+\z/]
949
- end
950
- end
951
- roda do |r|
952
- r.rodauth
953
-
954
- r.redirect '/login' unless rodauth.logged_in?
955
-
956
- if rodauth.two_factor_authentication_setup?
957
- if rodauth.sms_locked_out?
958
- view :content=>"With SMS Locked Out"
959
- else
960
- rodauth.require_two_factor_authenticated
961
- view :content=>"With OTP"
962
- end
963
- else
964
- view :content=>"Without OTP"
965
- end
966
- end
967
-
968
- login
969
- page.html.must_include('Without OTP')
970
-
971
- %w'/sms-disable /sms-request /sms-auth'.each do |path|
972
- visit path
973
- page.find('#error_flash').text.must_equal 'SMS authentication has not been setup yet.'
974
- page.current_path.must_equal '/sms-setup'
975
- end
976
-
977
- visit '/sms-setup'
978
- page.title.must_equal 'Setup SMS Backup Number'
979
- fill_in 'Password', :with=>'012345678'
980
- fill_in 'Phone Number', :with=>'(123) 456'
981
- click_button 'Setup SMS Backup Number'
982
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
983
- page.html.must_include 'invalid password'
984
-
985
- fill_in 'Password', :with=>'0123456789'
986
- click_button 'Setup SMS Backup Number'
987
- page.find('#error_flash').text.must_equal 'Error setting up SMS authentication'
988
- page.html.must_include 'invalid SMS phone number'
989
-
990
- fill_in 'Password', :with=>'0123456789'
991
- fill_in 'Phone Number', :with=>'(123) 456-7890'
992
- click_button 'Setup SMS Backup Number'
993
- page.find('#notice_flash').text.must_equal 'SMS authentication needs confirmation.'
994
- sms_phone.must_equal '1234567890'
995
- sms_message.must_match(/\ASMS confirmation code for www\.example\.com is \d{12}\z/)
996
-
997
- page.title.must_equal 'Confirm SMS Backup Number'
998
- fill_in 'SMS Code', :with=>"asdf"
999
- click_button 'Confirm SMS Backup Number'
1000
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
1001
-
1002
- fill_in 'Password', :with=>'0123456789'
1003
- fill_in 'Phone Number', :with=>'(123) 456-7890'
1004
- click_button 'Setup SMS Backup Number'
1005
-
1006
- visit '/sms-setup'
1007
- page.find('#error_flash').text.must_equal 'SMS authentication needs confirmation.'
1008
- page.title.must_equal 'Confirm SMS Backup Number'
1009
-
1010
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1011
- fill_in 'SMS Code', :with=>sms_code
1012
- click_button 'Confirm SMS Backup Number'
1013
- page.find('#error_flash').text.must_equal 'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'
1014
-
1015
- fill_in 'Password', :with=>'0123456789'
1016
- fill_in 'Phone Number', :with=>'(123) 456-7890'
1017
- click_button 'Setup SMS Backup Number'
1018
- fill_in 'SMS Code', :with=>sms_code
1019
- click_button 'Confirm SMS Backup Number'
1020
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
1021
-
1022
- logout
1023
- login
1024
- page.current_path.must_equal '/sms-request'
1025
-
1026
- %w'/sms-setup /sms-disable /sms-confirm'.each do |path|
1027
- visit path
1028
- page.find('#error_flash').text.must_equal 'You need to authenticate via 2nd factor before continuing.'
1029
- page.current_path.must_equal '/sms-request'
1030
- end
1031
-
1032
- visit '/sms-auth'
1033
- page.current_path.must_equal '/sms-request'
1034
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
1035
-
1036
- sms_phone = sms_message = nil
1037
- page.title.must_equal 'Send SMS Code'
1038
- click_button 'Send SMS Code'
1039
- sms_phone.must_equal '1234567890'
1040
- sms_message.must_match(/\ASMS authentication code for www\.example\.com is \d{6}\z/)
1041
-
1042
- fill_in 'SMS Code', :with=>"asdf"
1043
- click_button 'Authenticate via SMS Code'
1044
- page.html.must_include 'invalid SMS code'
1045
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
1046
-
1047
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1048
- fill_in 'SMS Code', :with=>sms_code
1049
- click_button 'Authenticate via SMS Code'
1050
- page.find('#error_flash').text.must_equal 'No current SMS code for this account'
1051
-
1052
- click_button 'Send SMS Code'
1053
- fill_in 'SMS Code', :with=>sms_code
1054
- click_button 'Authenticate via SMS Code'
1055
- page.find('#notice_flash').text.must_equal 'You have been authenticated via 2nd factor'
1056
-
1057
- %w'/sms-request /sms-auth'.each do |path|
1058
- visit path
1059
- page.find('#error_flash').text.must_equal 'Already authenticated via 2nd factor'
1060
- end
1061
-
1062
- %w'/sms-setup /sms-confirm'.each do |path|
1063
- visit path
1064
- page.find('#error_flash').text.must_equal 'SMS authentication has already been setup.'
1065
- page.current_path.must_equal '/'
1066
- end
1067
-
1068
- logout
1069
- login
1070
-
1071
- click_button 'Send SMS Code'
1072
-
1073
- 5.times do
1074
- click_button 'Authenticate via SMS Code'
1075
- page.find('#error_flash').text.must_equal 'Error authenticating via SMS code.'
1076
- page.current_path.must_equal '/sms-auth'
1077
- end
1078
-
1079
- click_button 'Authenticate via SMS Code'
1080
- page.body.must_include "With SMS Locked Out"
1081
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
1082
- page.current_path.must_equal '/'
1083
-
1084
- visit '/sms-request'
1085
- page.find('#error_flash').text.must_equal 'SMS authentication has been locked out.'
1086
- page.current_path.must_equal '/'
1087
-
1088
- DB[:account_sms_codes].update(:num_failures=>0)
1089
- visit '/sms-request'
1090
- click_button 'Send SMS Code'
1091
- fill_in 'SMS Code', :with=>sms_code
1092
- click_button 'Authenticate via SMS Code'
1093
-
1094
- visit '/sms-disable'
1095
- page.title.must_equal 'Disable Backup SMS Authentication'
1096
- fill_in 'Password', :with=>'012345678'
1097
- click_button 'Disable Backup SMS Authentication'
1098
- page.find('#error_flash').text.must_equal 'Error disabling SMS authentication'
1099
- page.html.must_include 'invalid password'
1100
-
1101
- fill_in 'Password', :with=>'0123456789'
1102
- click_button 'Disable Backup SMS Authentication'
1103
- page.find('#notice_flash').text.must_equal 'SMS authentication has been disabled.'
1104
- page.current_path.must_equal '/'
1105
-
1106
- DB[:account_sms_codes].count.must_equal 0
1107
- end
1108
-
1109
- it "should allow two factor authentication via jwt" do
1110
- hmac_secret = sms_phone = sms_message = sms_code = nil
1111
- rodauth do
1112
- enable :login, :logout, :otp, :recovery_codes, :sms_codes
1113
- otp_drift 10
1114
- hmac_secret do
1115
- hmac_secret
1116
- end
1117
- sms_send do |phone, msg|
1118
- sms_phone = phone
1119
- sms_message = msg
1120
- sms_code = msg[/\d+\z/]
1121
- end
1122
- end
1123
- roda(:jwt) do |r|
1124
- r.rodauth
1125
-
1126
- if rodauth.logged_in?
1127
- if rodauth.two_factor_authentication_setup?
1128
- if rodauth.authenticated?
1129
- [1]
1130
- else
1131
- [2]
1132
- end
1133
- else
1134
- [3]
1135
- end
1136
- else
1137
- [4]
1138
- end
1139
- end
1140
-
1141
- json_request.must_equal [200, [4]]
1142
- json_login
1143
- json_request.must_equal [200, [3]]
1144
-
1145
- %w'/otp-disable /recovery-auth /recovery-codes /sms-setup /sms-confirm /otp-auth'.each do |path|
1146
- json_request(path).must_equal [403, {'error'=>'This account has not been setup for two factor authentication'}]
1147
- end
1148
- %w'/sms-disable /sms-request /sms-auth'.each do |path|
1149
- json_request(path).must_equal [403, {'error'=>'SMS authentication has not been setup yet.'}]
1150
- end
1151
-
1152
- secret = (ROTP::Base32.respond_to?(:random_base32) ? ROTP::Base32.random_base32 : ROTP::Base32.random.downcase)
1153
- totp = ROTP::TOTP.new(secret)
1154
-
1155
- res = json_request('/otp-setup', :password=>'123456', :otp_secret=>secret)
1156
- res.must_equal [401, {'error'=>'Error setting up two factor authentication', "field-error"=>["password", 'invalid password']}]
1157
-
1158
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>'adsf', :otp_secret=>secret)
1159
- res.must_equal [401, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1160
-
1161
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>'adsf', :otp_secret=>'asdf')
1162
- res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1163
-
1164
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret)
1165
- res.must_equal [200, {'success'=>'Two factor authentication is now setup'}]
1166
- reset_otp_last_use
1167
-
1168
- json_logout
1169
- json_login
1170
- json_request.must_equal [200, [2]]
1171
-
1172
- %w'/otp-disable /recovery-codes /otp-setup /sms-setup /sms-disable /sms-confirm'.each do |path|
1173
- json_request(path).must_equal [401, {'error'=>'You need to authenticate via 2nd factor before continuing.'}]
1174
- end
1175
-
1176
- res = json_request('/otp-auth', :otp=>'adsf')
1177
- res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1178
-
1179
- res = json_request('/otp-auth', :otp=>totp.now)
1180
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1181
- json_request.must_equal [200, [1]]
1182
- reset_otp_last_use
1183
-
1184
- res = json_request('/otp-setup')
1185
- res.must_equal [400, {'error'=>'You have already setup two factor authentication'}]
1186
-
1187
- %w'/otp-auth /recovery-auth /sms-request /sms-auth'.each do |path|
1188
- res = json_request(path)
1189
- res.must_equal [403, {'error'=>'Already authenticated via 2nd factor'}]
1190
- end
1191
-
1192
- res = json_request('/sms-disable')
1193
- res.must_equal [403, {'error'=>'SMS authentication has not been setup yet.'}]
1194
-
1195
- res = json_request('/sms-setup', :password=>'012345678', "sms-phone"=>'(123) 456')
1196
- res.must_equal [401, {'error'=>'Error setting up SMS authentication', "field-error"=>["password", 'invalid password']}]
1197
-
1198
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 456')
1199
- res.must_equal [422, {'error'=>'Error setting up SMS authentication', "field-error"=>["sms-phone", 'invalid SMS phone number']}]
1200
-
1201
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1202
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1203
-
1204
- sms_phone.must_equal '1234567890'
1205
- sms_message.must_match(/\ASMS confirmation code for example\.com: is \d{12}\z/)
1206
-
1207
- res = json_request('/sms-confirm', :sms_code=>'asdf')
1208
- res.must_equal [401, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1209
-
1210
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1211
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1212
-
1213
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1214
- res = json_request('/sms-confirm', :sms_code=>sms_code)
1215
- res.must_equal [401, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1216
-
1217
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1218
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1219
-
1220
- res = json_request('/sms-confirm', "sms-code"=>sms_code)
1221
- res.must_equal [200, {'success'=>'SMS authentication has been setup.'}]
1222
-
1223
- %w'/sms-setup /sms-confirm'.each do |path|
1224
- res = json_request(path)
1225
- res.must_equal [403, {'error'=>'SMS authentication has already been setup.'}]
1226
- end
1227
-
1228
- json_logout
1229
- json_login
1230
-
1231
- res = json_request('/sms-auth')
1232
- res.must_equal [401, {'error'=>'No current SMS code for this account'}]
1233
-
1234
- sms_phone = sms_message = nil
1235
- res = json_request('/sms-request')
1236
- res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
1237
- sms_phone.must_equal '1234567890'
1238
- sms_message.must_match(/\ASMS authentication code for example\.com: is \d{6}\z/)
1239
-
1240
- res = json_request('/sms-auth')
1241
- res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1242
-
1243
- DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1244
- res = json_request('/sms-auth')
1245
- res.must_equal [401, {'error'=>'No current SMS code for this account'}]
1246
-
1247
- res = json_request('/sms-request')
1248
- res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
1249
-
1250
- res = json_request('/sms-auth', 'sms-code'=>sms_code)
1251
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1252
- json_request.must_equal [200, [1]]
1253
-
1254
- json_logout
1255
- json_login
1256
-
1257
- res = json_request('/sms-request')
1258
- res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
1259
-
1260
- 5.times do
1261
- res = json_request('/sms-auth')
1262
- res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1263
- end
1264
-
1265
- res = json_request('/sms-auth')
1266
- res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1267
-
1268
- res = json_request('/sms-request')
1269
- res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1270
-
1271
- res = json_request('/otp-auth', :otp=>totp.now)
1272
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1273
- json_request.must_equal [200, [1]]
1274
-
1275
- res = json_request('/sms-disable', :password=>'012345678')
1276
- res.must_equal [401, {'error'=>'Error disabling SMS authentication', "field-error"=>["password", 'invalid password']}]
1277
-
1278
- res = json_request('/sms-disable', :password=>'0123456789')
1279
- res.must_equal [200, {'success'=>'SMS authentication has been disabled.'}]
1280
-
1281
- res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1282
- res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1283
-
1284
- res = json_request('/sms-confirm', "sms-code"=>sms_code)
1285
- res.must_equal [200, {'success'=>'SMS authentication has been setup.'}]
1286
-
1287
- res = json_request('/recovery-codes', :password=>'asdf')
1288
- res.must_equal [401, {'error'=>'Unable to view recovery codes.', "field-error"=>["password", 'invalid password']}]
1289
-
1290
- res = json_request('/recovery-codes', :password=>'0123456789')
1291
- codes = res[1].delete('codes')
1292
- codes.sort.must_equal DB[:account_recovery_codes].select_map(:code).sort
1293
- res.must_equal [200, {'success'=>''}]
1294
-
1295
- json_logout
1296
- json_login
1297
-
1298
- 5.times do
1299
- res = json_request('/otp-auth', :otp=>'asdf')
1300
- res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1301
- end
1302
-
1303
- res = json_request('/otp-auth', :otp=>'asdf')
1304
- 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.'}]
1305
-
1306
- res = json_request('/sms-request')
1307
- 5.times do
1308
- res = json_request('/sms-auth')
1309
- res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1310
- end
1311
-
1312
- res = json_request('/otp-auth', :otp=>'asdf')
1313
- res.must_equal [403, {'error'=>'Authentication code use locked out due to numerous failures. Can use recovery code to unlock.'}]
1314
-
1315
- res = json_request('/sms-auth')
1316
- res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1317
-
1318
- res = json_request('/recovery-auth', 'recovery-code'=>'adsf')
1319
- res.must_equal [401, {'error'=>'Error authenticating via recovery code.', "field-error"=>["recovery-code", "Invalid recovery code"]}]
1320
-
1321
- res = json_request('/recovery-auth', 'recovery-code'=>codes.first)
1322
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1323
- json_request.must_equal [200, [1]]
1324
-
1325
- res = json_request('/recovery-codes', :password=>'0123456789')
1326
- codes2 = res[1].delete('codes')
1327
- codes2.sort.must_equal codes[1..-1].sort
1328
- res.must_equal [200, {'success'=>''}]
1329
-
1330
- res = json_request('/recovery-codes', :password=>'012345678', :add=>'1')
1331
- res.must_equal [401, {'error'=>'Unable to add recovery codes.', "field-error"=>["password", 'invalid password']}]
1332
-
1333
- res = json_request('/recovery-codes', :password=>'0123456789', :add=>'1')
1334
- codes3 = res[1].delete('codes')
1335
- (codes3 - codes2).length.must_equal 1
1336
- res.must_equal [200, {'success'=>'Additional authentication recovery codes have been added.'}]
1337
-
1338
- res = json_request('/otp-disable', :password=>'012345678')
1339
- res.must_equal [401, {'error'=>'Error disabling up two factor authentication', "field-error"=>["password", 'invalid password']}]
1340
-
1341
- res = json_request('/otp-disable', :password=>'0123456789')
1342
- res.must_equal [200, {'success'=>'Two factor authentication has been disabled'}]
1343
-
1344
- [:account_otp_keys, :account_recovery_codes, :account_sms_codes].each do |t|
1345
- DB[t].count.must_equal 0
1346
- end
1347
-
1348
- hmac_secret = "123"
1349
- res = json_request('/otp-setup')
1350
- secret = res[1].delete("otp_secret")
1351
- raw_secret = res[1].delete("otp_raw_secret")
1352
- res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1353
-
1354
- totp = ROTP::TOTP.new(secret)
1355
- hmac_secret = "321"
1356
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret, :otp_raw_secret=>raw_secret)
1357
- res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1358
-
1359
- reset_otp_last_use
1360
- hmac_secret = "123"
1361
- res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret, :otp_raw_secret=>raw_secret)
1362
- res.must_equal [200, {'success'=>'Two factor authentication is now setup'}]
1363
- reset_otp_last_use
1364
-
1365
- json_logout
1366
- json_login
1367
-
1368
- hmac_secret = "321"
1369
- res = json_request('/otp-auth', :otp=>totp.now)
1370
- res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1371
-
1372
- hmac_secret = "123"
1373
- res = json_request('/otp-auth', :otp=>totp.now)
1374
- res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1375
- json_request.must_equal [200, [1]]
1376
- end
1377
-
1378
- it "should allow two factor authentication setup, login, recovery, removal" do
1379
- warning = nil
1380
- before_called = false
1381
- rodauth do
1382
- enable :login, :otp, :logout
1383
- (class << self; self end).send(:define_method, :warn){|w| warning = w}
1384
- before_otp_authentication_route{before_called = true}
1385
- warning.must_equal "before_otp_authentication_route is deprecated, switch to before_otp_auth_route"
1386
- otp_drift 10
1387
- end
1388
- roda do |r|
1389
- r.rodauth
1390
-
1391
- r.redirect '/login' unless rodauth.logged_in?
1392
-
1393
- if rodauth.two_factor_authentication_setup?
1394
- r.redirect '/otp-auth' unless rodauth.authenticated?
1395
- view :content=>"With OTP"
1396
- else
1397
- view :content=>"Without OTP"
1398
- end
1399
- end
1400
-
1401
- login
1402
- page.html.must_include('Without OTP')
1403
-
1404
- visit '/otp-auth'
1405
- before_called.must_equal false
1406
- page.current_path.must_equal '/otp-setup'
1407
-
1408
- secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
1409
- totp = ROTP::TOTP.new(secret)
1410
- fill_in 'Password', :with=>'0123456789'
1411
- fill_in 'Authentication Code', :with=>totp.now
1412
- click_button 'Setup Two Factor Authentication'
1413
- page.find('#notice_flash').text.must_equal 'Two factor authentication is now setup'
1414
- page.current_path.must_equal '/'
1415
- page.html.must_include 'With OTP'
1416
-
1417
- logout
1418
- before_called.must_equal false
1419
- login
1420
- page.current_path.must_equal '/otp-auth'
1421
- before_called.must_equal true
1422
- end
1423
- end