Changes committed

This commit is contained in:
2026-06-01 21:49:53 +05:30
parent 8537c653c1
commit 3163bb213e
387 changed files with 21940 additions and 107 deletions

View File

@@ -0,0 +1,246 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.core.config import settings
from app.core.database import get_db
from app.core.dependencies import CurrentUser
from app.core.security import (
create_access_token,
create_refresh_token,
decode_token,
hash_password,
verify_password,
InvalidTokenError,
)
from app.repositories.user_repository import RefreshTokenRepository, UserRepository
from app.schemas.auth import (
ChangePasswordRequest,
LoginRequest,
RefreshTokenRequest,
RegisterRequest,
TokenResponse,
)
from app.schemas.common import SuccessResponse
from app.schemas.user import UserResponse
router = APIRouter(prefix="/auth", tags=["Authentication"])
@router.post(
"/register",
response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
summary="Register User",
description="Register a new user account.",
)
def register(
payload: RegisterRequest,
db: Session = Depends(get_db),
) -> UserResponse:
"""Register a new user."""
user_repo = UserRepository(db)
# Check for existing user
if user_repo.get_by_username(payload.username):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Username '{payload.username}' is already taken",
)
if user_repo.get_by_email(payload.email):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Email '{payload.email}' is already registered",
)
hashed = hash_password(payload.password)
user = user_repo.create_user(
username=payload.username,
email=payload.email,
hashed_password=hashed,
full_name=payload.full_name,
role_names=["user"],
)
db.commit()
db.refresh(user)
return UserResponse.model_validate(user)
@router.post(
"/login",
response_model=TokenResponse,
summary="Login",
description="Authenticate with username and password to obtain JWT tokens.",
)
def login(
payload: LoginRequest,
db: Session = Depends(get_db),
) -> TokenResponse:
"""Authenticate user and return JWT tokens."""
user_repo = UserRepository(db)
refresh_repo = RefreshTokenRepository(db)
user = user_repo.get_by_username(payload.username)
if not user or not verify_password(payload.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User account is deactivated",
)
# Generate tokens
access_token = create_access_token(data={"sub": str(user.id), "username": user.username})
refresh_token_str = create_refresh_token(data={"sub": str(user.id)})
# Store refresh token
expires_at = datetime.now(UTC) + timedelta(days=settings.jwt_refresh_token_expire_days)
refresh_repo.create_token(
user_id=user.id,
token=refresh_token_str,
expires_at=expires_at,
)
# Update last login
user_repo.update_last_login(user)
db.commit()
return TokenResponse(
access_token=access_token,
refresh_token=refresh_token_str,
token_type="bearer",
expires_in=settings.jwt_access_token_expire_minutes * 60,
)
@router.post(
"/refresh",
response_model=TokenResponse,
summary="Refresh Token",
description="Obtain a new access token using a valid refresh token.",
)
def refresh_token(
payload: RefreshTokenRequest,
db: Session = Depends(get_db),
) -> TokenResponse:
"""Refresh access token using a refresh token."""
refresh_repo = RefreshTokenRepository(db)
user_repo = UserRepository(db)
# Validate the refresh token
try:
token_payload = decode_token(payload.refresh_token)
except InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired refresh token",
)
if token_payload.get("type") != "refresh":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token type",
)
# Check if token exists in database and is not revoked
stored_token = refresh_repo.get_by_token(payload.refresh_token)
if not stored_token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Refresh token not found or revoked",
)
user = user_repo.get_by_id(token_payload["sub"])
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found or deactivated",
)
# Revoke old refresh token
refresh_repo.revoke_token(payload.refresh_token)
# Generate new tokens
new_access_token = create_access_token(data={"sub": str(user.id), "username": user.username})
new_refresh_token = create_refresh_token(data={"sub": str(user.id)})
expires_at = datetime.now(UTC) + timedelta(days=settings.jwt_refresh_token_expire_days)
refresh_repo.create_token(
user_id=user.id,
token=new_refresh_token,
expires_at=expires_at,
)
db.commit()
return TokenResponse(
access_token=new_access_token,
refresh_token=new_refresh_token,
token_type="bearer",
expires_in=settings.jwt_access_token_expire_minutes * 60,
)
@router.post(
"/logout",
response_model=SuccessResponse,
summary="Logout",
description="Revoke the current refresh token.",
)
def logout(
payload: RefreshTokenRequest,
current_user: CurrentUser,
db: Session = Depends(get_db),
) -> SuccessResponse:
"""Logout by revoking the refresh token."""
refresh_repo = RefreshTokenRepository(db)
refresh_repo.revoke_token(payload.refresh_token)
db.commit()
return SuccessResponse(message="Successfully logged out")
@router.post(
"/change-password",
response_model=SuccessResponse,
summary="Change Password",
description="Change the current user's password.",
)
def change_password(
payload: ChangePasswordRequest,
current_user: CurrentUser,
db: Session = Depends(get_db),
) -> SuccessResponse:
"""Change user password."""
if not verify_password(payload.current_password, current_user.hashed_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Current password is incorrect",
)
current_user.hashed_password = hash_password(payload.new_password)
# Revoke all refresh tokens for security
refresh_repo = RefreshTokenRepository(db)
refresh_repo.revoke_all_user_tokens(current_user.id)
db.commit()
return SuccessResponse(message="Password changed successfully")
@router.get(
"/me",
response_model=UserResponse,
summary="Get Current User",
description="Get the currently authenticated user's profile.",
)
def get_me(current_user: CurrentUser) -> UserResponse:
"""Get current authenticated user profile."""
return UserResponse.model_validate(current_user)