fix(tradein): убрать require_admin полностью — Caddy basic_auth достаточен
Доделка PR #437 (refactor: убрать X-Admin-Token UI / middleware). require_admin Depends остался в admin.py — блокировал /admin/scrape/* возвратом 401 для всех запросов без X-Admin-Token. Frontend /scrapers/avito (PR #472) не шлёт header (полагается на Caddy basic_auth gate), → 'Invalid or missing X-Admin-Token'. Изменения: - admin.py: удалена функция require_admin + 7 Depends() в endpoint decorators (/scrape, /geocode-missing, /scrape/cian/upload-cookies, /scrape/cian/test-auth, /scrape/avito-house, /scrape/avito-detail, /scrape/avito-imv) - config.py: удалено settings.admin_token field - cron-scrape.sh: удалён ADMIN_HDR + X-Admin-Token из всех curl - upload-cian-cookies.sh: удалён TRADEIN_ADMIN_TOKEN loading + header Auth contract: Caddy basic_auth gate'ит /trade-in/api/v1/admin/* (через handle_path конфиг). Application layer — open после Caddy unwrap. ADMIN_TOKEN env переменная больше не используется backend'ом, но её можно держать в .env.runtime как dead reference (без последствий).
This commit is contained in:
parent
89c4a2842d
commit
40c6f8b30f
4 changed files with 11 additions and 50 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"""Admin endpoints — debug + ручной запуск парсеров.
|
||||
|
||||
Пока без auth (Слой 7), потом закроем общим middleware.
|
||||
Auth: Caddy basic_auth gate'ит /trade-in/api/v1/admin/* — application layer открыт.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -9,7 +9,7 @@ import logging
|
|||
import time
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
|
@ -37,20 +37,6 @@ 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
|
||||
|
|
@ -76,7 +62,7 @@ class ScrapeResponse(BaseModel):
|
|||
by_source: list[ScrapeResult]
|
||||
|
||||
|
||||
@router.post("/scrape", response_model=ScrapeResponse, dependencies=[Depends(require_admin)])
|
||||
@router.post("/scrape", response_model=ScrapeResponse)
|
||||
async def scrape_around(
|
||||
payload: ScrapeRequest,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
|
|
@ -145,7 +131,7 @@ def _clean_address_for_geocode(addr: str) -> str:
|
|||
return main or addr
|
||||
|
||||
|
||||
@router.post("/geocode-missing", dependencies=[Depends(require_admin)])
|
||||
@router.post("/geocode-missing")
|
||||
async def geocode_missing(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
limit: int = 100,
|
||||
|
|
@ -244,7 +230,7 @@ async def geocode_missing(
|
|||
}
|
||||
|
||||
|
||||
@router.post("/scrape/cian/upload-cookies", status_code=200, dependencies=[Depends(require_admin)])
|
||||
@router.post("/scrape/cian/upload-cookies", status_code=200)
|
||||
async def upload_cian_cookies(
|
||||
cookies: dict[str, str],
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
|
|
@ -290,7 +276,7 @@ async def upload_cian_cookies(
|
|||
return {"ok": True, "userId": user_id, "cookieCount": len(cleaned)}
|
||||
|
||||
|
||||
@router.get("/scrape/cian/test-auth", status_code=200, dependencies=[Depends(require_admin)])
|
||||
@router.get("/scrape/cian/test-auth", status_code=200)
|
||||
async def test_cian_auth(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
) -> dict:
|
||||
|
|
@ -316,7 +302,7 @@ async def test_cian_auth(
|
|||
# ── Stage 4a: Avito enrichment endpoints (single-shot debug + manual triggers) ──
|
||||
|
||||
|
||||
@router.post("/scrape/avito-house", dependencies=[Depends(require_admin)])
|
||||
@router.post("/scrape/avito-house")
|
||||
async def scrape_avito_house(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
house_url: str,
|
||||
|
|
@ -344,7 +330,7 @@ async def scrape_avito_house(
|
|||
return {"ok": True, "house_url": house_url, "counters": counters}
|
||||
|
||||
|
||||
@router.post("/scrape/avito-detail", dependencies=[Depends(require_admin)])
|
||||
@router.post("/scrape/avito-detail")
|
||||
async def scrape_avito_detail(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
item_url: str,
|
||||
|
|
@ -376,7 +362,7 @@ async def scrape_avito_detail(
|
|||
return {"ok": True, "item_id": enrichment.item_id, "updated": updated}
|
||||
|
||||
|
||||
@router.post("/scrape/avito-imv", dependencies=[Depends(require_admin)])
|
||||
@router.post("/scrape/avito-imv")
|
||||
async def scrape_avito_imv(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
address: str,
|
||||
|
|
|
|||
|
|
@ -21,10 +21,6 @@ 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
|
||||
|
||||
# GlitchTip DSN — мониторинг ошибок (Sentry-совместимый). #396.
|
||||
# Пусто = мониторинг выключен (dev). В prod — env GLITCHTIP_DSN из .env.runtime.
|
||||
glitchtip_dsn: str | None = None
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
# cron запускается с ПУСТЫМ окружением — ADMIN_TOKEN в нём нет, и без него
|
||||
# backend (#2 admin-auth) отвечает 401. Подхватываем .env.runtime сами:
|
||||
# cron запускается с ПУСТЫМ окружением. Подхватываем .env.runtime сами:
|
||||
# он лежит на уровень выше скрипта — tradein-mvp/.env.runtime.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_RUNTIME="${ENV_RUNTIME:-$SCRIPT_DIR/../.env.runtime}"
|
||||
|
|
@ -61,10 +60,6 @@ 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")
|
||||
|
|
@ -73,7 +68,6 @@ 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"
|
||||
|
|
@ -87,7 +81,6 @@ log "→ geocode-missing listings (чанками)"
|
|||
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&target=listings" \
|
||||
-H "$ADMIN_HDR" \
|
||||
-m 320 2>/dev/null || echo '{"error":"geocode_timeout","remaining":0}')
|
||||
log "← listings чанк $gi: $geo"
|
||||
remaining=$(echo "$geo" | grep -oE '"remaining":[0-9]+' | grep -oE '[0-9]+' || echo 0)
|
||||
|
|
@ -100,7 +93,6 @@ log "→ geocode-missing deals (чанками)"
|
|||
for gi in $(seq 1 30); do
|
||||
geo=$(docker exec "$CONTAINER" curl -s -X POST \
|
||||
"http://localhost:8000/api/v1/admin/geocode-missing?limit=100&target=deals" \
|
||||
-H "$ADMIN_HDR" \
|
||||
-m 320 2>/dev/null || echo '{"error":"geocode_timeout","remaining":0}')
|
||||
log "← deals чанк $gi: $geo"
|
||||
remaining=$(echo "$geo" | grep -oE '"remaining":[0-9]+' | grep -oE '[0-9]+' || echo 0)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
# или через расширение Cookie-Editor → Export → JSON (object format).
|
||||
#
|
||||
# Переменные окружения:
|
||||
# TRADEIN_ADMIN_TOKEN — admin token (или берётся из .env.runtime)
|
||||
# TRADEIN_BASE_URL — base URL (default: https://gendsgn.ru/trade-in)
|
||||
# Auth: Caddy basic_auth (auth handled by curl --user / browser session).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -21,24 +21,12 @@ if [[ ! -f "$COOKIES_FILE" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Загружаем токен из env или .env.runtime
|
||||
ADMIN_TOKEN="${TRADEIN_ADMIN_TOKEN:-}"
|
||||
if [[ -z "$ADMIN_TOKEN" ]] && [[ -f ".env.runtime" ]]; then
|
||||
ADMIN_TOKEN="$(grep '^TRADEIN_ADMIN_TOKEN=' .env.runtime 2>/dev/null | cut -d= -f2- || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$ADMIN_TOKEN" ]]; then
|
||||
echo "Error: TRADEIN_ADMIN_TOKEN not set (env or .env.runtime)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_URL="${TRADEIN_BASE_URL:-https://gendsgn.ru/trade-in}"
|
||||
|
||||
echo "Uploading Cian cookies from $COOKIES_FILE to $BASE_URL ..."
|
||||
echo
|
||||
|
||||
curl -fsSL -X POST "$BASE_URL/api/v1/admin/scrape/cian/upload-cookies" \
|
||||
-H "X-Admin-Token: $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "@$COOKIES_FILE" \
|
||||
| python3 -m json.tool
|
||||
|
|
@ -46,5 +34,4 @@ curl -fsSL -X POST "$BASE_URL/api/v1/admin/scrape/cian/upload-cookies" \
|
|||
echo
|
||||
echo "Verifying session:"
|
||||
curl -fsSL "$BASE_URL/api/v1/admin/scrape/cian/test-auth" \
|
||||
-H "X-Admin-Token: $ADMIN_TOKEN" \
|
||||
| python3 -m json.tool
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue