"""Authentication router endpoints.""" from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from typing import Optional from datetime import datetime from app.core.database import get_db from app.core.security import ( verify_password, hash_password, create_access_token, create_refresh_token, decode_token, ) from app.models.models import User from app.schemas.schemas import ( LoginRequest, LoginResponse, RefreshTokenRequest, TokenResponse, UserCreate, UserResponse, ) router = APIRouter() @router.post("/login", response_model=LoginResponse) async def login(request: LoginRequest, db: AsyncSession = Depends(get_db)): """Authenticate user and return JWT tokens.""" # Find user by username stmt = select(User).where(User.username == request.username) result = await db.execute(stmt) user = result.scalar_one_or_none() if not user or not verify_password(request.password, user.password_hash): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password", ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account is disabled", ) if user.is_banned and user.ban_ends and user.ban_ends > __import__("datetime").datetime.now(): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account is banned", ) # Update last login user.last_login = __import__("datetime").datetime.now() await db.flush() # Generate tokens access_token = create_access_token(str(user.id), user.privlevel or "User") refresh_token = create_refresh_token(str(user.id), user.privlevel or "User") return LoginResponse( access_token=access_token, refresh_token=refresh_token, user=UserResponse.model_validate(user).model_dump(), ) @router.post("/refresh", response_model=TokenResponse) async def refresh_token(request: RefreshTokenRequest, db: AsyncSession = Depends(get_db)): """Refresh access token using refresh token.""" payload = decode_token(request.refresh_token) if not payload or payload.get("type") != "refresh": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token", ) # Verify user still exists and is active user_id = payload["sub"] stmt = select(User).where(User.id == int(user_id)) result = await db.execute(stmt) user = result.scalar_one_or_none() if not user or not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User account is invalid", ) # Generate new access token access_token = create_access_token(str(user.id), user.privlevel or "User") return TokenResponse(access_token=access_token) @router.post("/register", response_model=UserResponse) async def register(request: UserCreate, db: AsyncSession = Depends(get_db)): """Register a new user account.""" # Check if username exists stmt = select(User).where(User.username == request.username) result = await db.execute(stmt) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Username already exists", ) # Check if email exists (if provided) if request.email: stmt = select(User).where(User.email == request.email) result = await db.execute(stmt) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Email already registered", ) # Create new user new_user = User( username=request.username, password_hash=hash_password(request.password), salt="random_salt", # In production, generate random salt email=request.email, country=request.country, real_name=request.real_name, ) db.add(new_user) await db.flush() return UserResponse.model_validate(new_user) @router.get("/me", response_model=UserResponse) async def get_current_user( token: str, db: AsyncSession = Depends(get_db), ): """Get current authenticated user.""" payload = decode_token(token) if not payload or payload.get("type") != "access": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", ) user_id = payload["sub"] stmt = select(User).where(User.id == int(user_id)) result = await db.execute(stmt) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) return UserResponse.model_validate(user)