rodauth 1.21.0 → 2.2.0

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