After_Chido_Api/services/auth_service.py

194 lines
7.4 KiB
Python

from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from config.settings import settings
from models.schemas import TokenData, UserCreate, UserResponse
from config.database import get_db
from models.db import users_table
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession # Import ajouté ici
# Configuration pour le hachage des mots de passe
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Configuration pour OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
class AuthService:
@staticmethod
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
@staticmethod
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
@staticmethod
async def authenticate_user(email: str, password: str, db):
query = select(users_table).where(users_table.c.email == email)
result = await db.execute(query)
user = result.fetchone()
if not user:
return None
if not AuthService.verify_password(password, user["hashed_password"]):
return None
return user
@staticmethod
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.access_token_expire_minutes)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
@staticmethod
async def get_current_user(token: str = Depends(oauth2_scheme), db=Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
email: str = payload.get("sub")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
except JWTError:
raise credentials_exception
query = select(users_table).where(users_table.c.email == token_data.email)
result = await db.execute(query)
user = result.fetchone()
if user is None:
raise credentials_exception
return UserResponse(**user)
@staticmethod
async def create_user(user: UserCreate, db):
query = select(users_table).where(users_table.c.email == user.email)
result = await db.execute(query)
existing_user = result.fetchone()
if existing_user:
raise HTTPException(status_code=400, detail="Email already registered")
hashed_password = AuthService.get_password_hash(user.password)
query = insert(users_table).values(
email=user.email,
full_name=user.full_name,
phone=user.phone,
date_of_birth=user.date_of_birth,
organization=user.organization,
hashed_password=hashed_password,
role="user"
)
try:
result = await db.execute(query)
await db.commit()
user_id = result.inserted_primary_key[0]
return await AuthService.get_current_user(db=db)
except Exception as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not create user: {str(e)}")
@staticmethod
async def admin_required(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to perform this action.",
)
try:
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
email: str = payload.get("sub")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
except JWTError:
raise credentials_exception
query = users_table.select().where(users_table.c.email == token_data.email)
result = await db.execute(query)
user = result.fetchone()
if user is None:
raise credentials_exception
if user["role"] != "admin":
raise credentials_exception
return user
@staticmethod
async def update_user(user_id: int, updates: dict, token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)):
"""
Met à jour les informations d'un utilisateur.
- Un utilisateur peut mettre à jour ses propres informations.
- Un administrateur peut mettre à jour les informations de n'importe quel utilisateur.
"""
# Décodage et vérification des permissions
credentials_exception = HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to perform this action.",
)
try:
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
email: str = payload.get("sub")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
except JWTError:
raise credentials_exception
query = select(users_table).where(users_table.c.email == token_data.email)
result = await db.execute(query)
current_user = result.fetchone()
if current_user is None:
raise credentials_exception
# Vérifier si l'utilisateur est l'admin ou lui-même
if current_user["id"] != user_id and current_user["role"] != "admin":
raise credentials_exception
# Mise à jour des champs autorisés
allowed_updates = {"full_name", "phone", "date_of_birth", "organization", "email"}
updates = {key: value for key, value in updates.items() if key in allowed_updates}
if "email" in updates: # Vérifie si l'email existe déjà
existing_email_query = select(users_table).where(users_table.c.email == updates["email"])
result = await db.execute(existing_email_query)
existing_user = result.fetchone()
if existing_user and existing_user["id"] != user_id:
raise HTTPException(status_code=400, detail="Email already registered by another user.")
query = (
update(users_table)
.where(users_table.c.id == user_id)
.values(**updates)
.execution_options(synchronize_session="fetch")
)
try:
await db.execute(query)
await db.commit()
except Exception as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not update user: {str(e)}")
# Récupérer les informations mises à jour
updated_user_query = select(users_table).where(users_table.c.id == user_id)
result = await db.execute(updated_user_query)
updated_user = result.fetchone()
return UserResponse(**updated_user)