import logging from fastapi import APIRouter, Depends, HTTPException, Request, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from sqlalchemy.exc import IntegrityError from app.dependencies import get_db from app.limiter import limiter from app.models.user import User from app.schemas.user import UserCreate, Token, LoginRequest from app.utils.security import hash_password, verify_password, create_token logger = logging.getLogger("bourbonacci.auth") router = APIRouter(prefix="/api/auth", tags=["auth"]) @router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED) @limiter.limit("5/minute") async def register(request: Request, body: UserCreate, db: AsyncSession = Depends(get_db)): result = await db.execute(select(User).where(User.email == body.email)) if result.scalar_one_or_none(): raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered") user = User( email=body.email, password_hash=hash_password(body.password), display_name=body.display_name or body.email.split("@")[0], ) db.add(user) try: await db.commit() except IntegrityError: await db.rollback() raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered") await db.refresh(user) logger.info("New user registered: %s", body.email) return Token(access_token=create_token(user.id)) @router.post("/login", response_model=Token) @limiter.limit("10/minute") async def login(request: Request, body: LoginRequest, db: AsyncSession = Depends(get_db)): result = await db.execute(select(User).where(User.email == body.email)) user = result.scalar_one_or_none() if not user or not verify_password(body.password, user.password_hash): logger.warning("Failed login for: %s", body.email) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") if user.is_disabled: logger.warning("Login attempt on disabled account: %s", body.email) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Account disabled") logger.info("Login: %s (id=%s)", body.email, user.id) return Token(access_token=create_token(user.id))