From 641f388145881c8f03cfa48c4b3643f8c103e592 Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:04:57 -0500 Subject: [PATCH 1/9] add user_token_logout endpoint --- api/src/api/users/user_routes.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/src/api/users/user_routes.py b/api/src/api/users/user_routes.py index 1c34f0b5a..fcf112188 100644 --- a/api/src/api/users/user_routes.py +++ b/api/src/api/users/user_routes.py @@ -1,10 +1,15 @@ import logging +from src.adapters import db +from src.adapters.db import flask_db from src.api import response from src.api.route_utils import raise_flask_error from src.api.users import user_schemas from src.api.users.user_blueprint import user_blueprint +from src.api.users.user_schemas import LogoutResponseSchema +from src.auth.api_jwt_auth import api_jwt_auth from src.auth.api_key_auth import api_key_auth +from src.db.models.user_models import UserTokenSession logger = logging.getLogger(__name__) @@ -34,3 +39,26 @@ def user_token(x_oauth_login_gov: dict) -> response.ApiResponse: logger.info(message) raise_flask_error(400, message) + + +@user_blueprint.post("/token/logout") +@user_blueprint.output(LogoutResponseSchema) +@user_blueprint.doc(responses=[200, 401]) +@user_blueprint.auth_required(api_jwt_auth) +@flask_db.with_db_session() +def user_token_logout(db_session: db.Session) -> response.ApiResponse: + logger.info("POST /v1/users/token/logout") + + user_token_session: UserTokenSession = api_jwt_auth.get("current_user") + with db_session.begin(): + user_token_session.is_valid = False + db_session.add(user_token_session) + + logger.info( + "Logged out a user", + extra={ + "user_token_session.user_id": str(user_token_session.user_id), + }, + ) + + return response.ApiResponse(message="Success") From 44809155036fac6e073097262a68baeda2e479b7 Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:05:14 -0500 Subject: [PATCH 2/9] add response schema --- api/src/api/users/user_schemas.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/api/users/user_schemas.py b/api/src/api/users/user_schemas.py index 3ea05421f..ad545cf86 100644 --- a/api/src/api/users/user_schemas.py +++ b/api/src/api/users/user_schemas.py @@ -51,3 +51,8 @@ class UserTokenSchema(Schema): class UserTokenResponseSchema(AbstractResponseSchema): data = fields.Nested(UserTokenSchema) + + +class LogoutResponseSchema(AbstractResponseSchema): + # No data returned + data = fields.MixinField(metadata={"example": None}) From ee81a009de25b936a42f744b1483d4966bd33fdd Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:05:34 -0500 Subject: [PATCH 3/9] add 200 and 401 invalid token error --- .../src/api/users/test_user_route_token.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/api/tests/src/api/users/test_user_route_token.py b/api/tests/src/api/users/test_user_route_token.py index d0811de94..6e4a1576a 100644 --- a/api/tests/src/api/users/test_user_route_token.py +++ b/api/tests/src/api/users/test_user_route_token.py @@ -1,3 +1,6 @@ +from src.auth.api_jwt_auth import create_jwt_for_user +from tests.src.db.models.factories import UserFactory + ################## # POST /token ################## @@ -25,3 +28,30 @@ def test_post_user_route_token_400(client, api_auth_token): resp = client.post("v1/users/token", headers={"X-Auth": api_auth_token}) assert resp.status_code == 400 assert resp.get_json()["message"] == "Missing X-OAuth-login-gov header" + + +def test_post_user_route_token_logout_200( + enable_factory_create, client, db_session, api_auth_token +): + user = UserFactory.create() + token, _ = create_jwt_for_user(user, db_session) + db_session.commit() + + resp = client.post("v1/users/token/logout", headers={"X-SGG-Token": token}) + + assert resp.status_code == 200 + + +def test_post_user_route_token_logout_invalid( + enable_factory_create, client, db_session, api_auth_token +): + user = UserFactory.create() + + token, session = create_jwt_for_user(user, db_session) + session.is_valid = False + db_session.commit() + + resp = client.post("v1/users/token/logout", headers={"X-SGG-Token": token}) + + assert resp.status_code == 401 + assert resp.get_json()["message"] == "Token is no longer valid" From b2a9ae1c985d60535c1741308bca9cd9b5d2b1e4 Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:15:47 -0500 Subject: [PATCH 4/9] rename response schema --- api/src/api/users/user_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/api/users/user_schemas.py b/api/src/api/users/user_schemas.py index ad545cf86..b645ce092 100644 --- a/api/src/api/users/user_schemas.py +++ b/api/src/api/users/user_schemas.py @@ -53,6 +53,6 @@ class UserTokenResponseSchema(AbstractResponseSchema): data = fields.Nested(UserTokenSchema) -class LogoutResponseSchema(AbstractResponseSchema): +class UserTokenLogoutResponseSchema(AbstractResponseSchema): # No data returned data = fields.MixinField(metadata={"example": None}) From 28fc58613f5fac90a59deb0d09034a04c09257e0 Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:16:18 -0500 Subject: [PATCH 5/9] linter ignore type check on api_jwt_auth.current_user --- api/src/api/users/user_routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/api/users/user_routes.py b/api/src/api/users/user_routes.py index fcf112188..1223149e4 100644 --- a/api/src/api/users/user_routes.py +++ b/api/src/api/users/user_routes.py @@ -6,7 +6,7 @@ from src.api.route_utils import raise_flask_error from src.api.users import user_schemas from src.api.users.user_blueprint import user_blueprint -from src.api.users.user_schemas import LogoutResponseSchema +from src.api.users.user_schemas import UserTokenLogoutResponseSchema from src.auth.api_jwt_auth import api_jwt_auth from src.auth.api_key_auth import api_key_auth from src.db.models.user_models import UserTokenSession @@ -42,14 +42,14 @@ def user_token(x_oauth_login_gov: dict) -> response.ApiResponse: @user_blueprint.post("/token/logout") -@user_blueprint.output(LogoutResponseSchema) +@user_blueprint.output(UserTokenLogoutResponseSchema) @user_blueprint.doc(responses=[200, 401]) @user_blueprint.auth_required(api_jwt_auth) @flask_db.with_db_session() def user_token_logout(db_session: db.Session) -> response.ApiResponse: logger.info("POST /v1/users/token/logout") - user_token_session: UserTokenSession = api_jwt_auth.get("current_user") + user_token_session: UserTokenSession = api_jwt_auth.current_user # type: ignore with db_session.begin(): user_token_session.is_valid = False db_session.add(user_token_session) From 75b66ef2425526dfcd6cc35a4e61380b4791ef8c Mon Sep 17 00:00:00 2001 From: nava-platform-bot Date: Fri, 22 Nov 2024 19:18:40 +0000 Subject: [PATCH 6/9] Create ERD diagram and Update OpenAPI spec --- api/openapi.generated.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/api/openapi.generated.yml b/api/openapi.generated.yml index 31c07dac3..e97b0e181 100644 --- a/api/openapi.generated.yml +++ b/api/openapi.generated.yml @@ -77,6 +77,27 @@ paths: summary: User Token security: - ApiKeyAuth: [] + /v1/users/token/logout: + post: + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserTokenLogoutResponse' + description: Successful response + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: Authentication error + tags: + - User v1 + summary: User Token Logout + security: + - ApiJwtAuth: [] /v1/opportunities/search: post: parameters: [] @@ -433,6 +454,19 @@ components: type: integer description: The HTTP status code example: 200 + UserTokenLogoutResponse: + type: object + properties: + message: + type: string + description: The message to return + example: Success + data: + example: null + status_code: + type: integer + description: The HTTP status code + example: 200 FundingInstrumentFilterV1: type: object properties: From ba156e247c81ff2813e7af4cf000b7796f1538af Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:55:29 -0500 Subject: [PATCH 7/9] assert user_token_session is false --- api/tests/src/api/users/test_user_route_token.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/tests/src/api/users/test_user_route_token.py b/api/tests/src/api/users/test_user_route_token.py index 6e4a1576a..9e448faef 100644 --- a/api/tests/src/api/users/test_user_route_token.py +++ b/api/tests/src/api/users/test_user_route_token.py @@ -34,12 +34,15 @@ def test_post_user_route_token_logout_200( enable_factory_create, client, db_session, api_auth_token ): user = UserFactory.create() - token, _ = create_jwt_for_user(user, db_session) + token, user_token_session = create_jwt_for_user(user, db_session) db_session.commit() resp = client.post("v1/users/token/logout", headers={"X-SGG-Token": token}) + db_session.refresh(user_token_session) + assert resp.status_code == 200 + assert user_token_session.is_valid == False def test_post_user_route_token_logout_invalid( From d54f60561dad014009524996953ae63a08acbcb3 Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 14:56:55 -0500 Subject: [PATCH 8/9] add token_id to log --- api/src/api/users/user_routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/api/users/user_routes.py b/api/src/api/users/user_routes.py index 1223149e4..d2388e9fe 100644 --- a/api/src/api/users/user_routes.py +++ b/api/src/api/users/user_routes.py @@ -57,7 +57,8 @@ def user_token_logout(db_session: db.Session) -> response.ApiResponse: logger.info( "Logged out a user", extra={ - "user_token_session.user_id": str(user_token_session.user_id), + "user_token_session.token_id": str(user_token_session.token_id), + "user_token_session.user_id": str(user_token_session.token), }, ) From 4513261770a87e603b7e0ac2663f47582bd09395 Mon Sep 17 00:00:00 2001 From: bruk Date: Fri, 22 Nov 2024 15:01:11 -0500 Subject: [PATCH 9/9] clean up --- api/src/api/users/user_routes.py | 2 +- api/tests/src/api/users/test_user_route_token.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/api/users/user_routes.py b/api/src/api/users/user_routes.py index d2388e9fe..4ad189a3a 100644 --- a/api/src/api/users/user_routes.py +++ b/api/src/api/users/user_routes.py @@ -58,7 +58,7 @@ def user_token_logout(db_session: db.Session) -> response.ApiResponse: "Logged out a user", extra={ "user_token_session.token_id": str(user_token_session.token_id), - "user_token_session.user_id": str(user_token_session.token), + "user_token_session.user_id": str(user_token_session.user_id), }, ) diff --git a/api/tests/src/api/users/test_user_route_token.py b/api/tests/src/api/users/test_user_route_token.py index 9e448faef..f309afbec 100644 --- a/api/tests/src/api/users/test_user_route_token.py +++ b/api/tests/src/api/users/test_user_route_token.py @@ -42,7 +42,7 @@ def test_post_user_route_token_logout_200( db_session.refresh(user_token_session) assert resp.status_code == 200 - assert user_token_session.is_valid == False + assert not user_token_session.is_valid def test_post_user_route_token_logout_invalid(