rodauth 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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