91 lines
3.4 KiB
Python
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") |