Skip to content

Commit

Permalink
[Issue 2648] Add signout endpoint (#2997)
Browse files Browse the repository at this point in the history
## Summary
Fixes #{[2648](#2648)}

### Time to review: __5 mins__

## Changes proposed.

Added `/token/logout` endpoint 
Added `LogoutResponseSchema` response schema
Added success and invalid token tests

---------

Co-authored-by: nava-platform-bot <[email protected]>
  • Loading branch information
babebe and nava-platform-bot authored Nov 22, 2024
1 parent 3fc12a4 commit 35fb5d6
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 0 deletions.
34 changes: 34 additions & 0 deletions api/openapi.generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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: []
Expand Down Expand Up @@ -645,6 +666,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:
Expand Down
29 changes: 29 additions & 0 deletions api/src/api/users/user_routes.py
Original file line number Diff line number Diff line change
@@ -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 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

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -34,3 +39,27 @@ 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(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.current_user # type: ignore
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.token_id": str(user_token_session.token_id),
"user_token_session.user_id": str(user_token_session.user_id),
},
)

return response.ApiResponse(message="Success")
5 changes: 5 additions & 0 deletions api/src/api/users/user_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ class UserTokenSchema(Schema):

class UserTokenResponseSchema(AbstractResponseSchema):
data = fields.Nested(UserTokenSchema)


class UserTokenLogoutResponseSchema(AbstractResponseSchema):
# No data returned
data = fields.MixinField(metadata={"example": None})
33 changes: 33 additions & 0 deletions api/tests/src/api/users/test_user_route_token.py
Original file line number Diff line number Diff line change
@@ -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
##################
Expand Down Expand Up @@ -25,3 +28,33 @@ 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, 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 not user_token_session.is_valid


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"

0 comments on commit 35fb5d6

Please sign in to comment.