Skip to content

Commit

Permalink
Fix tf-validate returning authorization_token.
Browse files Browse the repository at this point in the history
Lots of fixes to openapi.yaml.

closes #861
  • Loading branch information
jwag956 committed Oct 22, 2023
1 parent 346a41c commit 90bbf5b
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 114 deletions.
5 changes: 3 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Released xxx
Fixes
++++++

- (:issue:`859`) Update Quickstart to show how to properly handle SQLAlchemy connections
- (:issue:`859`) Update Quickstart to show how to properly handle SQLAlchemy connections.
- (:issue:`861`) Auth Token not returned from /tf-validate. (thanks lilz-egoto)

Version 5.3.1
-------------
Expand Down Expand Up @@ -68,7 +69,7 @@ Backwards Compatibility Concerns
- To align with the W3C WebAuthn Level2 and 3 spec - transports are now part of the registration response.
This has been changed BOTH in the server code (using webauthn data structures) as well as the sample
javascript code. If an application has their own javascript front end code - it might need to be changed.
- The tf_validity feature :py:data`SECURITY_TWO_FACTOR_ALWAYS_VALIDATE` used to set a cookie if the request was
- The tf_validity feature :py:data:`SECURITY_TWO_FACTOR_ALWAYS_VALIDATE` used to set a cookie if the request was
form based, and return the token as part of a JSON response. Now, this feature is ONLY cookie based and the token
is no longer returned as part of any response.
- Reset password was changed to adhere to OWASP recommendations and reduce possible exploitation:
Expand Down
237 changes: 126 additions & 111 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,13 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/LoginJsonResponse"
allOf:
- description: >
The user successfully signed in using their primary credential.
Note that depending on SECURITY_TWO_FACTOR configuration variable, a second form of authentication might be required prior to the user being fully authenticated.
`tf_required` will be set to True in this case.
Note that if 2FA is not configured, none of the ``tf_`` properties will be returned.
- $ref: "#/components/schemas/LoginJsonResponse"
text/html:
schema:
description: Unsuccessful login
Expand Down Expand Up @@ -655,7 +661,12 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/UsSigninJsonResponse"
allOf:
- description: >
The user successfully signed in using their primary credentials.
Note that depending on the SECURITY_TWO_FACTOR and SECURITY_US_MFA_REQUIRED configuration variables, a second form of authentication might be required prior to the user being fully authenticated.
`tf_required` will be set to True in this case.
- $ref: "#/components/schemas/LoginJsonResponse"
text/html:
schema:
description: Unsuccessful sign in
Expand Down Expand Up @@ -1093,6 +1104,8 @@ paths:
type: string
post:
summary: Send two-factor code.
parameters:
- $ref: "#/components/parameters/include_auth_token"
requestBody:
required: true
content:
Expand Down Expand Up @@ -1121,7 +1134,7 @@ paths:
allOf:
- description: >
The code was correct, the caller is now signed in.
- $ref: "#/components/schemas/TfValidateJsonResponse"
- $ref: "#/components/schemas/JsonResponseWithToken"
text/html:
schema:
description:
Expand Down Expand Up @@ -1210,10 +1223,12 @@ paths:
authentication, this view will ask the user to choose.
responses:
200:
description: Two-factor method select form
description: Two-factor method select form - SECURITY_TWO_FACTOR_SELECT_TEMPLATE
content:
text/html:
schema:
description: Select Form (SECURITY_TWO_FACTOR_SELECT_TEMPLATE)
type: string
example: render_template(SECURITY_TWO_FACTOR_SELECT_TEMPLATE)
application/json:
schema:
Expand Down Expand Up @@ -1243,33 +1258,31 @@ paths:
properties:
which:
type: string
description: <
description: >
Which two-factor method to use.
example: "sms"
application/x-www-form-urlencoded:
schema:
description: Select Form
type: string
example: render_template(SECURITY_TWO_FACTOR_SELECT_TEMPLATE)
responses:
200:
description: Verify/re-authenticate response.
description: Second factor select response.
content:
application/json:
schema:
allOf:
- description: >
The user successfully re-authenticated.
- $ref: "#/components/schemas/JsonResponseWithToken"
$ref: "#/components/schemas/TfSelectJsonResponse"
text/html:
schema:
description: Unsuccessful re-authentication.
description: Form validation failure.
type: string
example: render_template(SECURITY_TWO_FACTOR_SELECT_TEMPLATE) with error values
302:
description: User successfully re-authenticated when using form based request.
description: User selected which two factor to use when using form based request.
headers:
Location:
description: Redirect to ``next`` or ``SECURITY_POST_VERIFY_VIEW``
description: Redirect to ``SECURITY_TWO_FACTOR_TOKEN_VALIDATION_URL`` or ``SECURITY_WAN_SIGNIN_URL``.
schema:
type: string
400:
Expand Down Expand Up @@ -1820,46 +1833,33 @@ components:
If true, will remember userid as part of cookie. There is a configuration variable DEFAULT_REMEMBER_ME that can be set. This field will override that.
LoginJsonResponse:
type: object
description: >
The user successfully signed in. Note that depending on SECURITY_TWO_FACTOR configuration variables, a second form of authentication might be required.
Note that if 2FA is not configured, none of the ``tf_`` properties will be returned.
required: [meta, response]
properties:
meta:
type: object
required: [code]
properties:
code:
type: integer
example: 200
description: Http status code
response:
type: object
allOf:
- $ref: '#/components/schemas/JsonResponseWithToken'
- type: object
properties:
authentication_token:
type: string
description: >
Token to be used in future token-based API calls. Only returned if "include_auth_token" parameter is set.
tf_required:
type: boolean
description: If two-factor authentication is required for caller.
tf_state:
type: string
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.
tf_method:
type: string
description: Which method was used to send code.
example: "webauthn"
tf_select:
type: boolean
description: <
If user has setup multiple forms of two-factor authentication, this will be True
and the application should prompt the user for which method they want to use.
tf_setup_methods:
type: array
items:
type: string
description: If user has setup multiple forms of two-factor authentication they are listed
response:
type: object
properties:
tf_required:
type: boolean
description: True if two-factor authentication is required for caller.
tf_state:
type: string
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.
tf_method:
type: string
description: Which method was used to send code.
example: "webauthn"
tf_select:
type: boolean
description: >
If user has setup multiple forms of two-factor authentication, this will be True
and the application should prompt the user for which method they want to use.
tf_setup_methods:
type: array
items:
type: string
description: If user has setup multiple forms of two-factor authentication, they are listed

BaseJsonResponse:
type: object
Expand Down Expand Up @@ -1922,35 +1922,42 @@ components:
type: string
description: Session CSRF token
DefaultJsonErrorResponse:
allOf:
- $ref: '#/components/schemas/BaseJsonResponse'
- type: object
type: object
required: [ meta, response ]
properties:
meta:
type: object
required: [ code ]
properties:
response:
code:
type: integer
example: 400
description: Http status code
response:
type: object
required: [ errors ]
description: >
For form validation errors, the 'field_errors' key will be set with a list of errors per
invalid form input field (i.e. a dict of 'field-name': list of error strings).
The 'errors' key will be a simple list of both form and non-form related
errors (all form errors will also be included here).
properties:
field_errors:
type: object
required: [ errors ]
description: >
For form validation errors, the 'field_errors' key will be set with a list of errors per
invalid form input field (i.e. a dict of 'field-name': list of error strings).
The 'errors' key will be a simple list of both form and non-form related
errors (all form errors will also be included here).
properties:
field_errors:
type: object
description: >
Errors per input/form field
additionalProperties:
type: array
items:
type: string
example: field validation error.
description: Error message (localized)
errors:
type: array
items:
type: string
example: "Unauthenticated"
description: Error message (localized)
Errors per input/form field
additionalProperties:
type: array
items:
type: string
example: field validation error.
description: Error message (localized)
errors:
type: array
items:
type: string
example: "Unauthenticated"
description: Error message (localized)
Verify:
type: object
required: [ password ]
Expand Down Expand Up @@ -2034,29 +2041,6 @@ components:
description: password or code
remember:
type: boolean
UsSigninJsonResponse:
allOf:
- $ref: '#/components/schemas/BaseJsonResponse'
- type: object
description: >
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.
properties:
response:
type: object
properties:
authentication_token:
type: string
description: >
Token to be used in future token-based API calls. Only returned if "include_auth_token" parameter is set.
tf_required:
type: boolean
description: If two-factor authentication is required for caller.
tf_state:
type: string
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.
tf_primary_method:
type: string
description: Which method was used to send code.
UsSigninSendCode:
type: object
required: [identity, chosen_method]
Expand Down Expand Up @@ -2183,16 +2167,47 @@ components:
tf_authr_username:
type: string
description: Username (same as used in QRcode) (if tf_primary_method == 'authenticator')
TfValidateJsonResponse:
type: object
properties:
user:
type: object
description: >
By default an empty dictionary is returned. However by overriding _User::get_security_payload()_ any attributes of the User model can be returned.
csrf_token:
type: string
description: Session CSRF token
TfSelectJsonResponse:
allOf:
- $ref: '#/components/schemas/BaseJsonResponse'
- oneOf:
- type: object
description: >
The user requires two-factor authorization and has chosen which one to use.
properties:
response:
type: object
properties:
tf_required:
type: boolean
description: Will be True since a second factor is required.
tf_state:
type: string
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.
tf_method:
type: string
description: Which method was used to send code/link.
example: "sms"
- type: object
description: >
The user requires two-factor authorization and has chosen to use `webauthn`.
properties:
response:
type: object
properties:
tf_required:
type: boolean
description: Will be True since a second factor is required.
tf_state:
type: string
description: This will be set to `ready`.
tf_method:
type: string
description: This will be set to `webauthn`.
tf_signin_url:
type: string
description: The value of SECURITY_WAN_SIGNIN_URL

WanRegister:
type: object
required: [ name, usage ]
Expand Down
2 changes: 1 addition & 1 deletion flask_security/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ def two_factor_token_validation():
return redirect(get_post_login_redirect())

else:
return base_render_json(form)
return base_render_json(form, include_auth_token=True)

# GET or not successful POST

Expand Down
26 changes: 26 additions & 0 deletions tests/test_two_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,32 @@ def test_rescue_json(app, client):
assert "[email protected]" in outbox[1].body


@pytest.mark.settings(two_factor_required=True)
def test_json_auth_token(app, client):
"""
Test getting auth_token with two-factor
"""
headers = {"Accept": "application/json", "Content-Type": "application/json"}

# Login with someone already setup.
sms_sender = SmsSenderFactory.createSender("test")
data = dict(email="[email protected]", password="password")
response = client.post("/login", json=data, headers=headers)
assert response.status_code == 200
assert response.json["response"]["tf_required"]

# Verify SMS sent
assert sms_sender.get_count() == 1
code = sms_sender.messages[0].split()[-1]
response = client.post("/tf-validate?include_auth_token", json=dict(code=code))
assert response.status_code == 200
token = response.json["response"]["user"]["authentication_token"]
headers = {"Authentication-Token": token}
# make sure can access restricted page
response = client.get("/token", headers=headers)
assert b"Token Authentication" in response.data


@pytest.mark.settings(two_factor_required=True)
def test_no_opt_out(app, client, get_message):
# Test if 2FA required, can't opt-out.
Expand Down

0 comments on commit 90bbf5b

Please sign in to comment.