| 1 | from __future__ import annotations |
| 2 | |
| 3 | from fastapi import APIRouter, Depends, HTTPException, status |
| 4 | from fastapi.security import OAuth2PasswordRequestForm |
| 5 | from sqlalchemy import select |
| 6 | from sqlalchemy.orm import Session |
| 7 | |
| 8 | from app.api.deps import get_current_user, get_db |
| 9 | from app.core.security import create_access_token, hash_password, verify_password |
| 10 | from app.models import AuditLog, User |
| 11 | from app.schemas import Token, UserCreate, UserOut |
| 12 | |
| 13 | router = APIRouter(prefix="/auth", tags=["auth"]) |
| 14 | |
| 15 | |
| 16 | @router.post("/register", response_model=UserOut, status_code=201) |
| 17 | def register(payload: UserCreate, db: Session = Depends(get_db)) -> User: |
| 18 | if db.scalar(select(User).where(User.email == payload.email)): |
| 19 | raise HTTPException(status.HTTP_409_CONFLICT, "email already registered") |
| 20 | is_first = db.scalar(select(User).limit(1)) is None |
| 21 | user = User(email=payload.email, hashed_password=hash_password(payload.password), |
| 22 | full_name=payload.full_name, role="admin" if is_first else payload.role) |
| 23 | db.add(user) |
| 24 | db.flush() |
| 25 | db.add(AuditLog(action="user.register", entity_type="user", entity_id=user.id)) |
| 26 | db.commit() |
| 27 | db.refresh(user) |
| 28 | return user |
| 29 | |
| 30 | |
| 31 | @router.post("/login", response_model=Token) |
| 32 | def login(form: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)) -> Token: |
| 33 | user = db.scalar(select(User).where(User.email == form.username)) |
| 34 | if not user or not verify_password(form.password, user.hashed_password): |
| 35 | raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid credentials") |
| 36 | db.add(AuditLog(actor_user_id=user.id, action="user.login", entity_type="user", entity_id=user.id)) |
| 37 | db.commit() |
| 38 | return Token(access_token=create_access_token(user.id, user.role)) |
| 39 | |
| 40 | |
| 41 | @router.get("/me", response_model=UserOut) |
| 42 | def me(user: User = Depends(get_current_user)) -> User: |
| 43 | return user |