RAG_Backend / backend /app /api /auth.py
Madhesh4124's picture
Major Chunking,Retrieval and Ranking Updates
e907a44
Raw
History Blame Contribute Delete
4.93 kB
from fastapi import APIRouter, Depends, HTTPException, Response, status, BackgroundTasks
from sqlalchemy import or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth import (
clear_auth_cookie, create_session_token, get_current_user, hash_password,
set_auth_cookie, verify_password, create_password_reset_token,
decode_password_reset_token
)
from app.database import get_db
from app.models.user import (
User, UserLoginRequest, UserResponse, UserSignupRequest,
PasswordResetRequestBody, PasswordResetConfirmBody, PasswordResetResponse
)
from app.services.email_service import EmailService
from itsdangerous import BadSignature
router = APIRouter(prefix="/api/auth", tags=["auth"])
email_service = EmailService()
@router.post("/signup", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def signup(payload: UserSignupRequest, response: Response, db: AsyncSession = Depends(get_db)):
stmt = select(User).where(or_(User.username == payload.username, User.email == payload.email))
existing = (await db.execute(stmt)).scalars().first()
if existing:
raise HTTPException(status_code=409, detail="Username or email already in use")
user = User(
username=payload.username.strip(),
email=payload.email.strip().lower(),
password_hash=hash_password(payload.password),
)
db.add(user)
await db.commit()
await db.refresh(user)
token = create_session_token(user.id)
set_auth_cookie(response, token)
return user
@router.post("/login", response_model=UserResponse)
async def login(payload: UserLoginRequest, response: Response, db: AsyncSession = Depends(get_db)):
identifier = payload.identifier.strip()
stmt = select(User).where(or_(User.username == identifier, User.email == identifier.lower()))
user = (await db.execute(stmt)).scalars().first()
if not user or not verify_password(payload.password, user.password_hash):
raise HTTPException(status_code=401, detail="Invalid credentials")
token = create_session_token(user.id)
set_auth_cookie(response, token)
return user
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
async def logout(response: Response):
clear_auth_cookie(response)
return None
@router.get("/me", response_model=UserResponse)
async def me(current_user: User = Depends(get_current_user)):
return current_user
@router.post("/password-reset/request", status_code=status.HTTP_200_OK)
async def request_password_reset(
payload: PasswordResetRequestBody,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
):
"""Request a password reset email. Always returns success for security."""
stmt = select(User).where(User.email == payload.email.lower())
user = (await db.execute(stmt)).scalars().first()
if user:
reset_token = create_password_reset_token(user.id)
background_tasks.add_task(email_service.send_password_reset_email, user.email, reset_token)
# Always return success to prevent email enumeration attacks
return PasswordResetResponse(
success=True,
message="If an account exists with this email, a password reset link has been sent."
)
@router.post("/password-reset/confirm", response_model=PasswordResetResponse)
async def confirm_password_reset(
payload: PasswordResetConfirmBody,
db: AsyncSession = Depends(get_db),
):
"""Confirm password reset with token and new password."""
try:
user_id = decode_password_reset_token(payload.token)
except BadSignature:
raise HTTPException(status_code=400, detail="Invalid or expired reset token")
stmt = select(User).where(User.id == user_id)
user = (await db.execute(stmt)).scalars().first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
user.password_hash = hash_password(payload.new_password)
from datetime import datetime, timezone
user.password_reset_count = datetime.now(timezone.utc)
await db.commit()
return PasswordResetResponse(
success=True,
message="Password has been reset successfully. Please login with your new password."
)
@router.post("/password-change", status_code=status.HTTP_200_OK)
async def change_password(
old_password: str,
new_password: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Change password for authenticated user."""
if not verify_password(old_password, current_user.password_hash):
raise HTTPException(status_code=401, detail="Current password is incorrect")
current_user.password_hash = hash_password(new_password)
from datetime import datetime, timezone
current_user.password_reset_count = datetime.now(timezone.utc)
await db.commit()
return {"success": True, "message": "Password changed successfully"}