"""Authentication API endpoints.""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.exc import IntegrityError from app.api.deps import get_auth_service, get_user_repository from app.core.security import password_hasher from app.models.organization import Organization from app.models.organization_member import OrganizationMember, OrganizationRole from app.models.token import LoginRequest, TokenResponse from app.models.user import UserCreate from app.repositories.user_repo import UserRepository from app.services.auth_service import AuthService, InvalidCredentialsError from .models import RegisterRequest router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED) async def register_user( payload: RegisterRequest, repo: UserRepository = Depends(get_user_repository), auth_service: AuthService = Depends(get_auth_service), ) -> TokenResponse: """Register a new owner along with the first organization and return JWT.""" existing = await repo.get_by_email(payload.email) if existing is not None: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="User already exists") organization = Organization(name=payload.organization_name) repo.session.add(organization) await repo.session.flush() user_data = UserCreate(email=payload.email, password=payload.password, name=payload.name) hashed_password = password_hasher.hash(payload.password) try: user = await repo.create(data=user_data, hashed_password=hashed_password) membership = OrganizationMember( organization_id=organization.id, user_id=user.id, role=OrganizationRole.OWNER, ) repo.session.add(membership) await repo.session.commit() except IntegrityError as exc: await repo.session.rollback() raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Organization or user already exists", ) from exc await repo.session.refresh(user) return auth_service.create_access_token(user) @router.post("/login", response_model=TokenResponse) async def login( credentials: LoginRequest, service: AuthService = Depends(get_auth_service), ) -> TokenResponse: """Authenticate user credentials and issue a JWT.""" try: user = await service.authenticate(credentials.email, credentials.password) except InvalidCredentialsError as exc: # pragma: no cover - thin API raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc return service.create_access_token(user) @router.post("/token", response_model=TokenResponse) async def login_for_access_token( credentials: LoginRequest, service: AuthService = Depends(get_auth_service), ) -> TokenResponse: try: user = await service.authenticate(credentials.email, credentials.password) except InvalidCredentialsError as exc: # pragma: no cover - thin API raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc return service.create_access_token(user)