rodauth 1.20.0 → 2.1.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 (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