From c7f3112ade3933d9418e41275e47219c92db9c44 Mon Sep 17 00:00:00 2001 From: Benjamin Lin Date: Sun, 20 Oct 2024 18:31:46 -0400 Subject: [PATCH] bearer tokens --- backend/api/decoder.py | 48 +++++++++++++++++++++++++++++++++++++++++ backend/api/resource.py | 33 ++++++++++++++++++++++------ backend/api/service.py | 24 ++++++++++++++++----- backend/api/tag.py | 23 +++++++++++++++----- start.sh | 0 5 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 backend/api/decoder.py mode change 100644 => 100755 start.sh diff --git a/backend/api/decoder.py b/backend/api/decoder.py new file mode 100644 index 0000000..1b55664 --- /dev/null +++ b/backend/api/decoder.py @@ -0,0 +1,48 @@ +# token_utils.py +import jwt +from jwt import PyJWTError +from fastapi import HTTPException, status +from workspace.backend.models.user_model import User +from ..services import UserService + +SECRET = "SECRET_KEY" +ALGORITHM = "HS256" + +def decode_token(token: str) -> User: + try: + payload = jwt.decode(token, SECRET, algorithms=[ALGORITHM]) + user_uuid = payload.get("sub") + if user_uuid is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + user_data = UserService.get_user_by_uuid(user_uuid) + if user_data is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User not found", + headers={"WWW-Authenticate": "Bearer"}, + ) + + user = User( + id=user_data.id, + username=user_data.username, + email=user_data.email, + experience=user_data.experience, + group=user_data.group, + program=user_data.program, + role=user_data.role, + created_at=user_data.created_at, + uuid=user_data.uuid, + ) + + return user + except PyJWTError: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or expired token", + headers={"WWW-Authenticate": "Bearer"}, + ) \ No newline at end of file diff --git a/backend/api/resource.py b/backend/api/resource.py index 0b11539..04e6bc1 100644 --- a/backend/api/resource.py +++ b/backend/api/resource.py @@ -1,6 +1,8 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from decoder import decode_token -from backend.models.user_model import User +from workspace.backend.models.user_model import User from ..services import ResourceService, UserService from ..models.resource_model import Resource @@ -13,31 +15,50 @@ openapi_tags = { "description": "Resource search and related operations.", } +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +def get_current_user(token: str = Depends(oauth2_scheme)) -> User: + user = decode_token(token) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + return user # TODO: Add security using HTTP Bearer Tokens # TODO: Enable authorization by passing user uuid to API # TODO: Create custom exceptions @api.post("", response_model=Resource, tags=["Resource"]) def create( - subject: User, resource: Resource, resource_svc: ResourceService = Depends() + subject: User = Depends(get_current_user), + resource: Resource, + resource_svc: ResourceService = Depends() ): return resource_svc.create(subject, resource) @api.get("", response_model=List[Resource], tags=["Resource"]) -def get_all(subject: User, resource_svc: ResourceService = Depends()): +def get_all( + subject: User = Depends(get_current_user), + resource_svc: ResourceService = Depends()): return resource_svc.get_resource_by_user(subject) @api.put("", response_model=Resource, tags=["Resource"]) def update( - subject: User, resource: Resource, resource_svc: ResourceService = Depends() + subject: User = Depends(get_current_user), + resource: Resource, + resource_svc: ResourceService = Depends() ): return resource_svc.update(subject, resource) @api.delete("", response_model=None, tags=["Resource"]) def delete( - subject: User, resource: Resource, resource_svc: ResourceService = Depends() + subject: User = Depends(get_current_user), + resource: Resource, + resource_svc: ResourceService = Depends() ): resource_svc.delete(subject, resource) diff --git a/backend/api/service.py b/backend/api/service.py index de73d13..ebb3442 100644 --- a/backend/api/service.py +++ b/backend/api/service.py @@ -1,4 +1,6 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from decoder import decode_token from backend.models.user_model import User from ..services import ServiceService, UserService @@ -13,13 +15,25 @@ openapi_tags = { "description": "Service search and related operations.", } +# Creates an OAuth instance +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +def get_current_user(token: str = Depends(oauth2_scheme)) -> User: + user = decode_token(token) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + return user # TODO: Add security using HTTP Bearer Tokens # TODO: Enable authorization by passing user uuid to API # TODO: Create custom exceptions @api.post("", response_model=Service, tags=["Service"]) def create( - subject: User, + subject: User = Depends(getUser), service: Service, service_svc: ServiceService = Depends() ): @@ -28,14 +42,14 @@ def create( @api.get("", response_model=List[Service], tags=["Service"]) def get_all( - subject: User, + subject: User = Depends(getUser), service_svc: ServiceService = Depends() ): return service_svc.get_service_by_user(subject) @api.put("", response_model=Service, tags=["Service"]) def update( - subject: User, + subject: User = Depends(getUser), service: Service, service_svc: ServiceService = Depends() ): @@ -43,7 +57,7 @@ def update( @api.delete("", response_model=None, tags=["Service"]) def delete( - subject: User, + subject: User = Depends(getUser), service: Service, service_svc: ServiceService = Depends() ): diff --git a/backend/api/tag.py b/backend/api/tag.py index 36e7e4a..1da302f 100644 --- a/backend/api/tag.py +++ b/backend/api/tag.py @@ -1,4 +1,6 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from decoder import decode_token from backend.models.tag_model import Tag from backend.models.user_model import User @@ -15,13 +17,24 @@ openapi_tags = { "description": "Tag CRUD operations.", } +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +def get_current_user(token: str = Depends(oauth2_scheme)) -> User: + user = decode_token(token) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + return user # TODO: Add security using HTTP Bearer Tokens # TODO: Enable authorization by passing user uuid to API # TODO: Create custom exceptions @api.post("", response_model=Tag, tags=["Tag"]) def create( - subject: User, + subject: User = Depends(get_current_user), tag: Tag, tag_service: TagService=Depends() ): @@ -29,14 +42,14 @@ def create( @api.get("", response_model=List[Tag], tags=["Tag"]) def get_all( - subject: User, + subject: User = Depends(get_current_user), tag_svc: TagService=Depends() ): return tag_svc.get_all() @api.put("", response_model=Tag, tags=["Tag"]) def update( - subject: User, + subject: User = Depends(get_current_user), tag: Tag, tag_svc: TagService=Depends() ): @@ -44,7 +57,7 @@ def update( @api.delete("", response_model=None, tags=["Tag"]) def delete( - subject: User, + subject: User = Depends(get_current_user), tag: Tag, tag_svc: TagService=Depends() ): diff --git a/start.sh b/start.sh old mode 100644 new mode 100755