Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue 2673] Users token stub endpoint #2885

Merged
merged 47 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8d7486e
add Schema for User/Token endpoint
babebe Nov 15, 2024
136dd01
add new user/token endpoint with static response
babebe Nov 15, 2024
50b2b75
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 15, 2024
ec38d8f
fix description
babebe Nov 15, 2024
d4468d0
merge main
babebe Nov 15, 2024
ac57712
Merge branch '2673/users-token-sub-endpoint' of https://github.com/HH…
babebe Nov 15, 2024
6cc4c37
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 15, 2024
f32b05a
user api blueprint
babebe Nov 18, 2024
352a79a
user routes with user/token endpoint
babebe Nov 18, 2024
e90139d
moved user endpoint
babebe Nov 18, 2024
ef9861d
moved user schemas
babebe Nov 18, 2024
c501111
user schemas
babebe Nov 18, 2024
6e83042
register user routes on blueprint
babebe Nov 18, 2024
db810fd
Merge branch '2673/users-token-sub-endpoint' of https://github.com/HH…
babebe Nov 18, 2024
cd15718
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Nov 18, 2024
98aabd4
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 18, 2024
9ef31c0
rm versioning
babebe Nov 18, 2024
0b3e402
Merge branch '2673/users-token-sub-endpoint' of https://github.com/HH…
babebe Nov 18, 2024
625f366
fix import
babebe Nov 18, 2024
a95f46b
register user routes blueprint
babebe Nov 18, 2024
1f03377
fix naming
babebe Nov 18, 2024
528dfd1
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 18, 2024
4594b26
define param for swagger
babebe Nov 19, 2024
15d14b8
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 19, 2024
34624a2
unit test user/route
babebe Nov 19, 2024
4c90bfb
use SHARED_ALPHA_DESCRIPTION
babebe Nov 19, 2024
240614b
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 19, 2024
db30992
set auth_endpoint env variable
babebe Nov 20, 2024
4e45c18
validate response data
babebe Nov 20, 2024
45b249d
add header schema
babebe Nov 20, 2024
e3292ad
add blueprint header
babebe Nov 20, 2024
c0c643d
lint/format
babebe Nov 20, 2024
3fc08bf
Merge branch '2673/users-token-sub-endpoint' of https://github.com/HH…
babebe Nov 20, 2024
4f34b86
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Nov 20, 2024
dee8882
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 20, 2024
fda2bfb
Set auth_endpoint env var
babebe Nov 20, 2024
d807154
Merge branch '2673/users-token-sub-endpoint' of https://github.com/HH…
babebe Nov 20, 2024
5c8320f
update AuthEndpointConfig
babebe Nov 20, 2024
6a1dd4d
add ENABLE_AUTH_ENDPOINT env var
babebe Nov 20, 2024
aceb260
update endpoint route
babebe Nov 21, 2024
d3bf43b
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 21, 2024
3f5934f
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Nov 21, 2024
b81c3fe
Merge branch '2673/users-token-sub-endpoint' of https://github.com/HH…
babebe Nov 21, 2024
e623330
fix comment
babebe Nov 21, 2024
2b25ab7
rm docstring and response type
babebe Nov 21, 2024
4b35871
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Nov 21, 2024
0433f6e
cleanup
babebe Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions api/openapi.generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ info:
tags:
- name: Health
- name: Opportunity v1
- name: User v1
servers: .
paths:
/health:
Expand All @@ -43,6 +44,61 @@ paths:
tags:
- Health
summary: Health
/v1/users/user/token:
post:
parameters:
- in: header
name: X-OAuth-login-gov
description: The login_gov header token
schema:
type: string
required: false
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/UserTokenResponse'
description: Successful response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Authentication error
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Bad Request
tags:
- User v1
summary: User Token
description: '

__ALPHA VERSION__


This endpoint in its current form is primarily for testing and feedback.


Features in this endpoint are still under heavy development, and subject to
change. Not for production use.


See [Release Phases](https://github.com/github/roadmap?tab=readme-ov-file#release-phases)
for further details.

'
security:
- ApiKeyAuth: []
/v1/opportunities/search:
post:
parameters: []
Expand Down Expand Up @@ -350,6 +406,55 @@ components:
type: string
description: An internal tracking ID
example: 550e8400-e29b-41d4-a716-446655440000
User:
type: object
properties:
user_id:
type: string
description: The internal ID of a user
example: 861a0148-cf2c-432b-b0b3-690016299ab1
email:
type: string
description: The email address returned from Oauth2 provider
example: [email protected]
external_user_type:
description: The Oauth2 provider through which a user was authenticated
example: !!python/object/apply:src.constants.lookup_constants.ExternalUserType
- login_gov
enum:
- login_gov
type:
- string
UserToken:
type: object
properties:
token:
type: string
description: Internal token generated for a user
user:
type:
- object
allOf:
- $ref: '#/components/schemas/User'
is_user_new:
type: boolean
description: Whether or not the user existed in our database
UserTokenResponse:
type: object
properties:
message:
type: string
description: The message to return
example: Success
data:
type:
- object
allOf:
- $ref: '#/components/schemas/UserToken'
status_code:
type: integer
description: The HTTP status code
example: 200
FundingInstrumentFilterV1:
type: object
properties:
Expand Down
6 changes: 6 additions & 0 deletions api/src/api/users/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from src.api.users.user_blueprint import user_blueprint

# import user_routes module to register the API routes on the blueprint
import src.api.users.user_routes # noqa: F401 E402 isort:skip

__all__ = ["user_blueprint"]
9 changes: 9 additions & 0 deletions api/src/api/users/user_blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from apiflask import APIBlueprint

user_blueprint = APIBlueprint(
"user_v1",
__name__,
tag="User v1",
cli_group="user_v1",
url_prefix="/v1/users",
)
52 changes: 52 additions & 0 deletions api/src/api/users/user_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import logging

from flask import Response

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.auth.api_key_auth import api_key_auth

logger = logging.getLogger(__name__)

# Descriptions in OpenAPI support markdown https://swagger.io/specification/
SHARED_ALPHA_DESCRIPTION = """
__ALPHA VERSION__

This endpoint in its current form is primarily for testing and feedback.

Features in this endpoint are still under heavy development, and subject to change. Not for production use.

See [Release Phases](https://github.com/github/roadmap?tab=readme-ov-file#release-phases) for further details.
"""

babebe marked this conversation as resolved.
Show resolved Hide resolved

@user_blueprint.post("/user/token")
babebe marked this conversation as resolved.
Show resolved Hide resolved
@user_blueprint.input(
user_schemas.UserTokenHeaderSchema, location="headers", arg_name="x_oauth_login_gov"
)
@user_blueprint.output(user_schemas.UserTokenResponseSchema)
@user_blueprint.auth_required(api_key_auth)
@user_blueprint.doc( # should we include this?
description=SHARED_ALPHA_DESCRIPTION, responses=[200, 400]
)
babebe marked this conversation as resolved.
Show resolved Hide resolved
def user_token(x_oauth_login_gov: str) -> response.ApiResponse | Response:
babebe marked this conversation as resolved.
Show resolved Hide resolved
logger.info("POST /v1/users/user/token")

if x_oauth_login_gov:
data = {
"token": "the token goes here!",
"user": {
"user_id": "abc-...",
"email": "[email protected]",
"external_user_type": "login_gov",
},
"is_user_new": True,
}
return response.ApiResponse(message="Success", data=data)

message = "Missing X-OAuth-login-gov header"
logger.error(message)
babebe marked this conversation as resolved.
Show resolved Hide resolved

raise_flask_error(400, message)
53 changes: 53 additions & 0 deletions api/src/api/users/user_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from src.api.schemas.extension import Schema, fields
from src.api.schemas.response_schema import AbstractResponseSchema
from src.constants.lookup_constants import ExternalUserType


class UserTokenHeaderSchema(Schema):
x_oauth_login_gov = fields.String(
data_key="X-OAuth-login-gov",
metadata={
"description": "The login_gov header token",
},
)


class UserSchema(Schema):
user_id = fields.String(
metadata={
"description": "The internal ID of a user",
"example": "861a0148-cf2c-432b-b0b3-690016299ab1",
}
)
email = fields.String(
metadata={
"description": "The email address returned from Oauth2 provider",
"example": "[email protected]",
}
)
external_user_type = fields.Enum(
ExternalUserType,
metadata={
"description": "The Oauth2 provider through which a user was authenticated",
"example": ExternalUserType.LOGIN_GOV,
},
)


class UserTokenSchema(Schema):
token = fields.String(
metadata={
"description": "Internal token generated for a user",
}
)
user = fields.Nested(UserSchema())
is_user_new = fields.Boolean(
allow_none=False,
metadata={
"description": "Whether or not the user existed in our database",
},
)


class UserTokenResponseSchema(AbstractResponseSchema):
data = fields.Nested(UserTokenSchema)
11 changes: 11 additions & 0 deletions api/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
from src.api.opportunities_v1 import opportunity_blueprint as opportunities_v1_blueprint
from src.api.response import restructure_error_response
from src.api.schemas import response_schema
from src.api.users.user_blueprint import user_blueprint
from src.app_config import AppConfig
from src.auth.api_key_auth import get_app_security_scheme
from src.data_migration.data_migration_blueprint import data_migration_blueprint
from src.search.backend.load_search_data_blueprint import load_search_data_blueprint
from src.task import task_blueprint
from src.util.env_config import PydanticBaseEnvConfig

logger = logging.getLogger(__name__)

Expand All @@ -37,6 +39,13 @@
"""


class AuthEndpointConfig(PydanticBaseEnvConfig):
auth_endpoint: bool = True if os.getenv("ENVIRONMENT", "local") == "local" else False

babebe marked this conversation as resolved.
Show resolved Hide resolved

auth_endpoint_config = AuthEndpointConfig()
babebe marked this conversation as resolved.
Show resolved Hide resolved


def create_app() -> APIFlask:
app = APIFlask(__name__, title=TITLE, version=API_OVERALL_VERSION)

Expand Down Expand Up @@ -118,6 +127,8 @@ def register_blueprints(app: APIFlask) -> None:
app.register_blueprint(opportunities_v0_blueprint)
app.register_blueprint(opportunities_v0_1_blueprint)
app.register_blueprint(opportunities_v1_blueprint)
if auth_endpoint_config.auth_endpoint:
app.register_blueprint(user_blueprint)

# Non-api blueprints
app.register_blueprint(data_migration_blueprint)
Expand Down
Empty file.
27 changes: 27 additions & 0 deletions api/tests/src/api/users/test_user_route_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
##################
# POST /user/token
##################


def test_post_user_route_token_200(client, api_auth_token):
resp = client.post(
"/v1/users/user/token", headers={"X-Auth": api_auth_token, "X-OAuth-login-gov": "test"}
)
response_data = resp.get_json()["data"]
expected_response_data = {
"token": "the token goes here!",
"user": {
"user_id": "abc-...",
"email": "[email protected]",
"external_user_type": "login_gov",
},
"is_user_new": True,
}
assert resp.status_code == 200
assert response_data == expected_response_data


def test_post_user_route_token_400(client, api_auth_token):
resp = client.post("v1/users/user/token", headers={"X-Auth": api_auth_token})
assert resp.status_code == 400
assert resp.get_json()["message"] == "Missing X-OAuth-login-gov header"