"""Authentication API endpoints and payloads.""" from __future__ import annotations from pydantic import BaseModel, EmailStr 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, RefreshRequest, TokenResponse from app.models.user import UserCreate from app.repositories.user_repo import UserRepository from app.services.auth_service import AuthService, InvalidCredentialsError, InvalidRefreshTokenError class RegisterRequest(BaseModel): email: EmailStr password: str name: str organization_name: str 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.issue_tokens(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.issue_tokens(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.issue_tokens(user) @router.post("/refresh", response_model=TokenResponse) async def refresh_tokens( payload: RefreshRequest, service: AuthService = Depends(get_auth_service), ) -> TokenResponse: try: return await service.refresh_tokens(payload.refresh_token) except InvalidRefreshTokenError as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc