Skip to content

Commit

Permalink
User endpoint (#56)
Browse files Browse the repository at this point in the history
* FIX & DOCS: user_uuid will be more suitable for data filteration & updated docs for the same

* FIX & DOCS: added a check for the user existence & updated docs for the same

* FIX: trailing white space

* FIX: added user check & resolved #38

* CHORE: update project dependency as per jwt auth & resolves #46 (#47)

* FEAT: added jwt accest token creation & refresh token creation (#48)

* FIX & DOCS: added a check for the user existence & updated docs for the same

* FIX & DOCS: added a check for the user existence & updated docs for the same

* FIX: added env lib initialized state

* FEAT & DOCS: replace UserLogin schema with OAuth2PasswordRequestForm & updated docs for same

* FIX & DOCS: added a check for the user existence & updated docs for t… (#44)

* FIX & DOCS: added a check for the user existence & updated docs for the same

* FIX: trailing white space

* FIX & DOCS: added a check for the user existence & updated docs for the same

* FEAT & DOCS: replace UserLogin schema with OAuth2PasswordRequestForm & updated docs for same

* FIX: returning only user_uuid instead of user object

* FEAT: added token payload schema

* DOCS: removed redundand explanation

* FEAT: added function to check current user token and uuid existence

* FEAT: added token dependency on get_user endpoint

* FEAT: added token dependency on get_users endpoint

* FEAT: added token dependency on filter_users endpoint

* FEAT: added token dependency on update_user endpoint

* FEAT: added token dependency on delete_user endpoint
  • Loading branch information
mramitdas authored Nov 8, 2023
1 parent cb02590 commit 286f75c
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 20 deletions.
33 changes: 13 additions & 20 deletions app/api/V1/endpoints/user.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
from datetime import datetime
from typing import Any, List, Union
from typing import List

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt
from pydantic import ValidationError
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm

from app.models.user import User as UserModel
from app.schemas.user import TokenPayload, UserIn, UserOut, UserSearch, UserUpdate
from app.schemas.user import UserIn, UserOut, UserSearch, UserUpdate
from .utils import get_current_user

from .utils import (
ALGORITHM,
JWT_SECRET_KEY,
create_access_token,
create_refresh_token,
hash_password,
Expand All @@ -24,7 +20,7 @@
@user.get(
"/user/{user_uuid}", response_model=UserIn, response_model_exclude={"password"}
)
async def get_user(user_uuid: int):
async def get_user(user_uuid: int, token: str = Depends(get_current_user)):
"""
Get user data by user UUID.
Expand Down Expand Up @@ -52,7 +48,7 @@ async def get_user(user_uuid: int):


@user.get("/users/", response_model=list[UserOut])
async def get_users():
async def get_users(token: str = Depends(get_current_user)):
"""
Retrieve a list of users.
Expand Down Expand Up @@ -80,7 +76,7 @@ async def get_users():


@user.post("/users/filter/", response_model=List[UserOut])
async def filter_users(filter: UserSearch):
async def filter_users(filter: UserSearch, token: str = Depends(get_current_user)):
"""
Filter users based on the provided filter criteria.
Expand Down Expand Up @@ -199,12 +195,9 @@ async def login_user(user_data: OAuth2PasswordRequestForm = Depends()):
if user_response:
if verify_password(user_data.password, user_response[0]["password"]):
return {
"status": "success",
"message": "User login successful",
"data": {
"access_token": create_access_token(user_response[0]["user_uuid"]),
"refresh_token": create_refresh_token(user_response[0]["user_uuid"])
},
"access_token": create_access_token(user_response[0]["user_uuid"]),
"refresh_token": create_refresh_token(user_response[0]["user_uuid"]),
"token_type": "bearer",
}
else:
raise HTTPException(status_code=401, detail="Invalid credentials")
Expand All @@ -213,7 +206,7 @@ async def login_user(user_data: OAuth2PasswordRequestForm = Depends()):


@user.patch("/user/update/")
async def update_user(data: UserUpdate):
async def update_user(data: UserUpdate, token: str = Depends(get_current_user)):
"""
Update user information.
Expand Down Expand Up @@ -261,7 +254,7 @@ async def update_user(data: UserUpdate):


@user.delete("/user/delete/{user_uuid}")
async def delete_user(user_uuid: int):
async def delete_user(user_uuid: int, token: str = Depends(get_current_user)):
"""
Delete a user.
Expand Down
66 changes: 66 additions & 0 deletions app/api/V1/endpoints/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
from typing import Any, Union

from dotenv import load_dotenv
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
from passlib.context import CryptContext
from pydantic import ValidationError

from app.models.user import User as UserModel
from app.schemas.user import TokenPayload, UserOut

load_dotenv()
ACCESS_TOKEN_EXPIRE_MINUTES = 30 # 30 minutes
Expand All @@ -13,6 +19,7 @@
JWT_SECRET_KEY = os.environ["JWT_SECRET_KEY"] # should be kept secret
JWT_REFRESH_SECRET_KEY = os.environ["JWT_REFRESH_SECRET_KEY"] # should be kept secret

reuseable_oauth = OAuth2PasswordBearer(tokenUrl="api/v1/user/login", scheme_name="JWT")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


Expand Down Expand Up @@ -116,3 +123,62 @@ def create_refresh_token(subject: Union[str, Any], expires_delta: int = None) ->
to_encode = {"exp": expires_delta, "sub": str(subject)}
encoded_jwt = jwt.encode(to_encode, JWT_REFRESH_SECRET_KEY, ALGORITHM)
return encoded_jwt


async def get_current_user(token: str = Depends(reuseable_oauth)) -> UserOut:
"""
Get the current user based on the provided JWT token.
This asynchronous function verifies and decodes a JWT token, retrieves the user's information, and returns it as a UserOut object. If the token is invalid, expired, or if the user data cannot be retrieved, appropriate HTTP exceptions are raised.
Args:
token (str, optional): The JWT token provided for authentication. It is obtained from the 'Authorization' header of the request.
Returns:
UserOut: A UserOut object containing user information if the token is valid and the user is found.
Raises:
HTTPException 401: If the token has expired or is invalid, an unauthorized HTTP exception is raised with the appropriate status code and details.
HTTPException 403: If the token payload or credentials cannot be validated, a forbidden HTTP exception is raised with the appropriate status code and details.
HTTPException 500: If there is an issue with retrieving user data, an internal server error HTTP exception is raised with the appropriate status code and details.
HTTPException 404: If no user with the provided user UUID exists, a not found HTTP exception is raised.
"""
try:
# Verify and decode the JWT token
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[ALGORITHM])
token_data = TokenPayload(**payload)

# Check if the token has expired
if datetime.fromtimestamp(token_data.exp) < datetime.now():
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token expired",
headers={"WWW-Authenticate": "Bearer"},
)
except (jwt.JWTError, ValidationError):
# Handle JWT validation errors or token payload validation errors
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)

try:
# Fetch user data from your data source (e.g., a database)
user_instance = UserModel()
user_data = user_instance.get(uuid=int(token_data.sub))
except Exception as e:
# Handle any exceptions that occur while fetching user data
raise HTTPException(
status_code=500, detail=f"Failed to retrieve user data: {e}"
)

if user_data is None:
# Handle the case when the user is not found
raise HTTPException(status_code=404, detail="User not found")

# Return the user data as a UserOut object
return UserOut(**user_data)

0 comments on commit 286f75c

Please sign in to comment.