feat(tradein): auth на admin-эндпоинтах (#2) #383
4 changed files with 30 additions and 3 deletions
|
|
@ -9,11 +9,12 @@ import asyncio
|
|||
import logging
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.db import get_db
|
||||
from app.services.geocoder import geocode
|
||||
from app.services.scrapers.avito import AvitoScraper
|
||||
|
|
@ -27,6 +28,20 @@ logger = logging.getLogger(__name__)
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
def require_admin(
|
||||
x_admin_token: Annotated[str | None, Header()] = None,
|
||||
) -> None:
|
||||
"""Проверка admin-токена для /api/v1/admin/*.
|
||||
|
||||
Если settings.admin_token не задан (dev) — пропускаем (открыто).
|
||||
Если задан — требуем заголовок X-Admin-Token с этим значением.
|
||||
"""
|
||||
if settings.admin_token is None:
|
||||
return # dev-режим: токен не настроен → эндпоинты открыты
|
||||
if x_admin_token != settings.admin_token:
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing X-Admin-Token")
|
||||
|
||||
|
||||
class ScrapeRequest(BaseModel):
|
||||
lat: float
|
||||
lon: float
|
||||
|
|
@ -52,7 +67,7 @@ class ScrapeResponse(BaseModel):
|
|||
by_source: list[ScrapeResult]
|
||||
|
||||
|
||||
@router.post("/scrape", response_model=ScrapeResponse)
|
||||
@router.post("/scrape", response_model=ScrapeResponse, dependencies=[Depends(require_admin)])
|
||||
async def scrape_around(
|
||||
payload: ScrapeRequest,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
|
|
@ -121,7 +136,7 @@ def _clean_address_for_geocode(addr: str) -> str:
|
|||
return main or addr
|
||||
|
||||
|
||||
@router.post("/geocode-missing")
|
||||
@router.post("/geocode-missing", dependencies=[Depends(require_admin)])
|
||||
async def geocode_missing(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
limit: int = 100,
|
||||
|
|
|
|||
|
|
@ -18,5 +18,9 @@ class Settings(BaseSettings):
|
|||
# Public URL — для QR-кода в PDF, shareable links, etc.
|
||||
public_url: str = "http://127.0.0.1:8080"
|
||||
|
||||
# Admin token — защищает /api/v1/admin/* (scrape, geocode-missing).
|
||||
# Пусто = эндпоинты открыты (dev). В prod задаётся через env ADMIN_TOKEN.
|
||||
admin_token: str | None = None
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ build_payload() {
|
|||
|
||||
log "=== cron-scrape start: source=$SOURCE mode=$MODE container=$CONTAINER ==="
|
||||
|
||||
# X-Admin-Token заголовок — из ADMIN_TOKEN (.env.runtime). Если пусто — заголовок
|
||||
# всё равно шлём, backend в dev-режиме (admin_token=None) его игнорирует.
|
||||
ADMIN_HDR="X-Admin-Token: ${ADMIN_TOKEN:-}"
|
||||
|
||||
for anchor in "${ANCHORS[@]}"; do
|
||||
read -r lat lon name <<< "$anchor"
|
||||
payload=$(build_payload "$lat" "$lon" "$SOURCE" "$MODE")
|
||||
|
|
@ -57,6 +61,7 @@ for anchor in "${ANCHORS[@]}"; do
|
|||
resp=$(docker exec "$CONTAINER" curl -s -X POST \
|
||||
http://localhost:8000/api/v1/admin/scrape \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "$ADMIN_HDR" \
|
||||
-d "$payload" \
|
||||
-m 600 2>/dev/null || echo '{"error":"exec_or_timeout"}')
|
||||
log "← $name $resp"
|
||||
|
|
@ -70,6 +75,7 @@ log "→ geocode-missing (чанками)"
|
|||
for gi in $(seq 1 15); do
|
||||
geo=$(docker exec "$CONTAINER" curl -s -X POST \
|
||||
"http://localhost:8000/api/v1/admin/geocode-missing?limit=100" \
|
||||
-H "$ADMIN_HDR" \
|
||||
-m 320 2>/dev/null || echo '{"error":"geocode_timeout","remaining":0}')
|
||||
log "← geocode чанк $gi: $geo"
|
||||
remaining=$(echo "$geo" | grep -oE '"remaining":[0-9]+' | grep -oE '[0-9]+' || echo 0)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ services:
|
|||
ENVIRONMENT: "production"
|
||||
CONTACT_EMAIL: "${TRADEIN_CONTACT_EMAIL:-tradein@gendsgn.ru}"
|
||||
YANDEX_GEOCODER_KEY: "${YANDEX_GEOCODER_KEY:-}"
|
||||
# Защита /api/v1/admin/* — задаётся в .env.runtime. Пусто = открыто (dev).
|
||||
ADMIN_TOKEN: "${ADMIN_TOKEN:-}"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue