rodauth 1.7.0 → 1.8.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +6 -0
  3. data/MIT-LICENSE +1 -1
  4. data/doc/base.rdoc +14 -0
  5. data/doc/jwt.rdoc +1 -0
  6. data/doc/login.rdoc +2 -0
  7. data/doc/release_notes/1.8.0.txt +14 -0
  8. data/doc/sms_codes.rdoc +3 -1
  9. data/doc/two_factor_base.rdoc +4 -1
  10. data/lib/rodauth/features/base.rb +20 -0
  11. data/lib/rodauth/features/change_login.rb +4 -4
  12. data/lib/rodauth/features/change_password.rb +4 -4
  13. data/lib/rodauth/features/close_account.rb +1 -0
  14. data/lib/rodauth/features/confirm_password.rb +1 -0
  15. data/lib/rodauth/features/create_account.rb +5 -5
  16. data/lib/rodauth/features/jwt.rb +15 -0
  17. data/lib/rodauth/features/lockout.rb +4 -0
  18. data/lib/rodauth/features/login.rb +4 -3
  19. data/lib/rodauth/features/otp.rb +7 -3
  20. data/lib/rodauth/features/recovery_codes.rb +2 -0
  21. data/lib/rodauth/features/remember.rb +1 -0
  22. data/lib/rodauth/features/reset_password.rb +5 -3
  23. data/lib/rodauth/features/sms_codes.rb +14 -2
  24. data/lib/rodauth/features/two_factor_base.rb +7 -0
  25. data/lib/rodauth/features/verify_account.rb +4 -0
  26. data/lib/rodauth/version.rb +1 -1
  27. data/spec/change_login_spec.rb +4 -4
  28. data/spec/change_password_spec.rb +4 -4
  29. data/spec/close_account_spec.rb +1 -1
  30. data/spec/confirm_password_spec.rb +1 -1
  31. data/spec/create_account_spec.rb +4 -4
  32. data/spec/lockout_spec.rb +6 -6
  33. data/spec/login_spec.rb +26 -0
  34. data/spec/remember_spec.rb +1 -1
  35. data/spec/reset_password_spec.rb +8 -8
  36. data/spec/spec_helper.rb +3 -1
  37. data/spec/two_factor_spec.rb +30 -30
  38. data/spec/verify_account_spec.rb +5 -5
  39. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 72d21867a8a5d725cfe2331b979ffbe31f3e9e9a
4
- data.tar.gz: b204b666ab3b52ab94c291a660af0c6f6741772b
3
+ metadata.gz: 65176959334b7e6fa96008eabe2b3f6f4d69032d
4
+ data.tar.gz: 44aeba80765dfc4a4afa4f15848f4004a83d453a
5
5
  SHA512:
6
- metadata.gz: 6e7ec902dd360cf3687097e3c8934687f8915f277334270b2acbf3a02e33314e83cb42631ae3539d8edfaa87dc50aaebeef5b184f0fdc601c1d0c8ee196156be
7
- data.tar.gz: 49de39a38e7cb00f6869c4c0bc25f7df521f541615373d21530d802b5d390924a178b86274620ed6b08517157bd704eaad1dc5c7847e9ee2ef5df1d2ae25ac44
6
+ metadata.gz: 0500da1e1abe0cf9ed4e3cac1ca02102ab8a388bc6726e35de3934600676e7c886ca0181d2e0bd06385d1cd43015120cf5b4a97dcbaff6ceba4dc66ae8ac9abd
7
+ data.tar.gz: 8a5eed74a9f73f0ef22a9965a8c7f74b5c5de6036fa9af62d6adced7304f01a147d3f9e16d2966a79a898766704d512079d801f5be6864929f533db9f1aacfed
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ === 1.8.0 (2017-01-06)
2
+
3
+ * Add json_response_custom_error_status? option to jwt feature to use specific 4xx statuses instead of 400 (jeremyevans)
4
+
5
+ * Use 4xx error statuses for errors, instead of using a 200 success status (jeremyevans)
6
+
1
7
  === 1.7.0 (2016-11-22)
2
8
 
3
9
  * Make reset password, unlock account, and verify account pages not leak keys to external servers via Referer header (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2016 Jeremy Evans
1
+ Copyright (c) 2015-2017 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/doc/base.rdoc CHANGED
@@ -37,13 +37,23 @@ account_select :: An array of columns to select from +accounts_table+. By
37
37
  account_status_column :: The status id column in the account model.
38
38
  account_unverified_status_value :: The representating unverified accounts.
39
39
  default_redirect :: Where to redirect after most successful actions.
40
+ invalid_field_error_status :: The response status to use for invalid field
41
+ value errors, 422 by default.
42
+ invalid_key_error_status :: The response status to use for invalid key codes,
43
+ 401 by default.
44
+ invalid_password_error_status :: The response status to use for invalid passwords,
45
+ 401 by default.
40
46
  invalid_password_message :: The error message to display when a given
41
47
  password doesn't match the stored password hash.
48
+ lockout_error_status :: The response status to use a login is attempted to an account that
49
+ is locked out, 403 by default.
42
50
  login_column :: The login column in the account model.
43
51
  login_label :: The label to use for logins.
44
52
  login_param :: The parameter name to use for logins.
45
53
  modifications_require_password? :: Whether making changes to an account requires
46
54
  the user reinputing their password.
55
+ no_matching_login_error_status :: The response status to use when the login is not
56
+ in the database, 401 by default.
47
57
  no_matching_login_message :: The error message to display when the login
48
58
  used is not in the database.
49
59
  password_hash_column :: The password hash column in the password hash table.
@@ -61,6 +71,10 @@ set_deadline_values? :: Whether deadline values should be set. True by default
61
71
  if you want to vary the value based on a request parameter.
62
72
  template_opts :: Any template options to pass to view/render. This can be used
63
73
  to set a custom layout, for example.
74
+ unmatched_field_error_status :: The response status to use when two field values should
75
+ match but do not, 422 by default.
76
+ unopen_account_error_status :: The response status to use when trying to login to an
77
+ account that isn't open, 403 by default.
64
78
  use_date_arithmetic? :: Whether the date_arithmetic extension should be loaded into
65
79
  the database. Defaults to whether deadline values should
66
80
  be set.
data/doc/jwt.rdoc CHANGED
@@ -41,6 +41,7 @@ json_non_post_error_message :: The error message to use when a JSON non-POST req
41
41
  json_not_accepted_error_message :: The error message to display if jwt_check_accept? is true and the Accept header is present but does not match json_request_content_type_regexp.
42
42
  json_request_content_type_regexp :: The regexp to use to recognize a request as a json request.
43
43
  json_response_content_type :: The content type to set for json responses, application/json by default.
44
+ json_response_custom_error_status? :: Whether to use custom error statuses, instead of always using +json_response_error_status+, false by default for backwards compatibility.
44
45
  json_response_error_key :: The JSON result key containing an error message, "error" by default.
45
46
  json_response_error_status :: The HTTP status code to use for JSON error responses, 400 by default.
46
47
  json_response_field_error_key :: The JSON result key containing an field error message, "field-error" by default.
data/doc/login.rdoc CHANGED
@@ -9,6 +9,8 @@ login_additional_form_tags :: HTML fragment containing additional form
9
9
  tags to use on the login form.
10
10
  login_button :: The text to use for the login button.
11
11
  login_error_flash :: The flash error to show for an unsuccesful login.
12
+ login_error_status :: The response status to use when using an invalid
13
+ login or password to login, 401 by default.
12
14
  login_form_footer :: A message to display after the login form.
13
15
  login_notice_flash :: The flash notice to show after successful login.
14
16
  login_redirect :: Where to redirect after a sucessful login.
@@ -0,0 +1,14 @@
1
+ = Improvements
2
+
3
+ * When using a browser, Rodauth now uses an appropriate 401, 403,
4
+ or 422 error status for errors instead of using 200 success status.
5
+ Many configuration methods have been added to customize the status
6
+ codes used for specific types of errors.
7
+
8
+ * The json_response_custom_error_status? configuration method
9
+ has been added to the jwt feature, which if set to true makes
10
+ the jwt feature use the same error status codes for JSON API
11
+ requests that it would use for browser requests. For backward
12
+ compatibility, the default is to continue to use the 400
13
+ error status for all errors in the JSON API, but this will
14
+ change in Rodauth 2.
data/doc/sms_codes.rdoc CHANGED
@@ -21,7 +21,8 @@ you prefer:
21
21
  == Auth Value Methods
22
22
 
23
23
  no_current_sms_code_error_flash :: The flash error to show when going to the SMS authentication page and no current SMS authentication code is available.
24
- sms_already_setup_error_flash :: The flash error to show when goign to a page to setup SMS authentication if SMS authentication has already been setup.
24
+ sms_already_setup_error_status :: The response status to use when going to a page to setup SMS authentication if SMS authentication has already been setup, 403 by default.
25
+ sms_already_setup_error_flash :: The flash error to show when going to a page to setup SMS authentication if SMS authentication has already been setup.
25
26
  sms_already_setup_redirect :: Where to redirect when going to a page to setup SMS authentication if SMS authentication has already been setup.
26
27
  sms_auth_additional_form_tags :: HTML fragment containing additional form tags when authenticating via SMS.
27
28
  sms_auth_button :: Text to use for button on form to authenticate via SMS.
@@ -56,6 +57,7 @@ sms_invalid_phone_message :: The error message to show when an invalid SMS phone
56
57
  sms_issued_at_column :: The column in the +sms_codes_table+ containing the time the SMS code was issued.
57
58
  sms_lockout_error_flash :: The flash error to show when SMS authentication has been locked out due to repeated failures.
58
59
  sms_lockout_redirect :: Where to redirect after SMS authentication has been locked out.
60
+ sms_needs_confirmation_error_status :: The response status to use on SMS authentication pages when SMS authentication setup needs confirmation, 403 by default.
59
61
  sms_needs_confirmation_error_flash :: The flash error to show on SMS authentication pages when SMS authentication setup needs confirmation.
60
62
  sms_needs_confirmation_redirect :: Where to redirect after SMS setup, when confirmation is required.
61
63
  sms_needs_setup_redirect :: Where to redirect if going to an SMS authentication page when SMS authentication has not been setup.
@@ -5,14 +5,17 @@ factor authentication features.
5
5
 
6
6
  == Auth Value Methods
7
7
 
8
- two_factor_already_authenticated_error_flash :: The flash error to show if going to a two factor authentication page when already authenticated via 2nd factor
8
+ two_factor_already_authenticated_error_status :: The response status to use if going to a two factor authentication page when already authenticated via 2nd factor, 403 by default.
9
+ two_factor_already_authenticated_error_flash :: The flash error to show if going to a two factor authentication page when already authenticated via 2nd factor.
9
10
  two_factor_already_authenticated_redirect :: Where to redirect if going to a two factor authentication page when already authenticated via 2nd factor.
10
11
  two_factor_auth_notice_flash :: The flash notice to show after a successful two factor authentication.
11
12
  two_factor_auth_redirect :: Whether to redirect after a successful two factor authentication.
12
13
  two_factor_auth_required_redirect :: Where to redirect if going to a page requiring two factor authentication when not authenticated via 2nd factor.
13
14
  two_factor_modifications_require_password? :: Whether modifications to two factor authentication require the use of passwords.
15
+ two_factor_need_authentication_error_status :: The response status to use if going to a page that requires two factor authentication when not authenticated, 401 by default.
14
16
  two_factor_need_authentication_error_flash :: The flash error to show if going to a page that requires two factor authentication when not authenticated.
15
17
  two_factor_need_setup_redirect :: Where to redirect if going to a two factor authentication page when two factor authentication has not been setup.
18
+ two_factor_not_setup_error_status :: The response status to use if going to a two factor authentication page when two factor authentication has not been setup, 403 by default.
16
19
  two_factor_not_setup_error_flash :: The flash error to show if going to a two factor authentication page when two factor authentication has not been setup.
17
20
  two_factor_session_key :: The session key used for storing a symbol indicating which type of 2nd factor was used to authenticate.
18
21
  two_factor_setup_session_key :: The session key used for storing whether two factor authentication has been setup for the current account.
@@ -18,11 +18,16 @@ module Rodauth
18
18
  auth_value_method :account_unverified_status_value, 1
19
19
  auth_value_method :accounts_table, :accounts
20
20
  auth_value_method :default_redirect, '/'
21
+ auth_value_method :invalid_field_error_status, 422
22
+ auth_value_method :invalid_key_error_status, 401
23
+ auth_value_method :invalid_password_error_status, 401
21
24
  auth_value_method :invalid_password_message, "invalid password"
22
25
  auth_value_method :login_column, :email
26
+ auth_value_method :lockout_error_status, 403
23
27
  auth_value_method :password_hash_id_column, :id
24
28
  auth_value_method :password_hash_column, :password_hash
25
29
  auth_value_method :password_hash_table, :account_password_hashes
30
+ auth_value_method :no_matching_login_error_status, 401
26
31
  auth_value_method :no_matching_login_message, "no matching login"
27
32
  auth_value_method :login_param, 'login'
28
33
  auth_value_method :login_label, 'Login'
@@ -35,6 +40,8 @@ module Rodauth
35
40
  auth_value_method :skip_status_checks?, true
36
41
  auth_value_method :template_opts, {}
37
42
  auth_value_method :title_instance_variable, nil
43
+ auth_value_method :unmatched_field_error_status, 422
44
+ auth_value_method :unopen_account_error_status, 403
38
45
  auth_value_method :unverified_account_message, "unverified account, please verify account before logging in"
39
46
 
40
47
  redirect(:require_login){"#{prefix}/login"}
@@ -321,11 +328,24 @@ module Rodauth
321
328
  catch(:rodauth_error, &block)
322
329
  end
323
330
 
331
+ # Don't set an error status when redirecting in an error case, as a redirect status is needed.
332
+ def set_redirect_error_status(status)
333
+ end
334
+
335
+ def set_response_error_status(status)
336
+ response.status = status
337
+ end
338
+
324
339
  def throw_error(field, error)
325
340
  set_field_error(field, error)
326
341
  throw :rodauth_error
327
342
  end
328
343
 
344
+ def throw_error_status(status, field, error)
345
+ set_response_error_status(status)
346
+ throw_error(field, error)
347
+ end
348
+
329
349
  def use_date_arithmetic?
330
350
  set_deadline_values?
331
351
  end
@@ -28,22 +28,22 @@ module Rodauth
28
28
  r.post do
29
29
  catch_error do
30
30
  if change_login_requires_password? && !password_match?(param(password_param))
31
- throw_error(password_param, invalid_password_message)
31
+ throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
32
32
  end
33
33
 
34
34
  login = param(login_param)
35
35
  unless login_meets_requirements?(login)
36
- throw_error(login_param, login_does_not_meet_requirements_message)
36
+ throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
37
37
  end
38
38
 
39
39
  if require_login_confirmation? && login != param(login_confirm_param)
40
- throw_error(login_param, logins_do_not_match_message)
40
+ throw_error_status(unmatched_field_error_status, login_param, logins_do_not_match_message)
41
41
  end
42
42
 
43
43
  transaction do
44
44
  before_change_login
45
45
  unless change_login(login)
46
- throw_error(login_param, login_does_not_meet_requirements_message)
46
+ throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
47
47
  end
48
48
 
49
49
  after_change_login
@@ -29,20 +29,20 @@ module Rodauth
29
29
  r.post do
30
30
  catch_error do
31
31
  if change_password_requires_password? && !password_match?(param(password_param))
32
- throw_error(password_param, invalid_password_message)
32
+ throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
33
33
  end
34
34
 
35
35
  password = param(new_password_param)
36
36
  if require_password_confirmation? && password != param(password_confirm_param)
37
- throw_error(new_password_param, passwords_do_not_match_message)
37
+ throw_error_status(unmatched_field_error_status, new_password_param, passwords_do_not_match_message)
38
38
  end
39
39
 
40
40
  if password_match?(password)
41
- throw_error(new_password_param, same_as_existing_password_message)
41
+ throw_error_status(invalid_field_error_status, new_password_param, same_as_existing_password_message)
42
42
  end
43
43
 
44
44
  unless password_meets_requirements?(password)
45
- throw_error(new_password_param, password_does_not_meet_requirements_message)
45
+ throw_error_status(invalid_field_error_status, new_password_param, password_does_not_meet_requirements_message)
46
46
  end
47
47
 
48
48
  transaction do
@@ -46,6 +46,7 @@ module Rodauth
46
46
  set_notice_flash close_account_notice_flash
47
47
  redirect close_account_redirect
48
48
  else
49
+ set_response_error_status(invalid_password_error_status)
49
50
  set_field_error(password_param, invalid_password_message)
50
51
  set_error_flash close_account_error_flash
51
52
  close_account_view
@@ -32,6 +32,7 @@ module Rodauth
32
32
  set_notice_flash confirm_password_notice_flash
33
33
  redirect confirm_password_redirect
34
34
  else
35
+ set_response_error_status(invalid_password_error_status)
35
36
  set_field_error(password_param, invalid_password_message)
36
37
  set_error_flash confirm_password_error_flash
37
38
  confirm_password_view
@@ -46,25 +46,25 @@ module Rodauth
46
46
 
47
47
  catch_error do
48
48
  if require_login_confirmation? && login != param(login_confirm_param)
49
- throw_error(login_param, logins_do_not_match_message)
49
+ throw_error_status(unmatched_field_error_status, login_param, logins_do_not_match_message)
50
50
  end
51
51
 
52
52
  unless login_meets_requirements?(login)
53
- throw_error(login_param, login_does_not_meet_requirements_message)
53
+ throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
54
54
  end
55
55
 
56
56
  if require_password_confirmation? && password != param(password_confirm_param)
57
- throw_error(password_param, passwords_do_not_match_message)
57
+ throw_error_status(unmatched_field_error_status, password_param, passwords_do_not_match_message)
58
58
  end
59
59
 
60
60
  unless password_meets_requirements?(password)
61
- throw_error(password_param, password_does_not_meet_requirements_message)
61
+ throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
62
62
  end
63
63
 
64
64
  transaction do
65
65
  before_create_account
66
66
  unless save_account
67
- throw_error(login_param, login_does_not_meet_requirements_message)
67
+ throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
68
68
  end
69
69
 
70
70
  unless account_password_hash_column
@@ -11,6 +11,7 @@ module Rodauth
11
11
  auth_value_method :json_request_content_type_regexp, /\bapplication\/(?:vnd\.api\+)?json\b/i
12
12
  auth_value_method :json_response_content_type, 'application/json'
13
13
  auth_value_method :json_response_error_status, 400
14
+ auth_value_method :json_response_custom_error_status?, false
14
15
  auth_value_method :json_response_error_key, "error"
15
16
  auth_value_method :json_response_field_error_key, "field-error"
16
17
  auth_value_method :json_response_success_key, nil
@@ -193,6 +194,20 @@ module Rodauth
193
194
  @json_request = request.content_type =~ json_request_content_type_regexp
194
195
  end
195
196
 
197
+ def set_redirect_error_status(status)
198
+ if json_request? && json_response_custom_error_status?
199
+ response.status = status
200
+ end
201
+ end
202
+
203
+ def set_response_error_status(status)
204
+ if json_request? && !json_response_custom_error_status?
205
+ status = json_response_error_status
206
+ end
207
+
208
+ super
209
+ end
210
+
196
211
  def use_jwt?
197
212
  jwt_token || only_json? || json_request?
198
213
  end
@@ -70,6 +70,7 @@ module Rodauth
70
70
 
71
71
  set_notice_flash unlock_account_request_notice_flash
72
72
  else
73
+ set_redirect_error_status(no_matching_login_error_status)
73
74
  set_redirect_error_flash no_matching_login_message
74
75
  end
75
76
 
@@ -101,6 +102,7 @@ module Rodauth
101
102
  r.post do
102
103
  key = session[unlock_account_session_key] || param(unlock_account_key_param)
103
104
  unless account_from_unlock_key(key)
105
+ set_redirect_error_status invalid_key_error_status
104
106
  set_redirect_error_flash no_matching_unlock_account_key_message
105
107
  redirect unlock_account_request_redirect
106
108
  end
@@ -119,6 +121,7 @@ module Rodauth
119
121
  set_notice_flash unlock_account_notice_flash
120
122
  redirect unlock_account_redirect
121
123
  else
124
+ set_response_error_status(invalid_password_error_status)
122
125
  set_field_error(password_param, invalid_password_message)
123
126
  set_error_flash unlock_account_error_flash
124
127
  unlock_account_view
@@ -240,6 +243,7 @@ module Rodauth
240
243
  end
241
244
 
242
245
  def show_lockout_page
246
+ set_response_error_status lockout_error_status
243
247
  set_error_flash login_lockout_error_flash
244
248
  response.write unlock_account_request_view
245
249
  request.halt
@@ -9,6 +9,7 @@ module Rodauth
9
9
  button 'Login'
10
10
  redirect
11
11
 
12
+ auth_value_method :login_error_status, 401
12
13
  auth_value_method :login_form_footer, ''
13
14
 
14
15
  route do |r|
@@ -24,18 +25,18 @@ module Rodauth
24
25
 
25
26
  catch_error do
26
27
  unless account_from_login(param(login_param))
27
- throw_error(login_param, no_matching_login_message)
28
+ throw_error_status(no_matching_login_error_status, login_param, no_matching_login_message)
28
29
  end
29
30
 
30
31
  before_login_attempt
31
32
 
32
33
  unless open_account?
33
- throw_error(login_param, unverified_account_message)
34
+ throw_error_status(unopen_account_error_status, login_param, unverified_account_message)
34
35
  end
35
36
 
36
37
  unless password_match?(param(password_param))
37
38
  after_login_failure
38
- throw_error(password_param, invalid_password_message)
39
+ throw_error_status(login_error_status, password_param, invalid_password_message)
39
40
  end
40
41
 
41
42
  transaction do
@@ -98,6 +98,7 @@ module Rodauth
98
98
  require_otp_setup
99
99
 
100
100
  if otp_locked_out?
101
+ set_response_error_status(lockout_error_status)
101
102
  set_redirect_error_flash otp_lockout_error_flash
102
103
  redirect otp_lockout_redirect
103
104
  end
@@ -116,6 +117,7 @@ module Rodauth
116
117
 
117
118
  otp_record_authentication_failure
118
119
  after_otp_authentication_failure
120
+ set_response_error_status(invalid_key_error_status)
119
121
  set_field_error(otp_auth_param, otp_invalid_auth_code_message)
120
122
  set_error_flash otp_auth_error_flash
121
123
  otp_auth_view
@@ -141,16 +143,16 @@ module Rodauth
141
143
  secret = param(otp_setup_param)
142
144
  catch_error do
143
145
  unless otp_valid_key?(secret)
144
- throw_error(otp_setup_param, otp_invalid_secret_message)
146
+ throw_error_status(invalid_field_error_status, otp_setup_param, otp_invalid_secret_message)
145
147
  end
146
148
  otp_tmp_key(secret)
147
149
 
148
150
  unless two_factor_password_match?(param(password_param))
149
- throw_error(password_param, invalid_password_message)
151
+ throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
150
152
  end
151
153
 
152
154
  unless otp_valid_code?(param(otp_auth_param))
153
- throw_error(otp_auth_param, otp_invalid_auth_code_message)
155
+ throw_error_status(invalid_key_error_status, otp_auth_param, otp_invalid_auth_code_message)
154
156
  end
155
157
 
156
158
  transaction do
@@ -189,6 +191,7 @@ module Rodauth
189
191
  redirect otp_disable_redirect
190
192
  end
191
193
 
194
+ set_response_error_status(invalid_password_error_status)
192
195
  set_field_error(password_param, invalid_password_message)
193
196
  set_error_flash otp_disable_error_flash
194
197
  otp_disable_view
@@ -232,6 +235,7 @@ module Rodauth
232
235
 
233
236
  def require_otp_setup
234
237
  unless otp_exists?
238
+ set_redirect_error_status(two_factor_not_setup_error_status)
235
239
  set_redirect_error_flash two_factor_not_setup_error_flash
236
240
  redirect two_factor_need_setup_redirect
237
241
  end
@@ -72,6 +72,7 @@ module Rodauth
72
72
  two_factor_authenticate(:recovery_code)
73
73
  end
74
74
 
75
+ set_response_error_status(invalid_key_error_status)
75
76
  set_field_error(recovery_codes_param, invalid_recovery_code_message)
76
77
  set_error_flash invalid_recovery_code_error_flash
77
78
  recovery_auth_view
@@ -114,6 +115,7 @@ module Rodauth
114
115
  set_error_flash view_recovery_codes_error_flash
115
116
  end
116
117
 
118
+ set_response_error_status(invalid_password_error_status)
117
119
  set_field_error(password_param, invalid_password_message)
118
120
  recovery_codes_view
119
121
  end
@@ -74,6 +74,7 @@ module Rodauth
74
74
  set_notice_flash remember_notice_flash
75
75
  redirect remember_redirect
76
76
  else
77
+ set_response_error_status(invalid_field_error_status)
77
78
  set_error_flash remember_error_flash
78
79
  remember_view
79
80
  end
@@ -64,6 +64,7 @@ module Rodauth
64
64
 
65
65
  set_notice_flash reset_password_email_sent_notice_flash
66
66
  else
67
+ set_redirect_error_status(no_matching_login_error_status)
67
68
  set_redirect_error_flash reset_password_request_error_flash
68
69
  end
69
70
 
@@ -95,6 +96,7 @@ module Rodauth
95
96
  r.post do
96
97
  key = session[reset_password_session_key] || param(reset_password_key_param)
97
98
  unless account_from_reset_password_key(key)
99
+ set_redirect_error_status(invalid_key_error_status)
98
100
  set_redirect_error_flash reset_password_error_flash
99
101
  redirect reset_password_email_sent_redirect
100
102
  end
@@ -102,15 +104,15 @@ module Rodauth
102
104
  password = param(password_param)
103
105
  catch_error do
104
106
  if password_match?(password)
105
- throw_error(password_param, same_as_existing_password_message)
107
+ throw_error_status(invalid_field_error_status, password_param, same_as_existing_password_message)
106
108
  end
107
109
 
108
110
  if require_password_confirmation? && password != param(password_confirm_param)
109
- throw_error(password_param, passwords_do_not_match_message)
111
+ throw_error_status(unmatched_field_error_status, password_param, passwords_do_not_match_message)
110
112
  end
111
113
 
112
114
  unless password_meets_requirements?(password)
113
- throw_error(password_param, password_does_not_meet_requirements_message)
115
+ throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
114
116
  end
115
117
 
116
118
  transaction do
@@ -56,6 +56,9 @@ module Rodauth
56
56
  view 'sms-request', 'Send SMS Code', 'sms_request'
57
57
  view 'sms-setup', 'Setup SMS Backup Number', 'sms_setup'
58
58
 
59
+ auth_value_method :sms_already_setup_error_status, 403
60
+ auth_value_method :sms_needs_confirmation_error_status, 403
61
+
59
62
  auth_value_method :sms_auth_code_length, 6
60
63
  auth_value_method :sms_code_allowed_seconds, 300
61
64
  auth_value_method :sms_code_column, :code
@@ -138,6 +141,8 @@ module Rodauth
138
141
  if sms_code
139
142
  sms_set_code(nil)
140
143
  end
144
+
145
+ set_response_error_status(invalid_key_error_status)
141
146
  set_redirect_error_flash no_current_sms_code_error_flash
142
147
  redirect sms_request_redirect
143
148
  end
@@ -160,6 +165,7 @@ module Rodauth
160
165
  end
161
166
  end
162
167
 
168
+ set_response_error_status(invalid_key_error_status)
163
169
  set_field_error(sms_code_param, sms_invalid_code_message)
164
170
  set_error_flash sms_invalid_code_error_flash
165
171
  sms_auth_view
@@ -175,6 +181,7 @@ module Rodauth
175
181
  require_sms_not_setup
176
182
 
177
183
  if sms_needs_confirmation?
184
+ set_redirect_error_status(sms_needs_confirmation_error_status)
178
185
  set_redirect_error_flash sms_needs_confirmation_error_flash
179
186
  redirect sms_needs_confirmation_redirect
180
187
  end
@@ -188,13 +195,13 @@ module Rodauth
188
195
  r.post do
189
196
  catch_error do
190
197
  unless two_factor_password_match?(param(password_param))
191
- throw_error(password_param, invalid_password_message)
198
+ throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
192
199
  end
193
200
 
194
201
  phone = sms_normalize_phone(param(sms_phone_param))
195
202
 
196
203
  unless sms_valid_phone?(phone)
197
- throw_error(sms_phone_param, sms_invalid_phone_message)
204
+ throw_error_status(invalid_field_error_status, sms_phone_param, sms_invalid_phone_message)
198
205
  end
199
206
 
200
207
  transaction do
@@ -242,6 +249,7 @@ module Rodauth
242
249
  end
243
250
 
244
251
  sms_confirm_failure
252
+ set_redirect_error_status(invalid_key_error_status)
245
253
  set_redirect_error_flash sms_invalid_confirmation_code_error_flash
246
254
  redirect sms_needs_setup_redirect
247
255
  end
@@ -270,6 +278,7 @@ module Rodauth
270
278
  redirect sms_disable_redirect
271
279
  end
272
280
 
281
+ set_response_error_status(invalid_password_error_status)
273
282
  set_field_error(password_param, invalid_password_message)
274
283
  set_error_flash sms_disable_error_flash
275
284
  sms_disable_view
@@ -331,6 +340,7 @@ module Rodauth
331
340
 
332
341
  def require_sms_setup
333
342
  unless sms_setup?
343
+ set_redirect_error_status(two_factor_not_setup_error_status)
334
344
  set_redirect_error_flash sms_not_setup_error_flash
335
345
  redirect sms_needs_setup_redirect
336
346
  end
@@ -338,6 +348,7 @@ module Rodauth
338
348
 
339
349
  def require_sms_not_setup
340
350
  if sms_setup?
351
+ set_redirect_error_status(sms_already_setup_error_status)
341
352
  set_redirect_error_flash sms_already_setup_error_flash
342
353
  redirect sms_already_setup_redirect
343
354
  end
@@ -347,6 +358,7 @@ module Rodauth
347
358
  require_sms_setup
348
359
 
349
360
  if sms_locked_out?
361
+ set_redirect_error_status(lockout_error_status)
350
362
  set_redirect_error_flash sms_lockout_error_flash
351
363
  redirect sms_lockout_redirect
352
364
  end
@@ -13,6 +13,10 @@ module Rodauth
13
13
  error_flash "Already authenticated via 2nd factor", 'two_factor_already_authenticated'
14
14
  error_flash "You need to authenticate via 2nd factor before continuing.", 'two_factor_need_authentication'
15
15
 
16
+ auth_value_method :two_factor_already_authenticated_error_status, 403
17
+ auth_value_method :two_factor_need_authentication_error_status, 401
18
+ auth_value_method :two_factor_not_setup_error_status, 403
19
+
16
20
  auth_value_method :two_factor_session_key, :two_factor_auth
17
21
  auth_value_method :two_factor_setup_session_key, :two_factor_auth_setup
18
22
  auth_value_method :two_factor_need_setup_redirect, nil
@@ -46,6 +50,7 @@ module Rodauth
46
50
 
47
51
  def require_two_factor_setup
48
52
  unless uses_two_factor_authentication?
53
+ set_redirect_error_status(two_factor_not_setup_error_status)
49
54
  set_redirect_error_flash two_factor_not_setup_error_flash
50
55
  redirect two_factor_need_setup_redirect
51
56
  end
@@ -53,6 +58,7 @@ module Rodauth
53
58
 
54
59
  def require_two_factor_not_authenticated
55
60
  if two_factor_authenticated?
61
+ set_redirect_error_status(two_factor_already_authenticated_error_status)
56
62
  set_redirect_error_flash two_factor_already_authenticated_error_flash
57
63
  redirect two_factor_already_authenticated_redirect
58
64
  end
@@ -60,6 +66,7 @@ module Rodauth
60
66
 
61
67
  def require_two_factor_authenticated
62
68
  unless two_factor_authenticated?
69
+ set_redirect_error_status(two_factor_need_authentication_error_status)
63
70
  set_redirect_error_flash two_factor_need_authentication_error_flash
64
71
  redirect _two_factor_auth_required_redirect
65
72
  end
@@ -64,6 +64,7 @@ module Rodauth
64
64
 
65
65
  set_notice_flash verify_account_email_sent_notice_flash
66
66
  else
67
+ set_redirect_error_status(no_matching_login_error_status)
67
68
  set_redirect_error_flash verify_account_resend_error_flash
68
69
  end
69
70
 
@@ -95,6 +96,7 @@ module Rodauth
95
96
  r.post do
96
97
  key = session[verify_account_session_key] || param(verify_account_key_param)
97
98
  unless account_from_verify_account_key(key)
99
+ set_redirect_error_status(invalid_key_error_status)
98
100
  set_redirect_error_flash verify_account_error_flash
99
101
  redirect verify_account_redirect
100
102
  end
@@ -137,6 +139,7 @@ module Rodauth
137
139
 
138
140
  def new_account(login)
139
141
  if account_from_login(login)
142
+ set_redirect_error_status(unopen_account_error_status)
140
143
  set_error_flash attempt_to_create_unverified_account_notice_message
141
144
  response.write resend_verify_account_view
142
145
  request.halt
@@ -178,6 +181,7 @@ module Rodauth
178
181
 
179
182
  def before_login_attempt
180
183
  unless open_account?
184
+ set_redirect_error_status(unopen_account_error_status)
181
185
  set_error_flash attempt_to_login_to_unverified_account_notice_message
182
186
  response.write resend_verify_account_view
183
187
  request.halt
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VERSION = '1.7.0'.freeze
4
+ VERSION = '1.8.0'.freeze
5
5
 
6
6
  def self.version
7
7
  VERSION
@@ -128,13 +128,13 @@ describe 'Rodauth change_login feature' do
128
128
  json_login
129
129
 
130
130
  res = json_request('/change-login', :login=>'foobar', "login-confirm"=>'foobar')
131
- res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, not a valid email address"]}]
131
+ res.must_equal [422, {'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, not a valid email address"]}]
132
132
 
133
133
  res = json_request('/change-login', :login=>'foo@example.com', "login-confirm"=>'foo2@example.com')
134
- res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["login", "logins do not match"]}]
134
+ res.must_equal [422, {'error'=>"There was an error changing your login", "field-error"=>["login", "logins do not match"]}]
135
135
 
136
136
  res = json_request('/change-login', :login=>'foo2@example.com', "login-confirm"=>'foo2@example.com')
137
- res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, already an account with this login"]}]
137
+ res.must_equal [422, {'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, already an account with this login"]}]
138
138
 
139
139
  res = json_request('/change-login', :login=>'foo3@example.com', "login-confirm"=>'foo3@example.com')
140
140
  res.must_equal [200, {'success'=>"Your login has been changed"}]
@@ -145,7 +145,7 @@ describe 'Rodauth change_login feature' do
145
145
  require_password = true
146
146
 
147
147
  res = json_request('/change-login', :login=>'foo4@example.com', "login-confirm"=>'foo4@example.com', :password=>'012345678')
148
- res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["password", "invalid password"]}]
148
+ res.must_equal [401, {'error'=>"There was an error changing your login", "field-error"=>["password", "invalid password"]}]
149
149
 
150
150
  res = json_request('/change-login', :login=>'foo4@example.com', "login-confirm"=>'foo4@example.com', :password=>'0123456789')
151
151
  res.must_equal [200, {'success'=>"Your login has been changed"}]
@@ -149,20 +149,20 @@ describe 'Rodauth change_password feature' do
149
149
  json_login
150
150
 
151
151
  res = json_request('/change-password', :password=>'0123456789', "new-password"=>'0123456', "password-confirm"=>'0123456789')
152
- res.must_equal [400, {'error'=>"There was an error changing your password", "field-error"=>["new-password", "passwords do not match"]}]
152
+ res.must_equal [422, {'error'=>"There was an error changing your password", "field-error"=>["new-password", "passwords do not match"]}]
153
153
 
154
154
  res = json_request('/change-password', :password=>'0123456', "new-password"=>'0123456', "password-confirm"=>'0123456')
155
- res.must_equal [400, {'error'=>"There was an error changing your password", "field-error"=>["password", "invalid password"]}]
155
+ res.must_equal [401, {'error'=>"There was an error changing your password", "field-error"=>["password", "invalid password"]}]
156
156
 
157
157
  res = json_request('/change-password', :password=>'0123456789', "new-password"=>'0123456789', "password-confirm"=>'0123456789')
158
- res.must_equal [400, {'error'=>"There was an error changing your password", "field-error"=>["new-password", "invalid password, same as current password"]}]
158
+ res.must_equal [422, {'error'=>"There was an error changing your password", "field-error"=>["new-password", "invalid password, same as current password"]}]
159
159
 
160
160
  res = json_request('/change-password', :password=>'0123456789', "new-password"=>'0123456', "password-confirm"=>'0123456')
161
161
  res.must_equal [200, {'success'=>"Your password has been changed"}]
162
162
 
163
163
  json_logout
164
164
  res = json_login(:no_check=>true)
165
- res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
165
+ res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
166
166
 
167
167
  json_login(:pass=>'0123456')
168
168
 
@@ -152,7 +152,7 @@ describe 'Rodauth close_account feature' do
152
152
  json_login
153
153
 
154
154
  res = json_request('/close-account', :password=>'0123456')
155
- res.must_equal [400, {'error'=>"There was an error closing your account", "field-error"=>["password", "invalid password"]}]
155
+ res.must_equal [401, {'error'=>"There was an error closing your account", "field-error"=>["password", "invalid password"]}]
156
156
  DB[:accounts].select_map(:status_id).must_equal [2]
157
157
 
158
158
  res = json_request('/close-account', :password=>'0123456789')
@@ -59,7 +59,7 @@ describe 'Rodauth confirm password feature' do
59
59
  json_request('/reset').must_equal [200, [1]]
60
60
 
61
61
  res = json_request('/change-password', "new-password"=>'01234567', "password-confirm"=>'01234567')
62
- res.must_equal [400, {"field-error"=>["password", "invalid password"], "error"=>"There was an error changing your password"}]
62
+ res.must_equal [401, {"field-error"=>["password", "invalid password"], "error"=>"There was an error changing your password"}]
63
63
 
64
64
  res = json_request('/confirm-password', "password"=>'0123456')
65
65
  res.must_equal [200, {'success'=>"Your password has been confirmed"}]
@@ -108,16 +108,16 @@ describe 'Rodauth create_account feature' do
108
108
  end
109
109
 
110
110
  res = json_request('/create-account', :login=>'foo@example.com', "login-confirm"=>'foo@example.com', :password=>'0123456789', "password-confirm"=>'0123456789')
111
- res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["login", "invalid login, already an account with this login"]}]
111
+ res.must_equal [422, {'error'=>"There was an error creating your account", "field-error"=>["login", "invalid login, already an account with this login"]}]
112
112
 
113
113
  res = json_request('/create-account', :login=>'foobar', "login-confirm"=>'foobar', :password=>'0123456789', "password-confirm"=>'0123456789')
114
- res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["login", "invalid login, not a valid email address"]}]
114
+ res.must_equal [422, {'error'=>"There was an error creating your account", "field-error"=>["login", "invalid login, not a valid email address"]}]
115
115
 
116
116
  res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foobar', :password=>'0123456789', "password-confirm"=>'0123456789')
117
- res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["login", "logins do not match"]}]
117
+ res.must_equal [422, {'error'=>"There was an error creating your account", "field-error"=>["login", "logins do not match"]}]
118
118
 
119
119
  res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foo@example2.com', :password=>'012345678', "password-confirm"=>'0123456789')
120
- res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["password", "passwords do not match"]}]
120
+ res.must_equal [422, {'error'=>"There was an error creating your account", "field-error"=>["password", "passwords do not match"]}]
121
121
 
122
122
  res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foo@example2.com', :password=>'0123456789', "password-confirm"=>'0123456789')
123
123
  res.must_equal [200, {'success'=>"Your account has been created", 'account_id'=>DB[:accounts].max(:id)}]
data/spec/lockout_spec.rb CHANGED
@@ -190,33 +190,33 @@ describe 'Rodauth lockout feature' do
190
190
  end
191
191
 
192
192
  res = json_request('/unlock-account-request', :login=>'foo@example.com')
193
- res.must_equal [400, {'error'=>"no matching login"}]
193
+ res.must_equal [401, {'error'=>"no matching login"}]
194
194
 
195
195
  res = json_login(:pass=>'1', :no_check=>true)
196
- res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
196
+ res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
197
197
 
198
198
  json_login
199
199
  json_logout
200
200
 
201
201
  2.times do
202
202
  res = json_login(:pass=>'1', :no_check=>true)
203
- res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
203
+ res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
204
204
  end
205
205
 
206
206
  2.times do
207
207
  res = json_login(:pass=>'1', :no_check=>true)
208
- res.must_equal [400, {'error'=>"This account is currently locked out and cannot be logged in to."}]
208
+ res.must_equal [403, {'error'=>"This account is currently locked out and cannot be logged in to."}]
209
209
  end
210
210
 
211
211
  res = json_request('/unlock-account')
212
- res.must_equal [400, {'error'=>"No matching unlock account key"}]
212
+ res.must_equal [401, {'error'=>"No matching unlock account key"}]
213
213
 
214
214
  res = json_request('/unlock-account-request', :login=>'foo@example.com')
215
215
  res.must_equal [200, {'success'=>"An email has been sent to you with a link to unlock your account"}]
216
216
 
217
217
  link = email_link(/key=.+$/)
218
218
  res = json_request('/unlock-account', :key=>link[4...-1])
219
- res.must_equal [400, {'error'=>"No matching unlock account key"}]
219
+ res.must_equal [401, {'error'=>"No matching unlock account key"}]
220
220
 
221
221
  res = json_request('/unlock-account', :key=>link[4..-1])
222
222
  res.must_equal [200, {'success'=>"Your account has been unlocked"}]
data/spec/login_spec.rb CHANGED
@@ -157,6 +157,7 @@ describe 'Rodauth login feature' do
157
157
  it "should login and logout via jwt" do
158
158
  rodauth do
159
159
  enable :login, :logout
160
+ json_response_custom_error_status? false
160
161
  jwt_secret{proc{super()}.must_raise ArgumentError; "1"}
161
162
  end
162
163
  roda(:jwt) do |r|
@@ -179,4 +180,29 @@ describe 'Rodauth login feature' do
179
180
  json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
180
181
  json_request.must_equal [200, 2]
181
182
  end
183
+
184
+ it "should login and logout via jwt with custom error statuses" do
185
+ rodauth do
186
+ enable :login, :logout
187
+ end
188
+ roda(:jwt) do |r|
189
+ r.rodauth
190
+ response['Content-Type'] = 'application/json'
191
+ rodauth.logged_in? ? '1' : '2'
192
+ end
193
+
194
+ json_request.must_equal [200, 2]
195
+
196
+ res = json_request("/login", :login=>'foo@example2.com', :password=>'0123456789')
197
+ res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["login", "no matching login"]}]
198
+
199
+ res = json_request("/login", :login=>'foo@example.com', :password=>'012345678')
200
+ res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
201
+
202
+ json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
203
+ json_request.must_equal [200, 1]
204
+
205
+ json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
206
+ json_request.must_equal [200, 2]
207
+ end
182
208
  end
@@ -404,7 +404,7 @@ describe 'Rodauth remember feature' do
404
404
  json_request.must_equal [200, [1]]
405
405
 
406
406
  res = json_request('/confirm-password', :password=>'123456')
407
- res.must_equal [400, {'error'=>"There was an error confirming your password", "field-error"=>["password", "invalid password"]}]
407
+ res.must_equal [401, {'error'=>"There was an error confirming your password", "field-error"=>["password", "invalid password"]}]
408
408
 
409
409
  res = json_request('/confirm-password', :password=>'0123456789')
410
410
  res.must_equal [200, {'success'=>"Your password has been confirmed"}]
@@ -156,32 +156,32 @@ describe 'Rodauth reset_password feature' do
156
156
  end
157
157
 
158
158
  res = json_login(:pass=>'1', :no_check=>true)
159
- res.must_equal [400, {"field-error"=>["password", "invalid password"], "error"=>"There was an error logging in"}]
159
+ res.must_equal [401, {"field-error"=>["password", "invalid password"], "error"=>"There was an error logging in"}]
160
160
 
161
161
  res = json_request('/reset-password')
162
- res.must_equal [400, {"error"=>"There was an error resetting your password"}]
162
+ res.must_equal [401, {"error"=>"There was an error resetting your password"}]
163
163
 
164
164
  res = json_request('/reset-password-request', :login=>'foo@example2.com')
165
- res.must_equal [400, {"error"=>"There was an error requesting a password reset"}]
165
+ res.must_equal [401, {"error"=>"There was an error requesting a password reset"}]
166
166
 
167
167
  res = json_request('/reset-password-request', :login=>'foo@example.com')
168
168
  res.must_equal [200, {"success"=>"An email has been sent to you with a link to reset the password for your account"}]
169
169
 
170
170
  link = email_link(/key=.+$/)
171
171
  res = json_request('/reset-password', :key=>link[4...-1])
172
- res.must_equal [400, {"error"=>"There was an error resetting your password"}]
172
+ res.must_equal [401, {"error"=>"There was an error resetting your password"}]
173
173
 
174
174
  res = json_request('/reset-password', :key=>link[4..-1], :password=>'1', "password-confirm"=>'2')
175
- res.must_equal [400, {"error"=>"There was an error resetting your password", "field-error"=>["password", 'passwords do not match']}]
175
+ res.must_equal [422, {"error"=>"There was an error resetting your password", "field-error"=>["password", 'passwords do not match']}]
176
176
 
177
177
  res = json_request('/reset-password', :key=>link[4..-1], :password=>'0123456789', "password-confirm"=>'0123456789')
178
- res.must_equal [400, {"error"=>"There was an error resetting your password", "field-error"=>["password", 'invalid password, same as current password']}]
178
+ res.must_equal [422, {"error"=>"There was an error resetting your password", "field-error"=>["password", 'invalid password, same as current password']}]
179
179
 
180
180
  res = json_request('/reset-password', :key=>link[4..-1], :password=>'1', "password-confirm"=>'1')
181
- res.must_equal [400, {"error"=>"There was an error resetting your password", "field-error"=>["password", "invalid password, does not meet requirements (minimum 6 characters)"]}]
181
+ res.must_equal [422, {"error"=>"There was an error resetting your password", "field-error"=>["password", "invalid password, does not meet requirements (minimum 6 characters)"]}]
182
182
 
183
183
  res = json_request('/reset-password', :key=>link[4..-1], :password=>"\0ab123456", "password-confirm"=>"\0ab123456")
184
- res.must_equal [400, {"error"=>"There was an error resetting your password", "field-error"=>["password", "invalid password, does not meet requirements (contains null byte)"]}]
184
+ res.must_equal [422, {"error"=>"There was an error resetting your password", "field-error"=>["password", "invalid password, does not meet requirements (contains null byte)"]}]
185
185
 
186
186
  res = json_request('/reset-password', :key=>link[4..-1], :password=>'0123456', "password-confirm"=>'0123456')
187
187
  res.must_equal [200, {"success"=>"Your password has been reset"}]
data/spec/spec_helper.rb CHANGED
@@ -41,7 +41,8 @@ require 'logger'
41
41
  require 'tilt/string'
42
42
 
43
43
  db_url = ENV['RODAUTH_SPEC_DB'] || 'postgres:///?user=rodauth_test&password=rodauth_test'
44
- DB = Sequel.connect(db_url)
44
+ DB = Sequel.connect(db_url, :identifier_mangling=>false)
45
+ DB.extension(:freeze_datasets)
45
46
  puts "using #{DB.database_type}"
46
47
 
47
48
  #DB.loggers << Logger.new($stdout)
@@ -110,6 +111,7 @@ class Minitest::HooksSpec
110
111
  enable :jwt
111
112
  jwt_secret '1'
112
113
  json_response_success_key 'success'
114
+ json_response_custom_error_status? true
113
115
  end
114
116
  instance_exec(&rodauth_block)
115
117
  end
@@ -1114,23 +1114,23 @@ describe 'Rodauth OTP feature' do
1114
1114
  json_request.must_equal [200, [3]]
1115
1115
 
1116
1116
  %w'/otp-disable /recovery-auth /recovery-codes /sms-setup /sms-confirm /otp-auth'.each do |path|
1117
- json_request(path).must_equal [400, {'error'=>'This account has not been setup for two factor authentication'}]
1117
+ json_request(path).must_equal [403, {'error'=>'This account has not been setup for two factor authentication'}]
1118
1118
  end
1119
1119
  %w'/sms-disable /sms-request /sms-auth'.each do |path|
1120
- json_request(path).must_equal [400, {'error'=>'SMS authentication has not been setup yet.'}]
1120
+ json_request(path).must_equal [403, {'error'=>'SMS authentication has not been setup yet.'}]
1121
1121
  end
1122
1122
 
1123
1123
  secret = ROTP::Base32.random_base32
1124
1124
  totp = ROTP::TOTP.new(secret)
1125
1125
 
1126
1126
  res = json_request('/otp-setup', :password=>'123456', :otp_secret=>secret)
1127
- res.must_equal [400, {'error'=>'Error setting up two factor authentication', "field-error"=>["password", 'invalid password']}]
1127
+ res.must_equal [401, {'error'=>'Error setting up two factor authentication', "field-error"=>["password", 'invalid password']}]
1128
1128
 
1129
1129
  res = json_request('/otp-setup', :password=>'0123456789', :otp=>'adsf', :otp_secret=>secret)
1130
- res.must_equal [400, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1130
+ res.must_equal [401, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1131
1131
 
1132
1132
  res = json_request('/otp-setup', :password=>'0123456789', :otp=>'adsf', :otp_secret=>'asdf')
1133
- res.must_equal [400, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1133
+ res.must_equal [422, {'error'=>'Error setting up two factor authentication', "field-error"=>["otp_secret", 'invalid secret']}]
1134
1134
 
1135
1135
  res = json_request('/otp-setup', :password=>'0123456789', :otp=>totp.now, :otp_secret=>secret)
1136
1136
  res.must_equal [200, {'success'=>'Two factor authentication is now setup'}]
@@ -1141,11 +1141,11 @@ describe 'Rodauth OTP feature' do
1141
1141
  json_request.must_equal [200, [2]]
1142
1142
 
1143
1143
  %w'/otp-disable /recovery-codes /otp-setup /sms-setup /sms-disable /sms-confirm'.each do |path|
1144
- json_request(path).must_equal [400, {'error'=>'You need to authenticate via 2nd factor before continuing.'}]
1144
+ json_request(path).must_equal [401, {'error'=>'You need to authenticate via 2nd factor before continuing.'}]
1145
1145
  end
1146
1146
 
1147
1147
  res = json_request('/otp-auth', :otp=>'adsf')
1148
- res.must_equal [400, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1148
+ res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1149
1149
 
1150
1150
  res = json_request('/otp-auth', :otp=>totp.now)
1151
1151
  res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
@@ -1157,17 +1157,17 @@ describe 'Rodauth OTP feature' do
1157
1157
 
1158
1158
  %w'/otp-auth /recovery-auth /sms-request /sms-auth'.each do |path|
1159
1159
  res = json_request(path)
1160
- res.must_equal [400, {'error'=>'Already authenticated via 2nd factor'}]
1160
+ res.must_equal [403, {'error'=>'Already authenticated via 2nd factor'}]
1161
1161
  end
1162
1162
 
1163
1163
  res = json_request('/sms-disable')
1164
- res.must_equal [400, {'error'=>'SMS authentication has not been setup yet.'}]
1164
+ res.must_equal [403, {'error'=>'SMS authentication has not been setup yet.'}]
1165
1165
 
1166
1166
  res = json_request('/sms-setup', :password=>'012345678', "sms-phone"=>'(123) 456')
1167
- res.must_equal [400, {'error'=>'Error setting up SMS authentication', "field-error"=>["password", 'invalid password']}]
1167
+ res.must_equal [401, {'error'=>'Error setting up SMS authentication', "field-error"=>["password", 'invalid password']}]
1168
1168
 
1169
1169
  res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 456')
1170
- res.must_equal [400, {'error'=>'Error setting up SMS authentication', "field-error"=>["sms-phone", 'invalid SMS phone number']}]
1170
+ res.must_equal [422, {'error'=>'Error setting up SMS authentication', "field-error"=>["sms-phone", 'invalid SMS phone number']}]
1171
1171
 
1172
1172
  res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1173
1173
  res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
@@ -1176,14 +1176,14 @@ describe 'Rodauth OTP feature' do
1176
1176
  sms_message.must_match(/\ASMS confirmation code for example\.com: is \d{12}\z/)
1177
1177
 
1178
1178
  res = json_request('/sms-confirm', :sms_code=>'asdf')
1179
- res.must_equal [400, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1179
+ res.must_equal [401, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1180
1180
 
1181
1181
  res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1182
1182
  res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
1183
1183
 
1184
1184
  DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1185
1185
  res = json_request('/sms-confirm', :sms_code=>sms_code)
1186
- res.must_equal [400, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1186
+ res.must_equal [401, {'error'=>'Invalid or out of date SMS confirmation code used, must setup SMS authentication again.'}]
1187
1187
 
1188
1188
  res = json_request('/sms-setup', :password=>'0123456789', "sms-phone"=>'(123) 4567 890')
1189
1189
  res.must_equal [200, {'success'=>'SMS authentication needs confirmation.'}]
@@ -1193,14 +1193,14 @@ describe 'Rodauth OTP feature' do
1193
1193
 
1194
1194
  %w'/sms-setup /sms-confirm'.each do |path|
1195
1195
  res = json_request(path)
1196
- res.must_equal [400, {'error'=>'SMS authentication has already been setup.'}]
1196
+ res.must_equal [403, {'error'=>'SMS authentication has already been setup.'}]
1197
1197
  end
1198
1198
 
1199
1199
  json_logout
1200
1200
  json_login
1201
1201
 
1202
1202
  res = json_request('/sms-auth')
1203
- res.must_equal [400, {'error'=>'No current SMS code for this account'}]
1203
+ res.must_equal [401, {'error'=>'No current SMS code for this account'}]
1204
1204
 
1205
1205
  sms_phone = sms_message = nil
1206
1206
  res = json_request('/sms-request')
@@ -1209,11 +1209,11 @@ describe 'Rodauth OTP feature' do
1209
1209
  sms_message.must_match(/\ASMS authentication code for example\.com: is \d{6}\z/)
1210
1210
 
1211
1211
  res = json_request('/sms-auth')
1212
- res.must_equal [400, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1212
+ res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1213
1213
 
1214
1214
  DB[:account_sms_codes].update(:code_issued_at=>Time.now - 310)
1215
1215
  res = json_request('/sms-auth')
1216
- res.must_equal [400, {'error'=>'No current SMS code for this account'}]
1216
+ res.must_equal [401, {'error'=>'No current SMS code for this account'}]
1217
1217
 
1218
1218
  res = json_request('/sms-request')
1219
1219
  res.must_equal [200, {'success'=>'SMS authentication code has been sent.'}]
@@ -1230,21 +1230,21 @@ describe 'Rodauth OTP feature' do
1230
1230
 
1231
1231
  5.times do
1232
1232
  res = json_request('/sms-auth')
1233
- res.must_equal [400, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1233
+ res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1234
1234
  end
1235
1235
 
1236
1236
  res = json_request('/sms-auth')
1237
- res.must_equal [400, {'error'=>'SMS authentication has been locked out.'}]
1237
+ res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1238
1238
 
1239
1239
  res = json_request('/sms-request')
1240
- res.must_equal [400, {'error'=>'SMS authentication has been locked out.'}]
1240
+ res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1241
1241
 
1242
1242
  res = json_request('/otp-auth', :otp=>totp.now)
1243
1243
  res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
1244
1244
  json_request.must_equal [200, [1]]
1245
1245
 
1246
1246
  res = json_request('/sms-disable', :password=>'012345678')
1247
- res.must_equal [400, {'error'=>'Error disabling SMS authentication', "field-error"=>["password", 'invalid password']}]
1247
+ res.must_equal [401, {'error'=>'Error disabling SMS authentication', "field-error"=>["password", 'invalid password']}]
1248
1248
 
1249
1249
  res = json_request('/sms-disable', :password=>'0123456789')
1250
1250
  res.must_equal [200, {'success'=>'SMS authentication has been disabled.'}]
@@ -1256,7 +1256,7 @@ describe 'Rodauth OTP feature' do
1256
1256
  res.must_equal [200, {'success'=>'SMS authentication has been setup.'}]
1257
1257
 
1258
1258
  res = json_request('/recovery-codes', :password=>'asdf')
1259
- res.must_equal [400, {'error'=>'Unable to view recovery codes.', "field-error"=>["password", 'invalid password']}]
1259
+ res.must_equal [401, {'error'=>'Unable to view recovery codes.', "field-error"=>["password", 'invalid password']}]
1260
1260
 
1261
1261
  res = json_request('/recovery-codes', :password=>'0123456789')
1262
1262
  codes = res[1].delete('codes')
@@ -1268,26 +1268,26 @@ describe 'Rodauth OTP feature' do
1268
1268
 
1269
1269
  5.times do
1270
1270
  res = json_request('/otp-auth', :otp=>'asdf')
1271
- res.must_equal [400, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1271
+ res.must_equal [401, {'error'=>'Error logging in via two factor authentication', "field-error"=>["otp", 'Invalid authentication code']}]
1272
1272
  end
1273
1273
 
1274
1274
  res = json_request('/otp-auth', :otp=>'asdf')
1275
- res.must_equal [400, {'error'=>'Authentication code use locked out due to numerous failures. Can use recovery code to unlock. Can use SMS code to unlock.'}]
1275
+ 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.'}]
1276
1276
 
1277
1277
  res = json_request('/sms-request')
1278
1278
  5.times do
1279
1279
  res = json_request('/sms-auth')
1280
- res.must_equal [400, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1280
+ res.must_equal [401, {'error'=>'Error authenticating via SMS code.', "field-error"=>["sms-code", "invalid SMS code"]}]
1281
1281
  end
1282
1282
 
1283
1283
  res = json_request('/otp-auth', :otp=>'asdf')
1284
- res.must_equal [400, {'error'=>'Authentication code use locked out due to numerous failures. Can use recovery code to unlock.'}]
1284
+ res.must_equal [403, {'error'=>'Authentication code use locked out due to numerous failures. Can use recovery code to unlock.'}]
1285
1285
 
1286
1286
  res = json_request('/sms-auth')
1287
- res.must_equal [400, {'error'=>'SMS authentication has been locked out.'}]
1287
+ res.must_equal [403, {'error'=>'SMS authentication has been locked out.'}]
1288
1288
 
1289
1289
  res = json_request('/recovery-auth', 'recovery-code'=>'adsf')
1290
- res.must_equal [400, {'error'=>'Error authenticating via recovery code.', "field-error"=>["recovery-code", "Invalid recovery code"]}]
1290
+ res.must_equal [401, {'error'=>'Error authenticating via recovery code.', "field-error"=>["recovery-code", "Invalid recovery code"]}]
1291
1291
 
1292
1292
  res = json_request('/recovery-auth', 'recovery-code'=>codes.first)
1293
1293
  res.must_equal [200, {'success'=>'You have been authenticated via 2nd factor'}]
@@ -1299,7 +1299,7 @@ describe 'Rodauth OTP feature' do
1299
1299
  res.must_equal [200, {'success'=>''}]
1300
1300
 
1301
1301
  res = json_request('/recovery-codes', :password=>'012345678', :add=>'1')
1302
- res.must_equal [400, {'error'=>'Unable to add recovery codes.', "field-error"=>["password", 'invalid password']}]
1302
+ res.must_equal [401, {'error'=>'Unable to add recovery codes.', "field-error"=>["password", 'invalid password']}]
1303
1303
 
1304
1304
  res = json_request('/recovery-codes', :password=>'0123456789', :add=>'1')
1305
1305
  codes3 = res[1].delete('codes')
@@ -1307,7 +1307,7 @@ describe 'Rodauth OTP feature' do
1307
1307
  res.must_equal [200, {'success'=>'Additional authentication recovery codes have been added.'}]
1308
1308
 
1309
1309
  res = json_request('/otp-disable', :password=>'012345678')
1310
- res.must_equal [400, {'error'=>'Error disabling up two factor authentication', "field-error"=>["password", 'invalid password']}]
1310
+ res.must_equal [401, {'error'=>'Error disabling up two factor authentication', "field-error"=>["password", 'invalid password']}]
1311
1311
 
1312
1312
  res = json_request('/otp-disable', :password=>'0123456789')
1313
1313
  res.must_equal [200, {'success'=>'Two factor authentication has been disabled'}]
@@ -116,23 +116,23 @@ describe 'Rodauth verify_account feature' do
116
116
  link = email_link(/key=.+$/, 'foo@example2.com')
117
117
 
118
118
  res = json_request('/verify-account-resend', :login=>'foo@example.com')
119
- res.must_equal [400, {'error'=>"Unable to resend verify account email"}]
119
+ res.must_equal [401, {'error'=>"Unable to resend verify account email"}]
120
120
 
121
121
  res = json_request('/verify-account-resend', :login=>'foo@example3.com')
122
- res.must_equal [400, {'error'=>"Unable to resend verify account email"}]
122
+ res.must_equal [401, {'error'=>"Unable to resend verify account email"}]
123
123
 
124
124
  res = json_request('/login', :login=>'foo@example2.com',:password=>'0123456789')
125
- res.must_equal [400, {'error'=>"The account you tried to login with is currently awaiting verification"}]
125
+ res.must_equal [403, {'error'=>"The account you tried to login with is currently awaiting verification"}]
126
126
 
127
127
  res = json_request('/verify-account-resend', :login=>'foo@example2.com')
128
128
  res.must_equal [200, {'success'=>"An email has been sent to you with a link to verify your account"}]
129
129
  email_link(/key=.+$/, 'foo@example2.com').must_equal link
130
130
 
131
131
  res = json_request('/verify-account')
132
- res.must_equal [400, {'error'=>"Unable to verify account"}]
132
+ res.must_equal [401, {'error'=>"Unable to verify account"}]
133
133
 
134
134
  res = json_request('/verify-account', :key=>link[4...-1])
135
- res.must_equal [400, {"error"=>"Unable to verify account"}]
135
+ res.must_equal [401, {"error"=>"Unable to verify account"}]
136
136
 
137
137
  res = json_request('/verify-account', :key=>link[4..-1])
138
138
  res.must_equal [200, {"success"=>"Your account has been verified"}]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-22 00:00:00.000000000 Z
11
+ date: 2017-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -234,6 +234,7 @@ extra_rdoc_files:
234
234
  - doc/release_notes/1.5.0.txt
235
235
  - doc/release_notes/1.6.0.txt
236
236
  - doc/release_notes/1.7.0.txt
237
+ - doc/release_notes/1.8.0.txt
237
238
  files:
238
239
  - CHANGELOG
239
240
  - MIT-LICENSE
@@ -267,6 +268,7 @@ files:
267
268
  - doc/release_notes/1.5.0.txt
268
269
  - doc/release_notes/1.6.0.txt
269
270
  - doc/release_notes/1.7.0.txt
271
+ - doc/release_notes/1.8.0.txt
270
272
  - doc/remember.rdoc
271
273
  - doc/reset_password.rdoc
272
274
  - doc/session_expiration.rdoc