from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException, Request, status from redis.asyncio import Redis from app.config import Settings from app.core.ip_utils import extract_client_ip from app.core.security import ( clear_login_failures, create_admin_jwt, ensure_login_allowed, register_login_failure, verify_admin_password, ) from app.dependencies import get_redis, get_settings from app.schemas.auth import LoginRequest, TokenResponse logger = logging.getLogger(__name__) router = APIRouter(prefix="/admin/api", tags=["auth"]) @router.post("/login", response_model=TokenResponse) async def login( payload: LoginRequest, request: Request, settings: Settings = Depends(get_settings), redis: Redis | None = Depends(get_redis), ) -> TokenResponse: if redis is None: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Login service is unavailable because Redis is offline.", ) client_ip = extract_client_ip(request, settings) await ensure_login_allowed(redis, client_ip, settings) if not verify_admin_password(payload.password, settings): await register_login_failure(redis, client_ip, settings) logger.warning("Admin login failed.", extra={"client_ip": client_ip}) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid admin password.") await clear_login_failures(redis, client_ip) token, expires_in = create_admin_jwt(settings) logger.info("Admin login succeeded.", extra={"client_ip": client_ip}) return TokenResponse(access_token=token, expires_in=expires_in)