Files
WealthWise/backend/app/api/v1/endpoints/auth.py

131 lines
4.1 KiB
Python
Raw Normal View History

"""Authentication endpoints for WealthWise.
This module provides endpoints for user registration, login, and token management
using OAuth2 with Password Flow (JWT-based authentication).
"""
from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy import select
from sqlmodel.ext.asyncio.session import AsyncSession
from app.api.deps import SessionDep
from app.core.security import create_access_token, get_password_hash, verify_password
from app.models import User
from app.schemas.user import Token, UserCreate, UserPublic
router = APIRouter()
@router.post(
"/register",
response_model=UserPublic,
status_code=status.HTTP_201_CREATED,
summary="Register a new user",
description="Create a new user account with email and password.",
response_description="Created user information (excluding password)",
tags=["Authentication"],
)
async def register(
user_in: UserCreate,
session: AsyncSession = SessionDep,
) -> UserPublic:
"""Register a new user account.
This endpoint creates a new user with the provided email and password.
The password is hashed using bcrypt before storage. If the email already
exists, a 400 error is returned.
Args:
user_in: User creation data with email and password
session: Database session
Returns:
UserPublic: Created user information (without password)
Raises:
HTTPException: 400 if email already registered
"""
# Check if user with this email already exists
statement = select(User).where(User.email == user_in.email)
result = await session.exec(statement)
existing_user = result.first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered",
)
# Create new user with hashed password
user = User(
email=user_in.email,
hashed_password=get_password_hash(user_in.password),
is_active=True,
is_superuser=False,
)
session.add(user)
await session.commit()
await session.refresh(user)
return user
@router.post(
"/token",
response_model=Token,
summary="Login and get access token",
description="Authenticate with email and password to receive a JWT access token.",
response_description="JWT access token for authenticated requests",
tags=["Authentication"],
)
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
session: AsyncSession = SessionDep,
) -> Token:
"""Authenticate user and return JWT access token.
This endpoint accepts username (email) and password via OAuth2 form data,
validates the credentials against the database, and returns a JWT access
token if authentication is successful.
Args:
form_data: OAuth2 form with username (email) and password
session: Database session
Returns:
Token: JWT access token with bearer type
Raises:
HTTPException: 401 if credentials are invalid
"""
# Find user by email (username field in OAuth2 form)
statement = select(User).where(User.email == form_data.username)
result = await session.exec(statement)
user = result.first()
# Validate user exists, is active, and password is correct
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Create access token
access_token = create_access_token(
data={"sub": str(user.id)},
)
return Token(access_token=access_token, token_type="bearer")