Working on the implementation of fastapi users.

This commit is contained in:
charles-gauthereau
2024-11-02 12:38:05 +01:00
parent ff7f864ec2
commit 54248b8670
15 changed files with 289 additions and 312 deletions
+2 -3
View File
@@ -1,10 +1,9 @@
from sqlalchemy import Column, DateTime, Integer
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.sql import func
from database import BaseModel
class Model(BaseModel):
class Model(DeclarativeBase):
__abstract__ = True
id = Column(Integer, primary_key=True, index=True)
+34 -26
View File
@@ -1,33 +1,41 @@
from sqlalchemy import asc
from sqlalchemy.orm import Session
from . import models, schemas, services
from sqlalchemy import asc, select
from sqlalchemy.ext.asyncio import AsyncSession
from .models import User
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
#
# def get_user(db: Session, user_id: int):
# return db.query(models.User).filter(models.User.id == user_id).first()
#
# def get_users(db: Session, skip: int = 0, limit: int = 100):
# return db.query(models.User).order_by(asc(models.User.id)).offset(skip).limit(limit).all()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).order_by(asc(models.User.id)).offset(skip).limit(limit).all()
async def get_users(session: AsyncSession, skip: int = 0, limit: int = 100):
query = select(User).order_by(asc(User.id)).offset(skip).limit(limit)
result = await session.execute(query)
users = result.scalars().all()
return users
#
# def get_user_by_email(db: Session, email: str):
# return db.query(models.User).filter(models.User.email == email).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
# def create_user(db: Session, user: schemas.UserCreate):
# fake_hashed_password = services.get_password_hash(user.password)
# db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
# db.add(db_user)
# db.commit()
# db.refresh(db_user)
# return db_user
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = services.get_password_hash(user.password)
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def create_admin_user(db: Session, user: schemas.AdminUserCreate):
fake_hashed_password = services.get_password_hash(user.password)
db_user = models.User(email=user.email, hashed_password=fake_hashed_password, role=user.role)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
#
# def create_admin_user(db: Session, user: schemas.AdminUserCreate):
# fake_hashed_password = services.get_password_hash(user.password)
# db_user = models.User(email=user.email, hashed_password=fake_hashed_password, role=user.role)
# db.add(db_user)
# db.commit()
# db.refresh(db_user)
# return db_user
+18 -10
View File
@@ -1,15 +1,23 @@
from sqlalchemy import Column, Boolean, String, Float, Enum
from sqlalchemy.orm import relationship
from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTableUUID
from sqlalchemy.orm import DeclarativeBase
from apps.base.models import Model
from apps.users.roles import Role
class User(Model):
__tablename__ = 'users'
#
# class User(Model):
# __tablename__ = 'users'
#
# email = Column(String, unique=True, index=True)
# hashed_password = Column(String)
# is_active = Column(Boolean, default=True)
# role = Column(Enum(Role), default=Role.USER) # Add a role attribute
# # role = Column(String) # Ensure role is defined
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
role = Column(Enum(Role), default=Role.USER) # Add a role attribute
# role = Column(String) # Ensure role is defined
class Base(DeclarativeBase):
pass
class User(SQLAlchemyBaseUserTableUUID, Base):
pass
+9 -30
View File
@@ -1,36 +1,15 @@
from apps.base.schemas import Schema
from apps.users.roles import Role
import uuid
from fastapi_users import schemas
class UserBase(Schema):
email: str
class UserRead(schemas.BaseUser[uuid.UUID]):
pass
class UserCreate(UserBase):
password: str
class UserCreate(schemas.BaseUserCreate):
pass
class User(UserBase):
id: int
is_active: bool
role: Role
class Config:
from_attributes = True
class Token(Schema):
access_token: str
token_type: str
class TokenData(Schema):
username: str | None = None
class UserInDB(User):
hashed_password: str
class AdminUserCreate(UserCreate):
role: Role
class UserUpdate(schemas.BaseUserUpdate):
pass
+41 -100
View File
@@ -1,115 +1,56 @@
from datetime import datetime, timedelta, timezone
from typing import Annotated
from fastapi import Request
import uuid
from typing import Optional
from fastapi import Depends, HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy.orm import Session
from functools import wraps
from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin
from fastapi_users.authentication import (
AuthenticationBackend,
BearerTransport,
JWTStrategy,
)
from fastapi_users.db import SQLAlchemyUserDatabase
from apps.users.crud import get_user_by_email
from apps.users.roles import Permission, role_permissions
from apps.users.schemas import TokenData, User, UserInDB
from middleware.db_connection import get_db
from settings import config
from apps.users.models import User
from database import get_user_db
ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
SECRET = "SECRET"
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def on_after_register(self, user: User, request: Optional[Request] = None):
print(f"User {user.id} has registered.")
async def on_after_forgot_password(
self, user: User, token: str, request: Optional[Request] = None
):
print(f"User {user.id} has forgot their password. Reset token: {token}")
async def on_after_request_verify(
self, user: User, token: str, request: Optional[Request] = None
):
print(f"Verification requested for user {user.id}. Verification token: {token}")
def get_password_hash(password):
return pwd_context.hash(password)
async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
yield UserManager(user_db)
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
def authenticate_user(db, username: str, password: str):
user = get_user_by_email(db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def get_jwt_strategy() -> JWTStrategy:
return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
auth_backend = AuthenticationBackend(
name="jwt",
transport=bearer_transport,
get_strategy=get_jwt_strategy,
)
fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
print(token)
payload = jwt.decode(token, config.SECRET_KEY, algorithms=[ALGORITHM])
print("payload", payload)
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
print(token_data.username)
user = get_user_by_email(db, email=token_data.username)
print(user)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(
current_user: Annotated[User, Depends(get_current_user)]
):
if not current_user.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
def permission_required(*permissions: Permission):
def decorator(func):
@wraps(func)
async def decorated_function(*args, **kwargs):
request: Request = kwargs.get("request")
db: Session = kwargs.get("db")
if not request:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing request object",
)
authorization_header = request.headers.get("Authorization")
if not authorization_header:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing authorization header",
)
token = authorization_header.split(" ")[1]
current_user = await get_current_user(token=token, db=db)
for permission in permissions:
if permission not in role_permissions[current_user.role]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"You do not have the required permission: {permission.value}",
)
return await func(*args, **kwargs)
return decorated_function
return decorator
current_active_user = fastapi_users.current_user(active=True)
+23 -8
View File
@@ -1,18 +1,33 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from typing import AsyncGenerator
from fastapi import Depends
from fastapi_users.db import SQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine, async_session
from apps.users.models import Base, User
from settings import config
POSTGRES_SERVER = config.POSTGRES_SERVER
POSTGRES_USER = config.POSTGRES_USER
POSTGRES_PASSWORD = config.POSTGRES_PASSWORD
POSTGRES_DB = config.POSTGRES_DB
SQLALCHEMY_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}/{POSTGRES_DB}"
engine = create_async_engine(SQLALCHEMY_DATABASE_URL)
async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
SQLALCHEMY_DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}/{POSTGRES_DB}"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
async def create_db_and_tables():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
BaseModel = declarative_base()
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(session, User)
async def get_db_session() -> AsyncSession:
async with async_session() as session:
yield session
+6 -36
View File
@@ -1,48 +1,18 @@
import os
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.orm import Session
from starlette.middleware.trustedhost import TrustedHostMiddleware
from apps.users.roles import Role
from apps.users.schemas import UserCreate, User, AdminUserCreate
from database import BaseModel, engine, SessionLocal
from middleware.db_connection import DatabaseSessionMiddleware
from database import create_db_and_tables
from middleware.create_user import create_user
from routes.main import api_router
from settings import config
def init_default_users(db: Session):
from apps.users import crud
print("Initializing default users...")
if not crud.get_user_by_email(db, "admin@portabase.com"):
print("Creating admin user...")
user = AdminUserCreate(email="admin@portabase.com", password="password", role=Role.ADMIN)
crud.create_admin_user(db, user)
@asynccontextmanager
async def lifespan(app: FastAPI):
print("Initialising database...")
BaseModel.metadata.create_all(bind=engine)
print("Database initialised!")
# Initialize default users
db = SessionLocal()
try:
init_default_users(db)
finally:
db.close()
await create_db_and_tables()
await create_user("soluce.technologies@gmail.com", "12345678", True)
yield
print("App shutdown!")
middlewares = []
app = FastAPI(lifespan=lifespan, middleware=middlewares)
app.add_middleware(DatabaseSessionMiddleware)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=config.ALLOWED_HOSTS)
app = FastAPI(lifespan=lifespan)
app.include_router(api_router)
+28
View File
@@ -0,0 +1,28 @@
import contextlib
from fastapi_users.exceptions import UserAlreadyExists
from apps.users.schemas import UserCreate
from apps.users.services import get_user_manager
from database import get_async_session, get_user_db
get_async_session_context = contextlib.asynccontextmanager(get_async_session)
get_user_db_context = contextlib.asynccontextmanager(get_user_db)
get_user_manager_context = contextlib.asynccontextmanager(get_user_manager)
async def create_user(email: str, password: str, is_superuser: bool = False):
try:
async with get_async_session_context() as session:
async with get_user_db_context(session) as user_db:
async with get_user_manager_context(user_db) as user_manager:
user = await user_manager.create(
UserCreate(
email=email, password=password, is_superuser=is_superuser
)
)
print(f"User created {user}")
return user
except UserAlreadyExists:
print(f"User {email} already exists")
# raise
+17 -11
View File
@@ -1,20 +1,26 @@
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from database import SessionLocal
from database import get_async_session
class DatabaseSessionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = Response("Internal server error", status_code=500)
try:
request.state.db = SessionLocal()
response = await call_next(request)
finally:
request.state.db.close()
return response
# from starlette.middleware.base import BaseHTTPMiddleware
# from database import SessionLocal
#
# class DatabaseSessionMiddleware(BaseHTTPMiddleware):
# async def dispatch(self, request: Request, call_next):
# response = Response("Internal server error", status_code=500)
# try:
# request.state.db = SessionLocal()
# response = await call_next(request)
# finally:
# request.state.db.close()
# return response
def get_db(request: Request):
print(request)
return request.state.db
@@ -0,0 +1,31 @@
#SQLAlchemy
#psycopg2
fastapi-users[sqlalchemy]
requests
websockets
python-dotenv
# fastapi libraries
fastapi[all]
fastapi-utilities
pydantic==2.6.0
pydantic-settings
alembic
# deployment libraries
uvicorn==0.27.0
gunicorn
# cryptographic libraries
python-jose
PyJWT
passlib
paramiko
python-multipart==0.0.12
filetype
biplist
+7 -27
View File
@@ -1,28 +1,8 @@
SQLAlchemy
psycopg2
requests
websockets
python-dotenv
# fastapi libraries
fastapi[all]
fastapi-utilities
pydantic==2.6.0
pydantic-settings
alembic
# deployment libraries
uvicorn==0.27.0
fastapi
fastapi-users[sqlalchemy]
uvicorn[standard]
aiosqlite
gunicorn
# cryptographic libraries
python-jose
PyJWT
passlib
paramiko
python-multipart==0.0.12
filetype
biplist
pydantic-settings
asyncpg
psycopg2
+20 -20
View File
@@ -1,20 +1,20 @@
from fastapi import Depends, APIRouter, Request
from sqlalchemy.orm import Session
from apps.users.roles import Permission
from apps.users.services import permission_required, get_current_user, get_current_active_user
from pydantic import BaseModel
from middleware.db_connection import get_db
router = APIRouter(prefix="/agents", tags=["agents"])
class Agent(BaseModel):
name: str
@router.post("/", dependencies=[Depends(get_current_active_user)])
@permission_required(Permission.CREATE, Permission.READ)
async def create_agent(agent: Agent, request: Request, db: Session = Depends(get_db)):
print(agent)
return True
# from fastapi import Depends, APIRouter, Request
# from sqlalchemy.orm import Session
# from apps.users.roles import Permission
# from apps.users.services import permission_required, get_current_user, get_current_active_user
# from pydantic import BaseModel
# from middleware.db_connection import get_db
#
#
# router = APIRouter(prefix="/agents", tags=["agents"])
#
#
# class Agent(BaseModel):
# name: str
#
#
# @router.post("/", dependencies=[Depends(get_current_active_user)])
# @permission_required(Permission.CREATE, Permission.READ)
# async def create_agent(agent: Agent, request: Request, db: Session = Depends(get_db)):
# print(agent)
# return True
+25
View File
@@ -0,0 +1,25 @@
from fastapi import APIRouter
from apps.users.schemas import UserRead, UserCreate
from apps.users.services import fastapi_users, auth_backend
router = APIRouter()
router.include_router(
fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
)
router.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
router.include_router(
fastapi_users.get_reset_password_router(),
prefix="/auth",
tags=["auth"],
)
router.include_router(
fastapi_users.get_verify_router(UserRead),
prefix="/auth",
tags=["auth"],
)
+25 -39
View File
@@ -1,53 +1,39 @@
from datetime import timedelta
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException
from fastapi_users import FastAPIUsers
from fastapi_users.schemas import BaseUser
from sqlalchemy import asc
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from typing import Annotated
from apps.users.roles import Permission
from apps.users.services import authenticate_user, create_access_token, get_current_active_user
from fastapi.security import OAuth2PasswordRequestForm
from apps.users import crud, models, schemas
from apps.users import crud
from apps.users.models import User
from apps.users.schemas import UserRead, UserUpdate
from apps.users.services import fastapi_users, current_active_user
from database import get_user_db, get_async_session
from middleware.db_connection import get_db
from sqlalchemy.future import select
from apps.users.schemas import User, UserCreate, Token
from settings import config
# router = APIRouter()
router = APIRouter(prefix="/users", tags=["users"])
@router.post("/token", response_model=Token)
async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
db: Session = Depends(get_db)):
user = authenticate_user(db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
return Token(access_token=access_token, token_type="bearer")
current_superuser = fastapi_users.current_user(active=True, superuser=True)
@router.get("/list", response_model=list[User])
async def list_users(db: Session = Depends(get_db)):
db_users = crud.get_users(db)
return db_users
# @router.get("/list", response_model=list[BaseUser])
# async def list_users(db: Session = Depends(get_db), user: User = Depends(current_superuser)):
# db_users = crud.get_users(db)
# return db_users
@router.get("/me", response_model=User)
async def read_users_me(current_user: Annotated[User, Depends(get_current_active_user)]):
return current_user
@router.get("/list", response_model=List[BaseUser])
async def get_users(skip: int = 0, limit: int = 10, session: AsyncSession = Depends(get_async_session)):
users = await crud.get_users(session=session, skip=skip, limit=limit)
@router.post("/", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
return users
router.include_router(
fastapi_users.get_users_router(UserRead, UserUpdate)
)
+3 -2
View File
@@ -1,8 +1,9 @@
from fastapi import APIRouter
from routes.http import welcome, user, agent
from routes.http import welcome, user, agent, auth
api_router = APIRouter(prefix="/api", tags=["api"])
api_router.include_router(welcome.router)
api_router.include_router(user.router)
api_router.include_router(agent.router)
api_router.include_router(auth.router)
# api_router.include_router(agent.router)