diff --git a/backend/README.md b/backend/README.md index 90567d47..4cde0d5f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -62,6 +62,49 @@ To run tests, run the following command from the root of the project directory: pytest ``` +## Local Development +During local development, you may need to generate and use a dummy JWT to +test protected endpoints of the application. + +# Generating a Dummy JWT + +FastAPI provides a convenient way to generate a dummy JWT for testing. +To generate a dummy JWT, follow the steps below: + +Make sure your application is running in a local environment. +The dummy token endpoint is only available in a local environment. + +While your application is running, visit the /dummy-token endpoint using a tool +like curl or any HTTP client like Postman. + +For instance, if your server is running locally on port 8000, you can use curl +to get a dummy token: + +```commandline +curl http://localhost:8000/dummy-token +``` + +You will receive a response containing the dummy JWT + +```json +{ + "dummy_token": "" +} +``` + +### Using the Dummy JWT +Once you've obtained a dummy JWT, you can use it to make requests to +protected endpoints in your application: + +1. When making a request, include an Authorization header with the value Bearer +. Replace with the token you +received from the /dummy-token endpoint. + +2. Now you can make requests to the protected endpoints of the application. + +Remember, the dummy JWT is meant for testing purposes only and should not be +used in production environments. The /dummy-token endpoint is not available +in non-local environments. ### Thoughts for authenticating the frontend with the backend To provide secure communication between your frontend Next.js application and your backend FastAPI service, you have several options. Here are a few popular approaches: diff --git a/backend/app/dependencies.py b/backend/app/dependencies.py index 68a5af09..92b63a32 100644 --- a/backend/app/dependencies.py +++ b/backend/app/dependencies.py @@ -19,11 +19,14 @@ async def validate_api_key(api_key_header: str = Depends(api_key_header)): def get_user(user_id: str): # Define here how to fetch a user from your database # using the user_id. Here's a simple placeholder implementation: - # TODO: This is a placeholder implementation that needs to be fully tested with the front end - user = None - if user_id == "known_id": - user = {"id": user_id, "name": "Known User"} - return user + # TODO: Update this function to fetch a user from your actual database + if get_settings().ENVIRONMENT == "local": + return {"id": user_id, "name": "Dummy User"} + else: + user = None + if user_id == "known_id": + user = {"id": user_id, "name": "Known User"} + return user def validate_jwt_token(token: str = Depends(oauth2_scheme)): @@ -33,8 +36,7 @@ def validate_jwt_token(token: str = Depends(oauth2_scheme)): headers={"WWW-Authenticate": "Bearer"}, ) try: - # TODO: This is a placeholder implementation that needs to be fully tested with the front end - # the SECRET_KEY should match the NEXTAUTH_SECRET in the front end + # The SECRET_KEY should match the NEXTAUTH_SECRET in the front end payload = jwt.decode(token, get_settings().SECRET_KEY, algorithms=[get_settings().ALGORITHM]) user_id: str = payload.get("sub") if user_id is None: @@ -48,10 +50,10 @@ def validate_jwt_token(token: str = Depends(oauth2_scheme)): async def validate_token(token: str = Depends(oauth2_scheme)): - if get_settings().ENVIRONMENT != "local": - token_data = validate_jwt_token(token) - if not token_data: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials" - ) + token_data = validate_jwt_token(token) + if not token_data: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials" + ) return token + diff --git a/backend/app/local/__init__.py b/backend/app/local/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/local/router.py b/backend/app/local/router.py new file mode 100644 index 00000000..b94b4afe --- /dev/null +++ b/backend/app/local/router.py @@ -0,0 +1,28 @@ +from fastapi import APIRouter, HTTPException, status, Depends +from jose import jwt +import datetime +from app.config import get_settings + +router = APIRouter( + prefix="/local", + tags=["local"], +) + + +def create_dummy_token(secret: str, algorithm: str): + data = { + "sub": "known_id", + "name": "Test User", + "iat": datetime.datetime.utcnow(), + "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30) + } + return jwt.encode(data, secret, algorithm=algorithm) + + +@router.get("/dummy-token") +async def dummy_token(): + settings = get_settings() + if settings.ENVIRONMENT != "local": + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, + detail="Dummy token can only be generated in local environment") + return {"dummy_token": create_dummy_token(settings.SECRET_KEY, settings.ALGORITHM)} diff --git a/backend/app/main.py b/backend/app/main.py index 0c7ab751..2469622f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,9 +1,14 @@ from fastapi import FastAPI, Depends from app.portfolio import router as portfolio_router from app.dependencies import validate_api_key, validate_token +from app.config import get_settings app = FastAPI(dependencies=[Depends(validate_api_key), Depends(validate_token)]) app.include_router(portfolio_router.router) + +if get_settings().ENVIRONMENT == "local": + from app.local import router as local_router + app.include_router(local_router.router)