"""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, timezone, timezone 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 > datetime.now(): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account is banned", ) # Update last login user.last_login = datetime.now(timezone.utc) 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)