doe/be/app/api/v1/endpoints/auth.py
2025-03-30 19:42:32 +02:00

91 lines
3.4 KiB
Python

# 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")