# app/api/v1/endpoints/auth.py import logging from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.schemas.user import UserCreate, UserPublic from app.schemas.auth import Token from app.crud import user as crud_user from app.core.security import verify_password, create_access_token logger = logging.getLogger(__name__) router = APIRouter() @router.post( "/signup", response_model=UserPublic, # Return public user info, not the password hash status_code=status.HTTP_201_CREATED, # Indicate resource creation summary="Register New User", description="Creates a new user account.", tags=["Authentication"] ) async def signup( user_in: UserCreate, db: AsyncSession = Depends(get_db) ): """ Handles user registration. - Validates input data. - Checks if email already exists. - Hashes the password. - Stores the new user in the database. """ logger.info(f"Signup attempt for email: {user_in.email}") existing_user = await crud_user.get_user_by_email(db, email=user_in.email) if existing_user: logger.warning(f"Signup failed: Email already registered - {user_in.email}") raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered.", ) try: created_user = await crud_user.create_user(db=db, user_in=user_in) logger.info(f"User created successfully: {created_user.email} (ID: {created_user.id})") # Note: UserPublic schema automatically excludes the hashed password return created_user except Exception as e: logger.error(f"Error during user creation for {user_in.email}: {e}", exc_info=True) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An error occurred during user creation.", ) @router.post( "/login", response_model=Token, summary="User Login", description="Authenticates a user and returns an access token.", tags=["Authentication"] ) async def login( form_data: OAuth2PasswordRequestForm = Depends(), # Use standard form for username/password db: AsyncSession = Depends(get_db) ): """ Handles user login. - Finds user by email (provided in 'username' field of form). - Verifies the provided password against the stored hash. - Generates and returns a JWT access token upon successful authentication. """ logger.info(f"Login attempt for user: {form_data.username}") user = await crud_user.get_user_by_email(db, email=form_data.username) # Check if user exists and password is correct # Use the correct attribute name 'password_hash' from the User model if not user or not verify_password(form_data.password, user.password_hash): # <-- CORRECTED LINE logger.warning(f"Login failed: Invalid credentials for user {form_data.username}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password", headers={"WWW-Authenticate": "Bearer"}, # Standard header for 401 ) # Generate JWT access_token = create_access_token(subject=user.email) # Use email as subject logger.info(f"Login successful, token generated for user: {user.email}") return Token(access_token=access_token, token_type="bearer")