"""Registration and login (section 17, additive). Public, but rate-limited like every other unauthenticated endpoint -- see app/core/security.py. """ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user from app.core.auth import create_access_token from app.core.database import get_db from app.core.ids import aurora_id from app.core.security import enforce_rate_limit from app.models.user import User from app.schemas.auth import LoginRequest, RegisterRequest, TokenOut, UserOut from app.services.user_service import ( EmailAlreadyRegisteredError, InvalidCredentialsError, authenticate_user, register_user, ) router = APIRouter(prefix="/auth", tags=["auth"], dependencies=[Depends(enforce_rate_limit)]) def _to_user_out(user: User) -> UserOut: return UserOut( id=aurora_id("user", str(user.id)), email=user.email, display_name=user.display_name, created_at=user.created_at, ) @router.post("/register", response_model=TokenOut, status_code=status.HTTP_201_CREATED) async def register(body: RegisterRequest, session: AsyncSession = Depends(get_db)) -> TokenOut: try: user = await register_user( session, email=body.email, password=body.password, display_name=body.display_name ) except EmailAlreadyRegisteredError as exc: raise HTTPException( status.HTTP_409_CONFLICT, {"code": "email_already_registered", "message": "An account with this email already exists."}, ) from exc await session.commit() return TokenOut(access_token=create_access_token(user.id)) @router.post("/login", response_model=TokenOut) async def login(body: LoginRequest, session: AsyncSession = Depends(get_db)) -> TokenOut: try: user = await authenticate_user(session, email=body.email, password=body.password) except InvalidCredentialsError as exc: # Deliberately identical whether the email doesn't exist or the # password is wrong (see authenticate_user) -- distinguishing them # here would let a client enumerate registered emails. raise HTTPException( status.HTTP_401_UNAUTHORIZED, {"code": "invalid_credentials", "message": "Incorrect email or password."}, ) from exc return TokenOut(access_token=create_access_token(user.id)) @router.get("/me", response_model=UserOut) async def get_me(user: User = Depends(get_current_user)) -> UserOut: return _to_user_out(user)