Barclays-banner-builder/backend/app/api/auth.py
Vadym Samoilenko 3f795c0d35 Replace passlib with bcrypt directly — passlib incompatible with bcrypt>=4.0
passlib hasn't been updated for bcrypt>=4.0 which removed __about__.
Use bcrypt library directly: hashpw/checkpw in auth.py and seed_admin.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:15:06 +01:00

86 lines
2.7 KiB
Python

from datetime import datetime, timedelta, timezone
import bcrypt
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import get_settings
from app.database import get_db
from app.models.user import User
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def verify_password(plain: str, hashed: str) -> bool:
return bcrypt.checkpw(plain.encode(), hashed.encode())
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
user_id: str | None = None
def create_access_token(data: dict) -> str:
settings = get_settings()
payload = data.copy()
payload["exp"] = datetime.now(timezone.utc) + timedelta(
minutes=settings.access_token_expire_minutes
)
return jwt.encode(payload, settings.secret_key, algorithm=settings.jwt_algorithm)
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
settings = get_settings()
credentials_exc = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.jwt_algorithm])
user_id = payload.get("sub")
if user_id is None:
raise credentials_exc
except JWTError:
raise credentials_exc
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if user is None:
raise credentials_exc
return user
@router.post("/token", response_model=Token)
async def login(
form_data: OAuth2PasswordRequestForm = Depends(),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(select(User).where(User.email == form_data.username))
user = result.scalar_one_or_none()
if not user or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
token = create_access_token({"sub": str(user.id)})
return {"access_token": token, "token_type": "bearer"}
@router.get("/me")
async def me(current_user: User = Depends(get_current_user)):
return {"id": str(current_user.id), "email": current_user.email, "role": current_user.role}