diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py new file mode 100644 index 0000000..48d9918 --- /dev/null +++ b/backend/app/routers/auth.py @@ -0,0 +1,59 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy import func, select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session + +from .. import models, schemas +from ..database import get_db +from ..deps import get_current_user +from ..security import create_access_token, hash_password, verify_password + +router = APIRouter(prefix="/auth", tags=["auth"]) + + +@router.post("/register", response_model=schemas.UserOut, status_code=status.HTTP_201_CREATED) +def register(payload: schemas.UserCreate, db: Session = Depends(get_db)): + """Регистрирует клиента; первого пользователя назначает администратором.""" + email = payload.email.lower() + exists = db.scalar(select(models.User).where(models.User.email == email)) + if exists: + raise HTTPException(status_code=409, detail="Пользователь уже существует") + + # Роль первого пользователя определяется внутри транзакции и защищается + # уникальным email-ограничением от повторной регистрации одного адреса. + first_user = db.scalar(select(func.count(models.User.id))) == 0 + user = models.User( + email=email, + password_hash=hash_password(payload.password), + role=models.UserRole.admin if first_user else models.UserRole.client, + ) + db.add(user) + try: + db.commit() + except IntegrityError: + db.rollback() + raise HTTPException(status_code=409, detail="Пользователь уже существует") + db.refresh(user) + return user + + +@router.post("/login", response_model=schemas.TokenOut) +def login( + form_data: OAuth2PasswordRequestForm = Depends(), + db: Session = Depends(get_db), +): + """Проверяет credentials и выдаёт Bearer JWT.""" + email = form_data.username.lower() + user = db.scalar(select(models.User).where(models.User.email == email)) + if user is None or not user.is_active or not verify_password( + form_data.password, user.password_hash + ): + raise HTTPException(status_code=401, detail="Неверный email или пароль") + return schemas.TokenOut(access_token=create_access_token(str(user.id))) + + +@router.get("/me", response_model=schemas.UserOut) +def me(user: models.User = Depends(get_current_user)): + """Возвращает профиль текущего пользователя без секретных данных.""" + return user