Skip to content

Commit 693a084

Browse files
authored
Completely change json error response (pallets-eco#638)
Now return errors is always a simple list of errors. we also return field_errors - which is the old wtforms dict of errors. Support new wtforms 3.0 form level errors. Convert some view code to utilize form level errors.
1 parent 0bf4f8e commit 693a084

17 files changed

+377
-343
lines changed

CHANGES.rst

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,33 @@ Version 5.0.0
88

99
Released TBD
1010

11+
**PLEASE READ CHANGE NOTES CAREFULLY - THERE ARE LIKELY REQUIRED CHANGES YOU WILL HAVE TO MAKE**
12+
1113
Features
1214
++++++++
13-
- (:issue:`475`) Support for WebAuthn
14-
- (:issue:`479`) Support Two-factor recovery codes
15-
- (:pr:`532`) Support for Python 3.10
16-
- (:pr:`540`) Improve Templates in support of JS required by WebAuthn
15+
- (:issue:`475`) Support for WebAuthn.
16+
- (:issue:`479`) Support Two-factor recovery codes.
17+
- (:pr:`532`) Support for Python 3.10.
18+
- (:pr:`540`) Improve Templates in support of JS required by WebAuthn.
1719
- (:pr:`568`) Deprecate the old passwordless feature in favor of Unified Signin.
1820
Deprecate replacing login_manager so we can possibly vendor that in in the future.
1921
- (:pr:`608`) Add Icelandic translations. (ofurkusi)
2022
- (:issue:`256`) Add custom HTML attributes to improve user experience.
2123
This changed LoginForm quite a bit - please see backwards compatability concerns
2224
below. The default LoginForm and template should be the same as before.
25+
- (:pr:`xx`) The JSON errors response has been unified. Please see backwards
26+
compatibility concerns below.
2327

2428
Fixes
2529
+++++
26-
- (:pr:`591`) Make the required zxcvbn complexity score configurable (mephi42)
27-
- (:issue:`585`) Provide option to prevent user enumeration.
30+
- (:pr:`591`) Make the required zxcvbn complexity score configurable. (mephi42)
31+
- (:issue:`585`) Provide option to prevent user enumeration (i.e. Generic Responses).
2832
- (:issue:`531`) Get rid of Flask-Mail. Flask-Mailman is now the default preferred email package.
2933
Flask-Mail is still supported so there should be no backwards compatability issues.
3034
- (:issue:`597`) A delete option has been added to us-setup (form and view).
3135
- (:pr:`625`) Improve username support - the LoginForm now has a separate field for username if
3236
``SECURITY_USERNAME_ENABLE`` is True, and properly displays input fields only if the associated
33-
field is an identity attribute (as specified by :py:data:`SECURITY_USER_IDENTITY_ATTRIBUTES`)
37+
field is an identity attribute (as specified by :py:data:`SECURITY_USER_IDENTITY_ATTRIBUTES`).
3438
- (:pr:`627`) Improve empty password handling. Prior, an unguessable password was set into the user
3539
record when a user registered without a password - now, the DB user model has been changed to
3640
allow nullable passwords. This provides a better user experience since Flask-Security now
@@ -90,6 +94,13 @@ Other:
9094

9195
- `lost_device` -> `email`
9296
- `no_mail_access` -> `help`
97+
- JSON error responses. **THIS IS A BREAKING CHANGE**.
98+
In earlier releases, the JSON error response could have either a `error` key which was for rare cases
99+
where there was a single non-form related error, or an `errors` key which was a a dict as defined by WTForms.
100+
Now, the `errors` key will contain a list of (localized) messages - both non-form related as well as any form related.
101+
The key `field_errors` will contain the dict as specified by WTForms. Please note that starting with WTForms 3.0
102+
form-level errors are supported and show up in the dict with the field name/key of "none". There are no changes to non-error
103+
related JSON responses.
93104

94105
For templates:
95106

docs/openapi.yaml

Lines changed: 103 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ paths:
946946
schema:
947947
$ref: "#/components/schemas/UsSetupValidateJsonResponse"
948948
302:
949-
description: Successfuly validated and persisted sign in method.
949+
description: Successfully validated and persisted sign in method.
950950
headers:
951951
Location:
952952
description: |
@@ -1890,26 +1890,29 @@ components:
18901890
properties:
18911891
response:
18921892
type: object
1893+
required: [ errors ]
18931894
description: >
1894-
For form validation errors, the 'errors' key will be set with a list of errors per
1895-
invalid form input field. For non-form related errors, the 'error' key will be set
1896-
with a single (localized) error string.
1895+
For form validation errors, the 'field_errors' key will be set with a list of errors per
1896+
invalid form input field (i.e. a dict of 'field-name': list of error strings).
1897+
The 'errors' key will be a simple list of both form and non-form related
1898+
errors (all form errors will also be included here).
18971899
properties:
1898-
errors:
1900+
field_errors:
18991901
type: object
19001902
description: >
19011903
Errors per input/form field
1902-
properties:
1903-
field-name:
1904-
type: array
1905-
items:
1906-
type: string
1907-
example: field validation error.
1908-
description: Error message (localized)
1909-
error:
1910-
type: string
1911-
example: "Unauthenticated"
1912-
description: Error message (localized)
1904+
additionalProperties:
1905+
type: array
1906+
items:
1907+
type: string
1908+
example: field validation error.
1909+
description: Error message (localized)
1910+
errors:
1911+
type: array
1912+
items:
1913+
type: string
1914+
example: "Unauthenticated"
1915+
description: Error message (localized)
19131916
Verify:
19141917
type: object
19151918
required: [ password ]
@@ -1995,37 +1998,30 @@ components:
19951998
type: boolean
19961999
tf_validity_token:
19972000
type: string
1998-
description: Code verifying the user has successfully verfied 2FA in the past. If verified, the user is able to skip validation of the second factor. Only used when SECURITY_TWO_FACTOR_ALWAYS_VALIDATE is False.
2001+
description: Code verifying the user has successfully verified 2FA in the past. If verified, the user is able to skip validation of the second factor. Only used when SECURITY_TWO_FACTOR_ALWAYS_VALIDATE is False.
19992002
UsSigninJsonResponse:
2000-
type: object
2001-
description: >
2002-
The user successfully signed in. Note that depending on SECURITY_TWO_FACTOR and SECURITY_US_MFA_REQUIRED configuration variables, a second form of authentication might be required.
2003-
required: [meta, response]
2004-
properties:
2005-
meta:
2006-
type: object
2007-
required: [code]
2008-
properties:
2009-
code:
2010-
type: integer
2011-
example: 200
2012-
description: Http status code
2013-
response:
2014-
type: object
2003+
allOf:
2004+
- $ref: '#/components/schemas/BaseJsonResponse'
2005+
- type: object
2006+
description: >
2007+
The user successfully signed in. Note that depending on SECURITY_TWO_FACTOR and SECURITY_US_MFA_REQUIRED configuration variables, a second form of authentication might be required.
20152008
properties:
2016-
authentication_token:
2017-
type: string
2018-
description: >
2019-
Token to be used in future token-based API calls. Only returned if "include_auth_token" parameter is set.
2020-
tf_required:
2021-
type: boolean
2022-
description: If two-factor authentication is required for caller.
2023-
tf_state:
2024-
type: string
2025-
description: if "setup_from_login" then the caller must go through two-factor setup endpoint. If "ready" then a code has been sent and should be supplied to SECURITY_TWO_FACTOR_TOKEN_VALIDATION_URL.
2026-
tf_primary_method:
2027-
type: string
2028-
description: Which method was used to send code.
2009+
response:
2010+
type: object
2011+
properties:
2012+
authentication_token:
2013+
type: string
2014+
description: >
2015+
Token to be used in future token-based API calls. Only returned if "include_auth_token" parameter is set.
2016+
tf_required:
2017+
type: boolean
2018+
description: If two-factor authentication is required for caller.
2019+
tf_state:
2020+
type: string
2021+
description: if "setup_from_login" then the caller must go through two-factor setup endpoint. If "ready" then a code has been sent and should be supplied to SECURITY_TWO_FACTOR_TOKEN_VALIDATION_URL.
2022+
tf_primary_method:
2023+
type: string
2024+
description: Which method was used to send code.
20292025
UsSigninSendCode:
20302026
type: object
20312027
required: [identity, chosen_method]
@@ -2064,39 +2060,32 @@ components:
20642060
type: string
20652061
description: phone number (this will be normalized). Required if chosen_method == "sms".
20662062
UsSetupJsonResponse:
2067-
type: object
2068-
required: [meta, response]
2069-
properties:
2070-
meta:
2071-
type: object
2072-
required: [code]
2073-
properties:
2074-
code:
2075-
type: integer
2076-
example: 200
2077-
description: Http status code
2078-
response:
2079-
type: object
2080-
description: Response when setting up a new method. When deleting, nothing is returned.
2063+
allOf:
2064+
- $ref: '#/components/schemas/BaseJsonResponse'
2065+
- type: object
20812066
properties:
2082-
chosen_method:
2083-
type: string
2084-
description: The chosen_method as passed into API.
2085-
phone:
2086-
type: string
2087-
description: The canonicalized phone number if setting up SMS
2088-
authr_key:
2089-
type: string
2090-
description: TOTP key for setting up authenticator (if chosen_method == 'authenticator')
2091-
authr_issuer:
2092-
type: string
2093-
description: Issuer as configured with TOTP_ISSUER (same as used in QRcode) (if chosen_method == 'authenticator')
2094-
authr_username:
2095-
type: string
2096-
description: Username (same as used in QRcode) (if chosen_method == 'authenticator')
2097-
state:
2098-
type: string
2099-
description: Opaque blob that must be pass to /us-setup/<state>. This is a signed, timed token.
2067+
response:
2068+
type: object
2069+
description: Response when setting up a new method. When deleting, nothing is returned.
2070+
properties:
2071+
chosen_method:
2072+
type: string
2073+
description: The chosen_method as passed into API.
2074+
phone:
2075+
type: string
2076+
description: The canonicalized phone number if setting up SMS
2077+
authr_key:
2078+
type: string
2079+
description: TOTP key for setting up authenticator (if chosen_method == 'authenticator')
2080+
authr_issuer:
2081+
type: string
2082+
description: Issuer as configured with TOTP_ISSUER (same as used in QRcode) (if chosen_method == 'authenticator')
2083+
authr_username:
2084+
type: string
2085+
description: Username (same as used in QRcode) (if chosen_method == 'authenticator')
2086+
state:
2087+
type: string
2088+
description: Opaque blob that must be pass to /us-setup/<state>. This is a signed, timed token.
21002089
UsSetupValidateRequest:
21012090
type: object
21022091
required: [passcode]
@@ -2105,26 +2094,19 @@ components:
21052094
type: string
21062095
description: Code/Passcode as received from method being setup.
21072096
UsSetupValidateJsonResponse:
2108-
type: object
2109-
required: [meta, response]
2110-
properties:
2111-
meta:
2112-
type: object
2113-
required: [code]
2114-
properties:
2115-
code:
2116-
type: integer
2117-
example: 200
2118-
description: Http status code
2119-
response:
2120-
type: object
2097+
allOf:
2098+
- $ref: '#/components/schemas/BaseJsonResponse'
2099+
- type: object
21212100
properties:
2122-
chosen_method:
2123-
type: string
2124-
description: The chosen_method as passed into API.
2125-
phone:
2126-
type: string
2127-
description: Phone number if set.
2101+
response:
2102+
type: object
2103+
properties:
2104+
chosen_method:
2105+
type: string
2106+
description: The chosen_method as passed into API.
2107+
phone:
2108+
type: string
2109+
description: Phone number if set.
21282110
TfSetup:
21292111
type: object
21302112
required: [setup]
@@ -2140,39 +2122,32 @@ components:
21402122
description: phone number (this will be validated for format). Required if setup == "sms".
21412123
example: 650-555-1212
21422124
TfSetupJsonResponse:
2143-
type: object
2144-
required: [meta, response]
2145-
properties:
2146-
meta:
2147-
type: object
2148-
required: [code]
2149-
properties:
2150-
code:
2151-
type: integer
2152-
example: 200
2153-
description: Http status code
2154-
response:
2155-
type: object
2125+
allOf:
2126+
- $ref: '#/components/schemas/BaseJsonResponse'
2127+
- type: object
21562128
properties:
2157-
tf_state:
2158-
type: string
2159-
description: >
2160-
Current state of Two Factor configuration. Not present when disabling 2FA. This will be set to 'validating_profile'
2161-
indicating the caller needs to call '/tf-validate' with the correct code.
2162-
example: validating_profile
2163-
tf_primary_method:
2164-
type: string
2165-
description: Current method being congfigured.
2166-
example: sms
2167-
tf_authr_key:
2168-
type: string
2169-
description: TOTP key for setting up authenticator (if tf_primary_method == 'authenticator')
2170-
tf_authr_issuer:
2171-
type: string
2172-
description: Issuer as configured with TOTP_ISSUER (same as used in QRcode) (if tf_primary_method == 'authenticator')
2173-
tf_authr_username:
2174-
type: string
2175-
description: Username (same as used in QRcode) (if tf_primary_method == 'authenticator')
2129+
response:
2130+
type: object
2131+
properties:
2132+
tf_state:
2133+
type: string
2134+
description: >
2135+
Current state of Two Factor configuration. Not present when disabling 2FA. This will be set to 'validating_profile'
2136+
indicating the caller needs to call '/tf-validate' with the correct code.
2137+
example: validating_profile
2138+
tf_primary_method:
2139+
type: string
2140+
description: Current method being configured.
2141+
example: sms
2142+
tf_authr_key:
2143+
type: string
2144+
description: TOTP key for setting up authenticator (if tf_primary_method == 'authenticator')
2145+
tf_authr_issuer:
2146+
type: string
2147+
description: Issuer as configured with TOTP_ISSUER (same as used in QRcode) (if tf_primary_method == 'authenticator')
2148+
tf_authr_username:
2149+
type: string
2150+
description: Username (same as used in QRcode) (if tf_primary_method == 'authenticator')
21762151
TfValidateJsonResponse:
21772152
type: object
21782153
properties:

flask_security/tf_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
get_message,
3333
get_within_delta,
3434
get_url,
35-
json_error_response,
3635
login_user,
3736
simple_render_json,
3837
suppress_form_csrf,
@@ -327,7 +326,8 @@ def tf_illegal_state(form, redirect_to):
327326
do_flash(m, c)
328327
return redirect(get_url(redirect_to))
329328
else:
330-
return _security._render_json(json_error_response(m), 400, None, None)
329+
form.form_errors.append(m)
330+
return base_render_json(form, include_user=False)
331331

332332

333333
def tf_clean_session():

flask_security/unified_signin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,8 +942,8 @@ def us_setup_validate(token: str) -> "ResponseValue":
942942
m, c = get_message("US_SETUP_EXPIRED", within=cv("US_SETUP_WITHIN"))
943943
if invalid or expired:
944944
if _security._want_json(request):
945-
payload = json_error_response(errors=m)
946-
return _security._render_json(payload, 400, None, None)
945+
form.form_errors.append(m)
946+
return base_render_json(form, include_user=False)
947947
do_flash(m, c)
948948
return redirect(url_for_security("us_setup"))
949949

0 commit comments

Comments
 (0)