Migrate to FastAPI JWT auth

This commit is contained in:
april
2023-12-20 16:11:02 -06:00
parent f8ecc028c7
commit d791e6f062
14 changed files with 369 additions and 281 deletions

0
api/app/__init__.py Normal file
View File

40
api/app/api.py Normal file
View File

@@ -0,0 +1,40 @@
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from mongoengine import connect
from app.config import get_settings
from database.utils import create_admin_user
from routes import users, flights
logger = logging.getLogger("api")
logging.basicConfig(format='%(asctime)s - %(levelname)s: %(message)s', level=logging.DEBUG)
async def connect_to_db():
# Connect to MongoDB
settings = get_settings()
try:
connected = connect(settings.db_name, host=settings.db_uri, username=settings.db_user,
password=settings.db_pwd, authentication_source=settings.db_name)
if connected:
logging.info("Connected to database %s", settings.db_name)
# Create default admin user if it doesn't exist
create_admin_user()
except ConnectionError:
logger.error("Failed to connect to MongoDB")
raise ConnectionError
# Initialize FastAPI
app = FastAPI()
app.include_router(users.router)
app.include_router(flights.router)
@app.on_event("startup")
async def startup():
await connect_to_db()

25
api/app/config.py Normal file
View File

@@ -0,0 +1,25 @@
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
db_uri: str = "localhost"
db_name: str = "tailfin"
db_user: str
db_pwd: str
access_token_expire_minutes: int = 30
refresh_token_expire_minutes: int = 60 * 24 * 7
jwt_algorithm: str = "HS256"
jwt_secret_key: str = "please-change-me"
jwt_refresh_secret_key: str = "change-me-i-beg-of-you"
@lru_cache
def get_settings():
return Settings()

73
api/app/deps.py Normal file
View File

@@ -0,0 +1,73 @@
from datetime import datetime
from typing import Annotated
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
from mongoengine import DoesNotExist
from pydantic import ValidationError
from app.config import get_settings, Settings
from database.models import User, TokenBlacklist
from schemas import GetSystemUserSchema, TokenPayload, AuthLevel
reusable_oath = OAuth2PasswordBearer(
tokenUrl="/login",
scheme_name="JWT"
)
async def get_current_user(settings: Annotated[Settings, Depends(get_settings)],
token: str = Depends(reusable_oath)) -> GetSystemUserSchema:
try:
payload = jwt.decode(
token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm]
)
token_data = TokenPayload(**payload)
if datetime.fromtimestamp(token_data.exp) < datetime.now():
raise HTTPException(401, "Token expired", {"WWW-Authenticate": "Bearer"})
except (jwt.JWTError, ValidationError):
raise HTTPException(403, "Could not validate credentials", {"WWW-Authenticate": "Bearer"})
try:
TokenBlacklist.objects.get(token=token)
raise HTTPException(403, "Token expired", {"WWW-Authenticate": "Bearer"})
except DoesNotExist:
try:
user = User.objects.get(id=token_data.sub)
except DoesNotExist:
raise HTTPException(404, "Could not find user")
return GetSystemUserSchema(id=str(user.id), username=user.username, level=user.level, password=user.password)
async def get_current_user_token(settings: Annotated[Settings, Depends(get_settings)],
token: str = Depends(reusable_oath)) -> (GetSystemUserSchema, str):
try:
payload = jwt.decode(
token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm]
)
token_data = TokenPayload(**payload)
if datetime.fromtimestamp(token_data.exp) < datetime.now():
raise HTTPException(401, "Token expired", {"WWW-Authenticate": "Bearer"})
except (jwt.JWTError, ValidationError):
raise HTTPException(403, "Could not validate credentials", {"WWW-Authenticate": "Bearer"})
try:
TokenBlacklist.objects.get(token=token)
raise HTTPException(403, "Token expired", {"WWW-Authenticate": "Bearer"})
except DoesNotExist:
try:
user = User.objects.get(id=token_data.sub)
except DoesNotExist:
raise HTTPException(404, "Could not find user")
return GetSystemUserSchema(id=str(user.id), username=user.username, level=user.level,
password=user.password), token
async def admin_required(user: Annotated[GetSystemUserSchema, Depends(get_current_user)]):
if user.level < AuthLevel.ADMIN:
raise HTTPException(403, "Access unauthorized")