fix: separate admin page and API authentication
This commit is contained in:
+2
-3
@@ -25,9 +25,8 @@
|
||||
|
||||
@adminApi path /api/v1/admin/*
|
||||
handle @adminApi {
|
||||
basic_auth {
|
||||
{$ADMIN_BASIC_USER} {$ADMIN_BASIC_PASSWORD_HASH}
|
||||
}
|
||||
# FastAPI verifies the Bearer token. Basic Auth here would reject the
|
||||
# browser's Bearer Authorization header before it reached the API.
|
||||
reverse_proxy api:8000
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Развёртывание открытой альфы 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).
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -44,4 +44,6 @@ check_redirect /api/report '/report?state=create_error'
|
||||
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/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"
|
||||
|
||||
Reference in New Issue
Block a user