fix: separate admin page and API authentication
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s

This commit is contained in:
ik
2026-09-09 07:29:39 +07:00
parent eb501affb8
commit c6fdc969c2
9 changed files with 61 additions and 12 deletions
+2 -2
View File
@@ -18,7 +18,7 @@ Web Docker-образ устанавливает зависимости чере
Идёт исправление аудита: актуальные изменения и ограничения перечислены в [AUDIT_FIXES.md](docs/AUDIT_FIXES.md). Production Compose включает community scheduler; страницы rules/privacy реализованы. Для запуска остаются сервер, DNS/TLS, секреты, внешний backup и контакты. Шкала 72 часов использует полную выборку по времени поступления; одинаковые поля разных источников больше не считаются доказательством одного события. Фоновая публикация обновляет кэш API в пределах TTL, не мгновенно. Идёт исправление аудита: актуальные изменения и ограничения перечислены в [AUDIT_FIXES.md](docs/AUDIT_FIXES.md). Production Compose включает community scheduler; страницы rules/privacy реализованы. Для запуска остаются сервер, DNS/TLS, секреты, внешний backup и контакты. Шкала 72 часов использует полную выборку по времени поступления; одинаковые поля разных источников больше не считаются доказательством одного события. Фоновая публикация обновляет кэш API в пределах TTL, не мгновенно.
Полный аудит 8 сентября: [отчёт](docs/PROJECT_AUDIT_2026-09-08.md), [приоритетный план](docs/ROADMAP.md#ближайший-рабочий-пакет). T01 выполнен: Caddy-маршруты формы исправлены и проверены через временный proxy командой `sh deploy/test-proxy-routing.sh` на запущенном локальном Compose. Перед production остаются конфликт Basic/Bearer admin-аутентификации и исходящая сеть community scheduler. Затем — клиентский rate limit, надёжность парсеров, согласованность фильтров/оценок и HTTP/SEO-контракт ошибок. Полный аудит 8 сентября: [отчёт](docs/PROJECT_AUDIT_2026-09-08.md), [приоритетный план](docs/ROADMAP.md#ближайший-рабочий-пакет). T01/T02 выполнены: исправлены маршруты формы и конфликт admin-аутентификации. Проверка `sh deploy/test-proxy-routing.sh` на запущенном локальном Compose проверяет Astro redirects, API, Basic-доступ к страницам и Bearer-доступ к API, включая отказы. Следующий блокер — исходящая сеть community scheduler (T03). Затем — клиентский rate limit, надёжность парсеров, согласованность фильтров/оценок и HTTP/SEO-контракт ошибок.
На ширинах 320, 390, 768 и 1280 px ранее проверено отсутствие горизонтального переполнения основных страниц. Это не полная визуальная приёмка: аудит обнаружил неверную desktop-компоновку фильтров; наполненные карточки, длинные названия, клавиатура и zoom остаются отдельной задачей. На ширинах 320, 390, 768 и 1280 px ранее проверено отсутствие горизонтального переполнения основных страниц. Это не полная визуальная приёмка: аудит обнаружил неверную desktop-компоновку фильтров; наполненные карточки, длинные названия, клавиатура и zoom остаются отдельной задачей.
@@ -59,7 +59,7 @@ FastAPI ─ PostgreSQL 17
└ MinIO/S3 └ MinIO/S3
``` ```
Наружу production-профиль публикует только Caddy. PostgreSQL, API, Astro и MinIO находятся во внутренних Docker-сетях. Caddy завершает TLS и защищает административные маршруты Basic Auth; административный API дополнительно требует bearer token. Наружу production-профиль публикует только Caddy. PostgreSQL, API, Astro и MinIO находятся в Docker-сетях. Caddy завершает TLS и защищает административные страницы Basic Auth; административный API отдельно проверяет Bearer-токен в FastAPI. Basic не накладывается на API-запросы.
Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты. Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты.
+2 -3
View File
@@ -25,9 +25,8 @@
@adminApi path /api/v1/admin/* @adminApi path /api/v1/admin/*
handle @adminApi { handle @adminApi {
basic_auth { # FastAPI verifies the Bearer token. Basic Auth here would reject the
{$ADMIN_BASIC_USER} {$ADMIN_BASIC_PASSWORD_HASH} # browser's Bearer Authorization header before it reached the API.
}
reverse_proxy api:8000 reverse_proxy api:8000
} }
+1 -1
View File
@@ -1,6 +1,6 @@
# Развёртывание открытой альфы rf4spotter.ru # Развёртывание открытой альфы rf4spotter.ru
Production-контур рассчитан на один Linux-сервер с Docker Compose. Наружу публикуются только Caddy `80/443`; PostgreSQL, FastAPI и MinIO не имеют host-портов. Административные страницы защищены одновременно Caddy Basic Auth и API bearer token. Production-контур рассчитан на один Linux-сервер с Docker Compose. Наружу публикуются только Caddy `80/443`; PostgreSQL, FastAPI и MinIO не имеют host-портов. Административные страницы защищены Caddy Basic Auth, административный API — Bearer-токеном FastAPI. API-запросы не требуют Basic: обе схемы используют заголовок Authorization и не могут накладываться на один запрос.
Текущее состояние публичных DNS/TLS и незакрытые инфраструктурные действия ведутся в [`docs/deployment-status.md`](../docs/deployment-status.md). Текущее состояние публичных DNS/TLS и незакрытые инфраструктурные действия ведутся в [`docs/deployment-status.md`](../docs/deployment-status.md).
+44
View File
@@ -0,0 +1,44 @@
"""Read-only checks through a temporary proxy, run inside the local API container."""
import base64
import json
import os
from urllib.error import HTTPError
from urllib.request import Request, urlopen
base = os.environ["RF4_PROXY_TEST_URL"]
token = os.environ["ADMIN_TOKEN"]
basic = "Basic " + base64.b64encode(b"route-test:hiccup").decode()
def probe(path, expected, authorization=None, method="GET", data=None):
headers = {"Host": "localhost:8080"}
if authorization:
headers["Authorization"] = authorization
if data is not None:
headers["Content-Type"] = "application/json"
request = Request(base + path, headers=headers, method=method, data=data)
try:
response = urlopen(request, timeout=15)
except HTTPError as error:
response = error
with response:
assert response.status == expected, f"{method} {path}: expected {expected}, got {response.status}"
if path.startswith("/api/v1/admin/") and expected == 200:
assert "no-store" in response.headers.get("Cache-Control", "")
assert isinstance(json.load(response), dict)
probe("/admin/moderation", 401)
probe("/admin/moderation", 200, basic)
probe("/admin/moderation", 401, "Bearer " + token)
for path in ("/api/v1/admin/diagnostics", "/api/v1/admin/catch-reports"):
probe(path, 401)
probe(path, 401, basic)
probe(path, 401, "Bearer invalid-routing-test-token")
probe("/api/v1/admin/diagnostics", 200, "Bearer " + token)
# Invalid UUID prevents writes while checking the moderation request path.
probe("/api/v1/admin/catch-reports/not-a-uuid", 401, method="PATCH", data=b'{"status":"approved"}')
probe("/api/v1/admin/catch-reports/not-a-uuid", 422, "Bearer " + token,
method="PATCH", data=b'{"status":"approved"}')
print("Admin auth passed: Basic pages, Bearer API, rejected missing/wrong credentials")
+2
View File
@@ -44,4 +44,6 @@ check_redirect /api/report '/report?state=create_error'
check_redirect /api/report-screenshot '/report?state=screenshot_error&report_id=' check_redirect /api/report-screenshot '/report?state=screenshot_error&report_id='
curl -fsS --max-time 15 -H 'Host: localhost:8080' "$base/api/v1/fishes?limit=1" >/dev/null curl -fsS --max-time 15 -H 'Host: localhost:8080' "$base/api/v1/fishes?limit=1" >/dev/null
curl -fsS --max-time 15 -H 'Host: localhost:8080' "$base/ready" >/dev/null curl -fsS --max-time 15 -H 'Host: localhost:8080' "$base/ready" >/dev/null
docker compose exec -T -e "RF4_PROXY_TEST_URL=http://$container:8080" api \
python - < "$repo/deploy/probe-admin-auth.py"
echo "Proxy routing passed: Astro form redirects, FastAPI catalog and health/readiness" echo "Proxy routing passed: Astro form redirects, FastAPI catalog and health/readiness"
+2
View File
@@ -4,6 +4,8 @@
## Исправлено в рабочей версии ## Исправлено в рабочей версии
- [x] T02: разделены Basic для административных страниц и Bearer для admin API. 9 сентября проверены положительные/отрицательные сценарии через Caddy; API сохраняет обязательную проверку токена и no-store.
- [x] T01 полного аудита: маршруты `/api/report` и `/api/report-screenshot` достигают Astro через Caddy; FastAPI получает `/api/v1/*`, health/readiness. 9 сентября: `sh deploy/test-proxy-routing.sh` проходит на локальном стенде с корректным Origin; создание реальной заявки через production proxy остаётся частью T06. - [x] T01 полного аудита: маршруты `/api/report` и `/api/report-screenshot` достигают Astro через Caddy; FastAPI получает `/api/v1/*`, health/readiness. 9 сентября: `sh deploy/test-proxy-routing.sh` проходит на локальном стенде с корректным Origin; создание реальной заявки через production proxy остаётся частью T06.
- [x] Очередь внешних наблюдений фильтруется в SQL до limit/offset; UI показывает по 50 записей с навигацией, опубликованные записи не вытесняют ожидающие проверки. - [x] Очередь внешних наблюдений фильтруется в SQL до limit/offset; UI показывает по 50 записей с навигацией, опубликованные записи не вытесняют ожидающие проверки.
+1 -1
View File
@@ -1,6 +1,6 @@
# Аудит RF4 Spotter — 8 сентября 2026 # Аудит RF4 Spotter — 8 сентября 2026
Обновление 9 сентября: T01 исправлен (`bb61d21`) и проверен скриптом `deploy/test-proxy-routing.sh`. Ниже сохранены исходные находки аудита; актуальные отметки выполнения — в ROADMAP. T02/T03 остаются блокерами production. Обновление 9 сентября: T01 исправлен (`bb61d21`), T02 закрыт разделением Basic для страниц и Bearer для API; оба проверены скриптом `deploy/test-proxy-routing.sh`. Ниже сохранены исходные находки аудита; актуальные отметки выполнения — в ROADMAP. T03 остаётся блокером production.
База проверки: commit `486b4e9`. Аудит охватывает production-конфигурацию, API, импорт и публикацию данных, Astro, пользовательские сценарии, визуальную идентичность и SEO. Это отчёт о текущем состоянии, а не подтверждение готовности к запуску. Исправления приложения в этот пакет не входят. База проверки: commit `486b4e9`. Аудит охватывает production-конфигурацию, API, импорт и публикацию данных, Astro, пользовательские сценарии, визуальную идентичность и SEO. Это отчёт о текущем состоянии, а не подтверждение готовности к запуску. Исправления приложения в этот пакет не входят.
+2 -2
View File
@@ -180,12 +180,12 @@
## Ближайший рабочий пакет ## Ближайший рабочий пакет
Production-контур требует исправлений до запуска. Доказательства и критерии приёмки каждого ID находятся в [полном аудите](PROJECT_AUDIT_2026-09-08.md). Следующая задача — **T02**, затем T03. Инфраструктурные изменения можно объединить в один проверочный Compose-прогон. Production-контур требует исправлений до запуска. Доказательства и критерии приёмки каждого ID находятся в [полном аудите](PROJECT_AUDIT_2026-09-08.md). Следующая задача — **T03**. Инфраструктурные изменения можно объединить в один проверочный Compose-прогон.
### P0 — блокеры production ### P0 — блокеры production
- [x] T01: Caddy направляет в FastAPI только `/api/v1/*`, `/health`, `/ready`, обработчики формы остаются в Astro (правка `bb61d21`). Проверено 9 сентября: `sh deploy/test-proxy-routing.sh`, оба POST дают ожидаемый 303, каталог и readiness доступны. Тест использует временный proxy и не создаёт уловов; успешная отправка с сохранением входит в T06. - [x] T01: Caddy направляет в FastAPI только `/api/v1/*`, `/health`, `/ready`, обработчики формы остаются в Astro (правка `bb61d21`). Проверено 9 сентября: `sh deploy/test-proxy-routing.sh`, оба POST дают ожидаемый 303, каталог и readiness доступны. Тест использует временный proxy и не создаёт уловов; успешная отправка с сохранением входит в T06.
- [ ] T02: согласовать Basic/Bearer admin-аутентификацию через production proxy. - [x] T02: Basic применяется к admin-страницам, Bearer — к admin API, конфликт Authorization устранён. 9 сентября: `sh deploy/test-proxy-routing.sh` проверяет доступ к странице/диагностике, 401 без правильных credentials и авторизованный PATCH с невалидным UUID (422, без записи). Полный цикл модерации — T06.
- [ ] T03: обеспечить исходящую сеть community scheduler при изоляции БД/MinIO. - [ ] T03: обеспечить исходящую сеть community scheduler при изоляции БД/MinIO.
### P1 — до открытой альфы ### P1 — до открытой альфы
+5 -3
View File
@@ -7,7 +7,7 @@
| Область | Реализация | | Область | Реализация |
|---|---| |---|---|
| Внешняя поверхность | наружу публикуются только Caddy `80/443`; PostgreSQL, API, web и MinIO находятся во внутренних сетях | | Внешняя поверхность | наружу публикуются только Caddy `80/443`; PostgreSQL, API, web и MinIO находятся во внутренних сетях |
| Admin web/API | `/admin/*` и `/api/v1/admin/*` требуют Caddy Basic Auth; API дополнительно требует длинный bearer token | | Admin web/API | `/admin/*` требует Caddy Basic Auth; `/api/v1/admin/*` требует длинный Bearer-токен в FastAPI. Схемы разделены по маршрутам, поскольку используют один заголовок Authorization (T02, 9 сентября) |
| Сравнение токена | constant-time `hmac.compare_digest` | | Сравнение токена | constant-time `hmac.compare_digest` |
| CORS | production принимает только явно заданные HTTPS origins, wildcard запрещён конфигурацией | | CORS | production принимает только явно заданные HTTPS origins, wildcard запрещён конфигурацией |
| Чувствительные ответы | административные ответы и создание заявки получают `Cache-Control: no-store` | | Чувствительные ответы | административные ответы и создание заявки получают `Cache-Control: no-store` |
@@ -31,8 +31,10 @@
1. Убедиться, что `ss -lntup` не показывает наружу порты `4321`, `8000`, `9000`, `9001`, `5432`. 1. Убедиться, что `ss -lntup` не показывает наружу порты `4321`, `8000`, `9000`, `9001`, `5432`.
2. Проверить `stat -c '%a %U:%G' .env.production`: ожидается `600 rf4spotter:rf4spotter`. 2. Проверить `stat -c '%a %U:%G' .env.production`: ожидается `600 rf4spotter:rf4spotter`.
3. Без Basic Auth запрос к `/admin/moderation` и `/api/v1/admin/catch-reports` должен вернуть `401` от Caddy. 3. Без Basic Auth запрос к `/admin/moderation` должен вернуть `401` от Caddy; с правильным Basic — `200`. Bearer не открывает административную страницу.
4. С Basic Auth, но без bearer token административный API должен вернуть `401` от FastAPI. 4. Admin API без Bearer, с неверным Bearer или только с Basic должен вернуть `401` от FastAPI; правильный Bearer даёт доступ и `Cache-Control: no-store`. Владение API-токеном достаточно для API-доступа; дополнительная Basic-проверка к этим запросам не применяется.
5. Проверить security headers через `curl -I https://rf4spotter.ru/`. 5. Проверить security headers через `curl -I https://rf4spotter.ru/`.
6. Убедиться, что `https://files.rf4spotter.ru/` не открывает Console, list buckets или анонимную запись. 6. Убедиться, что `https://files.rf4spotter.ru/` не открывает Console, list buckets или анонимную запись.
7. После проверки удалить команды с секретами из shell history либо вводить их через защищённый password manager. 7. После проверки удалить команды с секретами из shell history либо вводить их через защищённый password manager.
Локальная проверка: `sh deploy/test-proxy-routing.sh` запускает временный Caddy с production-маршрутами перед работающим Compose. `probe-admin-auth.py` читает токен из окружения API-контейнера и не печатает его. Проверяются успешное чтение, отказы и PATCH с невалидным UUID без изменения данных; полный цикл реальной модерации через proxy относится к T06.