fix: dev-запуск слушает только 127.0.0.1 (M-11)

В api/.env.example задан SERVER__HOST=127.0.0.1 (и SERVER__PORT=8000):
при DEBUG и AUTH_DEV_ISSUER эндпоинт dev-токенов выдаёт токен любому, кто
знает email. Значение приложения по умолчанию (0.0.0.0) не меняется.
.env.test — те же ключи; новый тест сверяет набор ключей .env.example и
.env.test. README: рекомендация в быстром старте и строка таблицы.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-04 01:55:59 +04:00
parent feaac9cb9a
commit 8bab965988
4 changed files with 32 additions and 3 deletions

View File

@ -91,7 +91,9 @@ dev-пользователей. Seed идемпотентен: повторны
cd api && .venv/bin/python run_server.py
```
Сервер слушает `0.0.0.0:8000` (`SERVER__HOST`, `SERVER__PORT`). Дальнейшие команды — из другого терминала.
Сервер слушает `127.0.0.1:8000` (`SERVER__HOST`, `SERVER__PORT` из `api/.env`). Для dev-запуска оставляйте loopback:
при `DEBUG=true` и `AUTH_DEV_ISSUER=true` эндпоинт dev-токенов выдаёт токен любому, кто знает email пользователя, —
не открывайте его в сеть. Дальнейшие команды — из другого терминала.
### 6. Проверка
@ -197,7 +199,7 @@ docker compose -f ../docs/dev/docker-compose.yml down -v # полный сб
|---|---|---|
| `ENV_FILE` | `.env` | файл окружения (относительно `api/`) |
| `DEBUG` | `false` | режим разработки. `true` необходим dev-эмитенту токенов; при `false` пакеты РАИСА подгружаются условно |
| `SERVER__HOST`, `SERVER__PORT` | `0.0.0.0`, `8000` | адрес и порт сервера (`run_server.py`) |
| `SERVER__HOST`, `SERVER__PORT` | `0.0.0.0`, `8000` | адрес и порт сервера (`run_server.py`); в `.env.example` — `127.0.0.1` (dev-запуск только на loopback) |
| `ROOT_PATH` | `/` | корневой путь за прокси (должен совпадать с `REACT_APP_ROOT_PATH` фронта) |
| `VERSION` | `1.0.0` | версия в `/healthcheck` и OpenAPI |
| `OPENBAO__DATABASE__DATABASE_URL` | — (обязательно) | PostgreSQL, драйвер asyncpg: `postgresql+asyncpg://armka:armka@localhost:5432/armka` |

View File

@ -3,6 +3,11 @@
DEBUG=true
# Dev-запуск слушает только loopback: при DEBUG и AUTH_DEV_ISSUER эндпоинт dev-токенов выдаёт токен любому, кто знает
# email пользователя. По умолчанию приложения (без этой строки) — 0.0.0.0, как в контейнере платформы.
SERVER__HOST=127.0.0.1
SERVER__PORT=8000
# PostgreSQL (драйвер asyncpg)
OPENBAO__DATABASE__DATABASE_URL=postgresql+asyncpg://armka:armka@localhost:5432/armka

View File

@ -4,6 +4,10 @@
DEBUG=true
# Как в .env.example (тесты сервер не запускают)
SERVER__HOST=127.0.0.1
SERVER__PORT=8000
OPENBAO__DATABASE__DATABASE_URL=postgresql+asyncpg://armka:armka@localhost:5432/armka
OPENBAO__MINIO__MINIO_URL=localhost:9000

View File

@ -15,7 +15,7 @@ API_DIR = Path(__file__).resolve().parent.parent
def clean_env(monkeypatch: pytest.MonkeyPatch) -> pytest.MonkeyPatch:
"""Убирает из окружения процесса переменные, которые перебили бы значения из env-файла."""
for key in list(os.environ):
if key.startswith(("OPENBAO__", "AUTH_", "OUTBOX_", "ACK_")) or key in {"NATS_ENABLED", "DEBUG"}:
if key.startswith(("OPENBAO__", "AUTH_", "OUTBOX_", "ACK_", "SERVER__")) or key in {"NATS_ENABLED", "DEBUG"}:
monkeypatch.delenv(key)
return monkeypatch
@ -57,6 +57,7 @@ def test_defaults(clean_env: pytest.MonkeyPatch) -> None:
assert s.OPENBAO.NATS.NATS_SEED == ""
assert s.OPENBAO.NATS.NATS_JWT == ""
assert s.OPENBAO.MINIO.MINIO_SECURE is True
assert (s.server.HOST, s.server.PORT) == ("0.0.0.0", 8000) # noqa: S104 - по умолчанию, как в контейнере
smb = s.OPENBAO.SMB
assert (smb.SMB_HOST, smb.SMB_PATH, smb.SMB_LOGIN, smb.SMB_PASSWORD) == ("", "", "", "")
@ -68,6 +69,7 @@ def test_empty_nats_seed_and_jwt_allowed(clean_env: pytest.MonkeyPatch) -> None:
assert s.OPENBAO.NATS.NATS_JWT == ""
assert s.NATS_ENABLED is True
assert s.OPENBAO.SMB.SMB_HOST == ""
assert (s.server.HOST, s.server.PORT) == ("127.0.0.1", 8000) # dev-запуск — только loopback (M-11)
REQUIRED_ENV = {
@ -105,3 +107,19 @@ def test_zero_republish_allowed(clean_env: pytest.MonkeyPatch) -> None:
clean_env.setenv(name, required)
clean_env.setenv("OUTBOX_MAX_REPUBLISH", "0")
assert Settings(_env_file=None).OUTBOX_MAX_REPUBLISH == 0 # type: ignore[call-arg]
def _env_keys(name: str) -> set[str]:
lines = (API_DIR / name).read_text(encoding="utf-8").splitlines()
return {line.split("=", 1)[0] for line in lines if line.strip() and not line.lstrip().startswith("#")}
def test_env_example_and_env_test_have_same_keys() -> None:
"""`.env.example` и `.env.test` перечисляют одни и те же переменные: новая настройка не забывается ни в одном."""
assert _env_keys(".env.example") == _env_keys(".env.test")
def test_env_example_listens_on_loopback(clean_env: pytest.MonkeyPatch) -> None:
"""Dev-запуск по `.env.example` слушает только 127.0.0.1 (финальное ревью M-11)."""
s = Settings(_env_file=API_DIR / ".env.example") # type: ignore[call-arg]
assert (s.server.HOST, s.server.PORT) == ("127.0.0.1", 8000)