Browse Source

Added authentication

main
Madiwka3 1 year ago
parent
commit
0e80ac8c76
14 changed files with 160 additions and 31 deletions
  1. +2
    -1
      app/apis/base.py
  2. +60
    -0
      app/apis/v1/route_auth.py
  3. +13
    -4
      app/apis/v1/route_user.py
  4. +22
    -3
      app/apis/v1/route_vehicle.py
  5. +16
    -0
      app/core/auth.py
  6. +12
    -9
      app/core/config.py
  7. +0
    -2
      app/db/base.py
  8. +1
    -2
      app/db/models/user.py
  9. +0
    -5
      app/db/models/vehicle.py
  10. +10
    -0
      app/db/repository/user.py
  11. +14
    -2
      app/db/repository/vehicle.py
  12. +6
    -0
      app/schemas/token.py
  13. +1
    -2
      app/schemas/vehicle.py
  14. +3
    -1
      requirements.txt

+ 2
- 1
app/apis/base.py View File

@@ -1,8 +1,9 @@
# Base API router -- collecting all APIs here to not clutter main.py # Base API router -- collecting all APIs here to not clutter main.py
from fastapi import APIRouter from fastapi import APIRouter


from apis.v1 import route_user, route_vehicle
from apis.v1 import route_user, route_vehicle, route_auth


api_router = APIRouter() api_router = APIRouter()
api_router.include_router(route_user.router, prefix="/user", tags=["users"]) api_router.include_router(route_user.router, prefix="/user", tags=["users"])
api_router.include_router(route_vehicle.router, prefix="/vehicle", tags=["vehicles"]) api_router.include_router(route_vehicle.router, prefix="/vehicle", tags=["vehicles"])
api_router.include_router(route_auth.router, prefix="", tags=["auth"])

+ 60
- 0
app/apis/v1/route_auth.py View File

@@ -0,0 +1,60 @@
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi import Depends, APIRouter
from sqlalchemy.orm import Session
from fastapi import status, HTTPException
from typing import Annotated
from db.session import get_db
from core.hashing import Hasher
from core.config import settings
from jose import JWTError, jwt
from schemas.token import Token
from db.repository.user import get_user_by_email, get_user_by_phone
from core.auth import create_access_token

router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")


def authenticate_user(login: str, password: str, db: Session):
print("Trying to auth...")
user = None
if ("@" in login):
user = get_user_by_email(email=login, db=db)
elif ("+" in login):
user = get_user_by_phone(phone=login, db=db)
else:
return False
if not user:
return False
if not Hasher.verify_password(password, user.HashedPassword):
return False
return user


def get_current_user(token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[Session, Depends(get_db)]):
print("Getting current user...")
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
except JWTError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
if ("@" in username):
user = get_user_by_email(email=username, db=db)
elif ("+" in username):
user = get_user_by_phone(phone=username, db=db)
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
return user


@router.post("/token", response_model=Token)
def access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
print("Getting token...")
user = authenticate_user(form_data.username, form_data.password, db)
print(user)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password")
access_token = create_access_token(data={"sub": user.Email})
return {"access_token": access_token, "token_type": "bearer"}

+ 13
- 4
app/apis/v1/route_user.py View File

@@ -2,8 +2,9 @@
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from fastapi import Depends from fastapi import Depends
from typing import List

from typing import List, Annotated
from apis.v1.route_auth import get_current_user
from db.models.user import User
from schemas.user import UserCreate, ShowUser from schemas.user import UserCreate, ShowUser
from db.session import get_db from db.session import get_db
from db.repository.user import create_new_user, list_users, get_user_by_id from db.repository.user import create_new_user, list_users, get_user_by_id
@@ -13,20 +14,28 @@ router = APIRouter()




@router.post("/", response_model=ShowUser, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=ShowUser, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
def create_user(user: UserCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
if current_user.Role != "Admin":
raise HTTPException(status_code=403, detail="You are not authorized to perform this action")
user = create_new_user(user=user, db=db) user = create_new_user(user=user, db=db)
return user return user




@router.get("/", response_model=List[ShowUser], status_code=status.HTTP_200_OK) @router.get("/", response_model=List[ShowUser], status_code=status.HTTP_200_OK)
def get_all_users(db: Session = Depends(get_db), role: str = None): def get_all_users(db: Session = Depends(get_db), role: str = None):
if role == None:
if role is None:
users = list_users(db=db) users = list_users(db=db)
return users return users
users = list_users(db=db, role=role) users = list_users(db=db, role=role)
return users return users




@router.get("/me", response_model=ShowUser, status_code=status.HTTP_200_OK)
def get_user_me(current_user: Annotated[User, Depends(get_current_user)], db: Annotated[Session, Depends(get_db)]):
print("Getting current user...")
return current_user


@router.get("/{user_id}", response_model=ShowUser, status_code=status.HTTP_200_OK) @router.get("/{user_id}", response_model=ShowUser, status_code=status.HTTP_200_OK)
def get_user(user_id: int, db: Session = Depends(get_db)): def get_user(user_id: int, db: Session = Depends(get_db)):
user = get_user_by_id(user_id=user_id, db=db) user = get_user_by_id(user_id=user_id, db=db)


+ 22
- 3
app/apis/v1/route_vehicle.py View File

@@ -10,13 +10,18 @@ from db.repository.vehicle import (
list_vehicles, list_vehicles,
get_vehicle_by_id, get_vehicle_by_id,
replace_vehicle_data, replace_vehicle_data,
delete_vehicle_data,
) )
from db.models.user import User
from apis.v1.route_auth import get_current_user


router = APIRouter() router = APIRouter()




@router.post("/", response_model=OutputVehicle, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=OutputVehicle, status_code=status.HTTP_201_CREATED)
async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db)):
async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
if current_user.Role != "Admin":
raise HTTPException(status_code=403, detail="You are not authorized to perform this action")
vehicle = create_new_vehicle(vehicle=vehicle, db=db) vehicle = create_new_vehicle(vehicle=vehicle, db=db)
return vehicle return vehicle


@@ -47,7 +52,9 @@ async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db)):
response_model=OutputVehicle, response_model=OutputVehicle,
status_code=status.HTTP_200_OK, status_code=status.HTTP_200_OK,
) )
async def assign_driver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db)):
async def assign_driver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
if current_user.Role != "Admin":
raise HTTPException(status_code=403, detail="You are not authorized to perform this action")
vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db)
if vehicle == "nodriver": if vehicle == "nodriver":
raise HTTPException( raise HTTPException(
@@ -85,11 +92,23 @@ async def get_vehicle(vehicle_id: int, db: Session = Depends(get_db)):
"/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK "/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK
) )
def update_vehicle( def update_vehicle(
vehicle_id: int, vehicle: UpdateVehicle, db: Session = Depends(get_db)
vehicle_id: int, vehicle: UpdateVehicle, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
): ):
if current_user.Role != "Admin":
raise HTTPException(status_code=403, detail="You are not authorized to perform this action")
vehicleRes = replace_vehicle_data(id=vehicle_id, vehicle=vehicle, db=db) vehicleRes = replace_vehicle_data(id=vehicle_id, vehicle=vehicle, db=db)
if vehicleRes == "vehicleNotFound": if vehicleRes == "vehicleNotFound":
raise HTTPException(status_code=404, detail="Vehicle not found") raise HTTPException(status_code=404, detail="Vehicle not found")
elif vehicleRes == "badreq": elif vehicleRes == "badreq":
raise HTTPException(status_code=502, detail="Bad request") raise HTTPException(status_code=502, detail="Bad request")
return vehicleRes return vehicleRes


@router.delete("/{vehicle_id}", status_code=status.HTTP_200_OK)
def delete_vehicle(vehicle_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
if current_user.Role != "Admin":
raise HTTPException(status_code=403, detail="You are not authorized to perform this action")
result = delete_vehicle_data(id=vehicle_id, db=db)
if result == "vehicleNotFound":
raise HTTPException(status_code=404, detail="Vehicle not found")
return {"msg": "Vehicle deleted successfully"}

+ 16
- 0
app/core/auth.py View File

@@ -0,0 +1,16 @@
from datetime import datetime, timedelta
from typing import Optional
from jose import jwt

from core.config import settings


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt

+ 12
- 9
app/core/config.py View File

@@ -1,12 +1,15 @@
class Settings: class Settings:
PROJECT_NAME:str = "VMS"
PROJECT_VERSION:str = "1.0.0"
POSTGRES_USER : str = "VMSBase"
PROJECT_NAME: str = "VMS"
PROJECT_VERSION: str = "1.0.0"
POSTGRES_USER: str = "VMSBase"
POSTGRES_PASSWORD = "VMSBasePass" POSTGRES_PASSWORD = "VMSBasePass"
POSTGRES_SERVER : str = "localhost"
POSTGRES_PORT : str = "5432"
POSTGRES_DB : str = "VMSData"
POSTGRES_SERVER: str = "localhost"
POSTGRES_PORT: str = "5432"
POSTGRES_DB: str = "VMSData"
DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}:{POSTGRES_PORT}/{POSTGRES_DB}" DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}:{POSTGRES_PORT}/{POSTGRES_DB}"
settings = Settings()
ACCESS_TOKEN_EXPIRE: int = 30
SECRET_KEY: str = "tH357aC6oA7ofCaN3yTffYkRh"
ALGORITHM: str = "HS256"


settings = Settings()

+ 0
- 2
app/db/base.py View File

@@ -2,5 +2,3 @@
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base() Base = declarative_base()
from db.models.user import User
from db.models.vehicle import Vehicle

+ 1
- 2
app/db/models/user.py View File

@@ -1,6 +1,5 @@
# PostgreSQL table model for users # PostgreSQL table model for users
from sqlalchemy import Column, Integer, String, DateTime, Boolean, URL
from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, DateTime
from db.base import Base from db.base import Base






+ 0
- 5
app/db/models/vehicle.py View File

@@ -3,13 +3,8 @@ from sqlalchemy import (
Column, Column,
Integer, Integer,
String, String,
DateTime,
Boolean,
URL,
ARRAY, ARRAY,
ForeignKey,
) )
from sqlalchemy.orm import relationship
from db.base import Base from db.base import Base






+ 10
- 0
app/db/repository/user.py View File

@@ -28,6 +28,16 @@ def get_user_by_id(user_id: int, db: Session):
return user return user




def get_user_by_email(email: str, db: Session):
user = db.query(User).filter(User.Email == email).first()
return user


def get_user_by_phone(phone: str, db: Session):
user = db.query(User).filter(User.ContactNumber == phone).first()
return user


def verify_driver_exists(driver_id: int, db: Session): def verify_driver_exists(driver_id: int, db: Session):
driver = db.query(User).filter(User.Id == driver_id).first() driver = db.query(User).filter(User.Id == driver_id).first()
if not driver: if not driver:


+ 14
- 2
app/db/repository/vehicle.py View File

@@ -1,5 +1,5 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from schemas.vehicle import CreateVehicle, OutputVehicle, UpdateVehicle
from schemas.vehicle import CreateVehicle, UpdateVehicle
from db.models.vehicle import Vehicle from db.models.vehicle import Vehicle
from db.repository.user import verify_driver_exists from db.repository.user import verify_driver_exists


@@ -27,7 +27,9 @@ def assign_vehicle_driver(vehicle_id: int, driver_id: int, db: Session):
return "alreadyassigned" return "alreadyassigned"
if verify_driver_exists(driver_id=driver_id, db=db): if verify_driver_exists(driver_id=driver_id, db=db):
print(vehicle.AssignedDriverIds) print(vehicle.AssignedDriverIds)
vehicledb.update({"AssignedDriverIds": vehicle.AssignedDriverIds + [driver_id]})
vehicledb.update(
{"AssignedDriverIds": vehicle.AssignedDriverIds + [driver_id]}
)
print(vehicle.AssignedDriverIds) print(vehicle.AssignedDriverIds)
db.add(vehicle) db.add(vehicle)
db.commit() db.commit()
@@ -65,3 +67,13 @@ def replace_vehicle_data(id: int, vehicle: UpdateVehicle, db: Session):
db.add(vehicle_object) db.add(vehicle_object)
db.commit() db.commit()
return vehicle_object return vehicle_object


def delete_vehicle_data(id: int, db: Session):
vehicle_db = db.query(Vehicle).filter(Vehicle.Id == id)
vehicle_object = vehicle_db.first()
if not vehicle_object:
return "vehiclenotfound"
db.delete(vehicle_object)
db.commit()
return vehicle_object

+ 6
- 0
app/schemas/token.py View File

@@ -0,0 +1,6 @@
from pydantic import BaseModel


class Token(BaseModel):
access_token: str
token_type: str

+ 1
- 2
app/schemas/vehicle.py View File

@@ -1,6 +1,5 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, root_validator
from datetime import datetime
from pydantic import BaseModel




class CreateVehicle(BaseModel): class CreateVehicle(BaseModel):


+ 3
- 1
requirements.txt View File

@@ -3,4 +3,6 @@ pydantic
sqlalchemy sqlalchemy
psycopg2 psycopg2
alembic==1.12.0 alembic==1.12.0
passlib
passlib
python-jose==3.3.0
python-multipart==0.0.6

Loading…
Cancel
Save