fix: resolve webhook client IP behind proxies

This commit is contained in:
3252a8
2026-06-08 11:25:02 +03:00
parent 23ad893f69
commit c2f0ae0b8b
21 changed files with 161 additions and 25 deletions
+3 -2
View File
@@ -71,8 +71,9 @@ WEB_SERVER_PORT=8080
FRONTEND_PORT=8082 FRONTEND_PORT=8082
# Reverse proxy IPs/CIDRs trusted for X-Forwarded-For. # Reverse proxy IPs/CIDRs trusted for X-Forwarded-For.
# Keep loopback for local proxy; add your proxy network if needed. # Keep loopback and private network ranges so payment provider IP allowlists
TRUSTED_PROXIES=127.0.0.1,::1 # see the original webhook sender behind Docker/LAN/Kubernetes proxies.
TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7
# ─── Anonymous install telemetry (opt-out) ────────────────────────────── # ─── Anonymous install telemetry (opt-out) ──────────────────────────────
# Once a day the worker sends a single anonymous "heartbeat" so the project # Once a day the worker sends a single anonymous "heartbeat" so the project
+1
View File
@@ -90,6 +90,7 @@ docker compose logs -f backend worker frontend
- `WEBAPP_SESSION_SECRET`, `WEBHOOK_SECRET_TOKEN` - стабильные секреты; - `WEBAPP_SESSION_SECRET`, `WEBHOOK_SECRET_TOKEN` - стабильные секреты;
- `SUBSCRIPTION_MINI_APP_URL` - публичный HTTPS URL Mini App/frontend, например `https://app.domain.com/`; - `SUBSCRIPTION_MINI_APP_URL` - публичный HTTPS URL Mini App/frontend, например `https://app.domain.com/`;
- `PANEL_API_URL`, `PANEL_API_KEY`, `PANEL_WEBHOOK_SECRET` - доступ к Remnawave; - `PANEL_API_URL`, `PANEL_API_KEY`, `PANEL_WEBHOOK_SECRET` - доступ к Remnawave;
- `TRUSTED_PROXIES` - оставьте дефолт для Docker/Caddy/Nginx/Newt или укажите IP/CIDR своего reverse proxy, чтобы IP allowlist платежных webhook видел реального провайдера;
- остальные настройки удобнее задать в Web App админке. - остальные настройки удобнее задать в Web App админке.
В Remnawave Panel укажите `WEBHOOK_URL` как публичный адрес Minishop с путем `/webhook/panel`, например `https://app.example.com/webhook/panel`. Секрет вебхука задается в самой Remnawave Panel; это же значение вставьте в `PANEL_WEBHOOK_SECRET` в `.env` или в **Система -> Настройки -> Remnawave Panel** в админке. В Remnawave Panel укажите `WEBHOOK_URL` как публичный адрес Minishop с путем `/webhook/panel`, например `https://app.example.com/webhook/panel`. Секрет вебхука задается в самой Remnawave Panel; это же значение вставьте в `PANEL_WEBHOOK_SECRET` в `.env` или в **Система -> Настройки -> Remnawave Panel** в админке.
+25 -10
View File
@@ -34,12 +34,26 @@ def _parse_ip(value: Optional[str]) -> Optional[ipaddress._BaseAddress]:
return None return None
def _last_forwarded_ip(header_value: str) -> Optional[str]: def _forwarded_ips(header_value: str) -> list[ipaddress._BaseAddress]:
candidates = [item.strip() for item in header_value.split(",") if item.strip()] candidates = [item.strip() for item in header_value.split(",") if item.strip()]
if not candidates: parsed: list[ipaddress._BaseAddress] = []
return None for candidate in candidates:
candidate = candidates[-1] parsed_ip = _parse_ip(candidate)
return candidate if _parse_ip(candidate) is not None else None if parsed_ip is not None:
parsed.append(parsed_ip)
return parsed
def _forwarded_client_ip(
forwarded_ips: Sequence[ipaddress._BaseAddress],
trusted_networks: Sequence[ipaddress._BaseNetwork],
) -> Optional[str]:
for forwarded_ip in reversed(forwarded_ips):
if not any(forwarded_ip in network for network in trusted_networks):
return str(forwarded_ip)
if forwarded_ips:
return str(forwarded_ips[0])
return None
def request_client_ip( def request_client_ip(
@@ -48,20 +62,21 @@ def request_client_ip(
trusted_proxies: Optional[Sequence[str] | str] = None, trusted_proxies: Optional[Sequence[str] | str] = None,
) -> Optional[str]: ) -> Optional[str]:
remote_ip = _parse_ip(request.remote or "") remote_ip = _parse_ip(request.remote or "")
forwarded_for = request.headers.get("X-Forwarded-For", "") forwarded_ips = _forwarded_ips(request.headers.get("X-Forwarded-For", ""))
if remote_ip and forwarded_for: if remote_ip and forwarded_ips:
trusted_networks = parse_ip_entries(trusted_proxies) trusted_networks = parse_ip_entries(trusted_proxies)
if any(remote_ip in network for network in trusted_networks): if any(remote_ip in network for network in trusted_networks):
forwarded_ip = _last_forwarded_ip(forwarded_for) forwarded_ip = _forwarded_client_ip(forwarded_ips, trusted_networks)
if forwarded_ip: if forwarded_ip:
return forwarded_ip return forwarded_ip
if remote_ip: if remote_ip:
return str(remote_ip) return str(remote_ip)
forwarded_ip = _last_forwarded_ip(forwarded_for) if forwarded_ips:
return forwarded_ip return str(forwarded_ips[-1])
return None
def ip_in_allowlist( def ip_in_allowlist(
+12 -1
View File
@@ -138,6 +138,17 @@ DEFAULT_DISPOSABLE_EMAIL_DOMAINS = "\n".join(
] ]
) )
DEFAULT_TRUSTED_PROXIES = ",".join(
[
"127.0.0.1",
"::1",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7",
]
)
class DBSettings(BaseModel): class DBSettings(BaseModel):
user: str user: str
@@ -298,7 +309,7 @@ class Settings(BaseSettings):
WEBHOOK_BASE_URL: Optional[str] = None WEBHOOK_BASE_URL: Optional[str] = None
TRUSTED_PROXIES: Optional[str] = Field( TRUSTED_PROXIES: Optional[str] = Field(
default="127.0.0.1,::1", default=DEFAULT_TRUSTED_PROXIES,
description="Comma-separated list of reverse proxy IPs or CIDRs trusted to forward X-Forwarded-For.", # noqa: E501 description="Comma-separated list of reverse proxy IPs or CIDRs trusted to forward X-Forwarded-For.", # noqa: E501
) )
+3
View File
@@ -2,11 +2,14 @@
app.example.com { app.example.com {
encode zstd gzip encode zstd gzip
# Caddy sets X-Forwarded-For/Host/Proto for reverse_proxy by default.
# The backend uses X-Forwarded-For for payment provider IP allowlists.
reverse_proxy backend:8080 reverse_proxy backend:8080
} }
web.example.com { web.example.com {
encode zstd gzip encode zstd gzip
# Keep forwarded headers for Web App URL generation and request logging.
reverse_proxy frontend:80 reverse_proxy frontend:80
} }
@@ -72,6 +72,7 @@ server {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
# Required for payment provider IP allowlists in webhook handlers.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
+2 -2
View File
@@ -36,5 +36,5 @@ PANEL_API_URL=https://panel.example.com/api
PANEL_API_KEY=change_me PANEL_API_KEY=change_me
PANEL_WEBHOOK_SECRET=change_me PANEL_WEBHOOK_SECRET=change_me
# Caddy and Docker network ranges that may set X-Forwarded-For. # Caddy and private Docker/LAN ranges that may set X-Forwarded-For.
TRUSTED_PROXIES=127.0.0.1,::1,172.16.0.0/12 TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7
+3
View File
@@ -8,12 +8,15 @@
{$WEBHOOK_HOST} { {$WEBHOOK_HOST} {
encode zstd gzip encode zstd gzip
# Caddy sets X-Forwarded-For/Host/Proto for reverse_proxy by default.
# The backend uses X-Forwarded-For for payment provider IP allowlists.
reverse_proxy backend:8080 reverse_proxy backend:8080
} }
{$MINIAPP_HOST} { {$MINIAPP_HOST} {
encode zstd gzip encode zstd gzip
# Keep forwarded headers for Web App URL generation and request logging.
reverse_proxy frontend:80 reverse_proxy frontend:80
} }
+1 -1
View File
@@ -64,7 +64,7 @@ services:
environment: environment:
<<: *app_environment <<: *app_environment
WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,172.16.0.0/12} TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7}
volumes: volumes:
- ./data:/app/data - ./data:/app/data
- ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw} - ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw}
+2 -2
View File
@@ -37,5 +37,5 @@ PANEL_API_URL=https://panel.example.com/api
PANEL_API_KEY=change_me PANEL_API_KEY=change_me
PANEL_WEBHOOK_SECRET=change_me PANEL_WEBHOOK_SECRET=change_me
# Pangolin/Newt and Docker network ranges that may set X-Forwarded-For. # Pangolin/Newt and private Docker/LAN ranges that may set X-Forwarded-For.
TRUSTED_PROXIES=127.0.0.1,::1,172.16.0.0/12 TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7
+1 -1
View File
@@ -64,7 +64,7 @@ services:
environment: environment:
<<: *app_environment <<: *app_environment
WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,172.16.0.0/12} TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7}
volumes: volumes:
- ./data:/app/data - ./data:/app/data
- ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw} - ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw}
+2 -2
View File
@@ -36,5 +36,5 @@ PANEL_API_URL=https://panel.example.com/api
PANEL_API_KEY=change_me PANEL_API_KEY=change_me
PANEL_WEBHOOK_SECRET=change_me PANEL_WEBHOOK_SECRET=change_me
# Nginx and Docker network ranges that may set X-Forwarded-For. # Nginx and private Docker/LAN ranges that may set X-Forwarded-For.
TRUSTED_PROXIES=127.0.0.1,::1,172.16.0.0/12 TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7
+1 -1
View File
@@ -64,7 +64,7 @@ services:
environment: environment:
<<: *app_environment <<: *app_environment
WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,172.16.0.0/12} TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7}
volumes: volumes:
- ./data:/app/data - ./data:/app/data
- ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw} - ${COMPOSE_BACKUP_SOURCE:-.}:/app/compose-source:${COMPOSE_RESTORE_MODE:-rw}
@@ -31,6 +31,7 @@ server {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
# Required for payment provider IP allowlists in webhook handlers.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
+10 -1
View File
@@ -41,10 +41,19 @@
| `DB_POOL_RECYCLE_SECONDS` | `.env` | Период recycling DB-соединений. | | `DB_POOL_RECYCLE_SECONDS` | `.env` | Период recycling DB-соединений. |
| `REDIS_URL` | Compose | Redis для FSM, кеша, rate-limit, очередей и locks. В Compose задается автоматически. | | `REDIS_URL` | Compose | Redis для FSM, кеша, rate-limit, очередей и locks. В Compose задается автоматически. |
| `REDIS_KEY_PREFIX` | `.env` | Префикс Redis-ключей. | | `REDIS_KEY_PREFIX` | `.env` | Префикс Redis-ключей. |
| `TRUSTED_PROXIES` | `.env` | IP/CIDR обратных прокси, которым доверяется `X-Forwarded-For`. | | `TRUSTED_PROXIES` | `.env` | IP/CIDR обратных прокси, которым доверяется `X-Forwarded-For`. По умолчанию включает loopback и private ranges для Docker/LAN/Kubernetes proxy. |
| `HTTP_BIND` / `HTTPS_BIND` | Caddy Compose | Адреса публикации Caddy-варианта. | | `HTTP_BIND` / `HTTPS_BIND` | Caddy Compose | Адреса публикации Caddy-варианта. |
| `NEWT_ID` / `NEWT_SECRET` | Dev Compose | Доступы Newt в dev-compose. | | `NEWT_ID` / `NEWT_SECRET` | Dev Compose | Доступы Newt в dev-compose. |
`TRUSTED_PROXIES` нужен не только для логов: платежные webhook-обработчики с IP-фильтром
сравнивают allowlist провайдера с client IP после обработки `X-Forwarded-For`. Если внешний
proxy не передает этот заголовок или его IP не входит в `TRUSTED_PROXIES`, backend увидит IP
proxy/Docker gateway и может отклонить валидный webhook. Для Caddy/Nginx/Newt из
`deploy/examples` дефолта достаточно; в кастомной инфраструктуре добавьте CIDR своего proxy
или сузьте значение до конкретных proxy IP. Trust-all вариант записывается как
`0.0.0.0/0,::/0`, но он безопасен только если backend не доступен напрямую, а внешний proxy
очищает входящий `X-Forwarded-For`.
## Кеши, rate limits и worker ## Кеши, rate limits и worker
Обычно эти значения не требуют правки. Обычно эти значения не требуют правки.
+8
View File
@@ -28,6 +28,14 @@ openssl rand -hex 32
- `SUBSCRIPTION_MINI_APP_URL` должен вести на frontend/Mini App-домен. - `SUBSCRIPTION_MINI_APP_URL` должен вести на frontend/Mini App-домен.
- Не добавляйте `/api`, `/auth` или webhook-пути в `SUBSCRIPTION_MINI_APP_URL`. - Не добавляйте `/api`, `/auth` или webhook-пути в `SUBSCRIPTION_MINI_APP_URL`.
## IP allowlist вебхуков
- Reverse proxy для `WEBHOOK_BASE_URL` должен передавать `X-Forwarded-For` с реальным IP отправителя.
- `TRUSTED_PROXIES` должен включать IP/CIDR последнего proxy-hop до backend. Иначе платежные webhook-обработчики будут проверять allowlist по IP proxy или Docker gateway.
- В Docker Compose профилях Caddy, Nginx и Pangolin/Newt дефолт покрывает loopback и private ranges: `127.0.0.1`, `::1`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`.
- Если backend находится в общей Docker-сети с недоверенными контейнерами, сузьте `TRUSTED_PROXIES` до конкретных IP ваших reverse proxy.
- Если вы сознательно хотите доверять любому proxy-hop, используйте `0.0.0.0/0,::/0`, но только когда backend не опубликован напрямую, а внешний proxy очищает входящий `X-Forwarded-For`.
## Дополнительно ## Дополнительно
- Используйте HTTPS на всех публичных доменах. - Используйте HTTPS на всех публичных доменах.
+6
View File
@@ -28,6 +28,12 @@
Все платежные webhook URL строятся от `WEBHOOK_BASE_URL` - публичного HTTPS-адреса backend/webhook-домена. Это должен быть домен, который проксируется на backend-сервер вебхуков (`backend:8080`), а не `SUBSCRIPTION_MINI_APP_URL` frontend/Mini App. Если `WEBHOOK_BASE_URL=https://bot.example.com`, то полный адрес получается как `https://bot.example.com` + путь из таблицы. Все платежные webhook URL строятся от `WEBHOOK_BASE_URL` - публичного HTTPS-адреса backend/webhook-домена. Это должен быть домен, который проксируется на backend-сервер вебхуков (`backend:8080`), а не `SUBSCRIPTION_MINI_APP_URL` frontend/Mini App. Если `WEBHOOK_BASE_URL=https://bot.example.com`, то полный адрес получается как `https://bot.example.com` + путь из таблицы.
Если у провайдера включена IP-фильтрация (`FREEKASSA_TRUSTED_IPS`, `WATA_TRUSTED_IPS`,
`HELEKET_TRUSTED_IPS`, `PAYKILLA_TRUSTED_IPS` или встроенный allowlist YooKassa),
reverse proxy должен прокидывать `X-Forwarded-For`, а его IP/CIDR должен входить в
`TRUSTED_PROXIES`. Иначе backend увидит IP proxy/Docker gateway и может отклонить
валидный webhook с ошибкой `403`.
| Провайдер | Что указать в кабинете провайдера | Комментарий | | Провайдер | Что указать в кабинете провайдера | Комментарий |
| --- | --- | --- | | --- | --- | --- |
| YooKassa | `WEBHOOK_BASE_URL` + `/webhook/yookassa` | Например `https://bot.example.com/webhook/yookassa`. | | YooKassa | `WEBHOOK_BASE_URL` + `/webhook/yookassa` | Например `https://bot.example.com/webhook/yookassa`. |
+10
View File
@@ -433,6 +433,16 @@ docker compose up -d backend worker
- Mini App/frontend-домен целиком идет в `frontend:80`; - Mini App/frontend-домен целиком идет в `frontend:80`;
- API/auth/theme routes Mini App дальше проксируются frontend nginx в `backend:8081`. - API/auth/theme routes Mini App дальше проксируются frontend nginx в `backend:8081`.
Для платежных провайдеров с IP allowlist важно, чтобы reverse proxy передавал реальный IP
отправителя в `X-Forwarded-For`, а backend доверял IP последнего proxy-hop через
`TRUSTED_PROXIES`. Готовые профили `caddy`, `nginx` и `newt` уже доверяют loopback и
private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`), чтобы
Docker/LAN/Kubernetes proxy не ломал проверки `YOOKASSA`, `FREEKASSA_TRUSTED_IPS`,
`WATA_TRUSTED_IPS`, `HELEKET_TRUSTED_IPS` и `PAYKILLA_TRUSTED_IPS`. Если в вашей
Docker-сети есть недоверенные контейнеры, сузьте `TRUSTED_PROXIES` до конкретного IP
Caddy/Nginx/Newt. Trust-all режим возможен через `0.0.0.0/0,::/0`, но используйте его
только когда backend недоступен напрямую, а внешний proxy очищает входящий `X-Forwarded-For`.
Минимальная логика Caddy: Минимальная логика Caddy:
```caddyfile ```caddyfile
+1 -1
View File
@@ -483,7 +483,7 @@ prompt_common_env() {
WEBHOOK_HOST_VALUE="$PROMPT_VALUE" WEBHOOK_HOST_VALUE="$PROMPT_VALUE"
prompt_value "Mini App public hostname" "$(env_get MINIAPP_HOST app.example.com)" 1 0 "hostname" prompt_value "Mini App public hostname" "$(env_get MINIAPP_HOST app.example.com)" 1 0 "hostname"
MINIAPP_HOST_VALUE="$PROMPT_VALUE" MINIAPP_HOST_VALUE="$PROMPT_VALUE"
TRUSTED_PROXIES_VALUE="$(env_get TRUSTED_PROXIES '127.0.0.1,::1,172.16.0.0/12')" TRUSTED_PROXIES_VALUE="$(env_get TRUSTED_PROXIES '127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7')"
;; ;;
esac esac
+48 -1
View File
@@ -37,12 +37,59 @@ from db.database_setup import redacted_database_url
class RequestSecurityTests(unittest.IsolatedAsyncioTestCase): class RequestSecurityTests(unittest.IsolatedAsyncioTestCase):
async def test_request_client_ip_uses_last_forwarded_for_value_for_trusted_proxy(self): async def test_request_client_ip_uses_rightmost_untrusted_forwarded_ip(self):
request = SimpleNamespace( request = SimpleNamespace(
remote="127.0.0.1", remote="127.0.0.1",
headers={"X-Forwarded-For": "203.0.113.10, 198.51.100.7"}, headers={"X-Forwarded-For": "203.0.113.10, 198.51.100.7"},
) )
self.assertEqual(
request_client_ip(
request,
trusted_proxies=["127.0.0.1", "198.51.100.0/24"],
),
"203.0.113.10",
)
async def test_request_client_ip_ignores_spoofed_forwarded_prefix(self):
request = SimpleNamespace(
remote="127.0.0.1",
headers={"X-Forwarded-For": "198.51.100.200, 203.0.113.10"},
)
self.assertEqual(
request_client_ip(request, trusted_proxies=["127.0.0.1"]),
"203.0.113.10",
)
async def test_request_client_ip_ignores_forwarded_for_from_untrusted_remote(self):
request = SimpleNamespace(
remote="203.0.113.50",
headers={"X-Forwarded-For": "198.51.100.200, 192.0.2.10"},
)
self.assertEqual(
request_client_ip(request, trusted_proxies=["127.0.0.1"]),
"203.0.113.50",
)
async def test_request_client_ip_uses_leftmost_forwarded_ip_when_all_hops_are_trusted(self):
request = SimpleNamespace(
remote="127.0.0.1",
headers={"X-Forwarded-For": "172.18.0.4, 172.18.0.5"},
)
self.assertEqual(
request_client_ip(request, trusted_proxies=["127.0.0.1", "172.16.0.0/12"]),
"172.18.0.4",
)
async def test_request_client_ip_keeps_last_forwarded_ip_without_remote(self):
request = SimpleNamespace(
remote=None,
headers={"X-Forwarded-For": "203.0.113.10, 198.51.100.7"},
)
self.assertEqual( self.assertEqual(
request_client_ip(request, trusted_proxies=["127.0.0.1"]), request_client_ip(request, trusted_proxies=["127.0.0.1"]),
"198.51.100.7", "198.51.100.7",
+20
View File
@@ -42,6 +42,26 @@ class SettingsTests(unittest.TestCase):
self.assertEqual(settings.WEBAPP_TITLE, "/minishop") self.assertEqual(settings.WEBAPP_TITLE, "/minishop")
def test_trusted_proxies_default_includes_private_proxy_ranges(self):
settings = Settings(
_env_file=None,
BOT_TOKEN="token",
POSTGRES_USER="app_user",
POSTGRES_PASSWORD="app_password",
)
self.assertEqual(
settings.trusted_proxies,
[
"127.0.0.1",
"::1",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7",
],
)
def test_panel_write_mode_defaults_to_live_in_production(self): def test_panel_write_mode_defaults_to_live_in_production(self):
settings = Settings( settings = Settings(
_env_file=None, _env_file=None,