Revert "Enforce websocket origin allowlist with secure-mode config"

This reverts commit cf30229b37.
This commit is contained in:
Jage9
2026-02-28 04:52:44 -05:00
parent cf30229b37
commit 027f04e58d
9 changed files with 1 additions and 87 deletions

View File

@@ -26,7 +26,6 @@ npm run dev
Notes: Notes:
- Server defaults to `config.toml` when present. - Server defaults to `config.toml` when present.
- Server bind/port defaults are `127.0.0.1:8765` unless changed in config or CLI flags. - Server bind/port defaults are `127.0.0.1:8765` unless changed in config or CLI flags.
- Server websocket origin checks use `network.allowed_origins`; in secure mode (`allow_insecure_ws=false`), configure your real `https://` site origins.
- Client dev defaults to Vite local host/port (`localhost:5173`) unless flags override. - Client dev defaults to Vite local host/port (`localhost:5173`) unless flags override.
- Auth requires `CHGRID_AUTH_SECRET` in server environment; `deploy/scripts/install_server.sh` creates `server/.env` with this value automatically if missing. - Auth requires `CHGRID_AUTH_SECRET` in server environment; `deploy/scripts/install_server.sh` creates `server/.env` with this value automatically if missing.
- Saved login/session persistence uses a server-set `HttpOnly` cookie (`chgrid_session_token`). - Saved login/session persistence uses a server-set `HttpOnly` cookie (`chgrid_session_token`).

View File

@@ -20,7 +20,6 @@ Defaults:
- Server reads `config.toml` automatically when present. - Server reads `config.toml` automatically when present.
- Server default bind/port is `127.0.0.1:8765`. - Server default bind/port is `127.0.0.1:8765`.
- Server defaults to TLS-required unless you set `network.allow_insecure_ws=true` or pass `--allow-insecure-ws` for local/dev. - Server defaults to TLS-required unless you set `network.allow_insecure_ws=true` or pass `--allow-insecure-ws` for local/dev.
- In local/dev insecure mode (`allow_insecure_ws=true`), websocket Origin allowlist defaults to `http://localhost:5173` and `http://127.0.0.1:5173` when `network.allowed_origins` is empty.
- Client dev default is `localhost:5173`. - Client dev default is `localhost:5173`.
- Auth requires `CHGRID_AUTH_SECRET` in environment. - Auth requires `CHGRID_AUTH_SECRET` in environment.
- Saved login uses server-managed `HttpOnly` cookie (`chgrid_session_token`) via `GET /auth/session/set` and `GET /auth/session/clear` (both require `X-Chgrid-Auth-Client: 1`). - Saved login uses server-managed `HttpOnly` cookie (`chgrid_session_token`) via `GET /auth/session/set` and `GET /auth/session/clear` (both require `X-Chgrid-Auth-Client: 1`).

View File

@@ -111,7 +111,6 @@ This is a behavior guide for packet semantics beyond raw schemas.
- Server is authoritative for all action validation and normalization. - Server is authoritative for all action validation and normalization.
- Server is authoritative for movement acceptance (bounds + rate/delta checks). - Server is authoritative for movement acceptance (bounds + rate/delta checks).
- Server enforces websocket Origin allowlist at handshake (`network.allowed_origins`).
- Server persists account state (last nickname + last position) and restores spawn from that state on auth login/resume. - Server persists account state (last nickname + last position) and restores spawn from that state on auth login/resume.
- Server also supports websocket handshake cookie resume: - Server also supports websocket handshake cookie resume:
- reads `chgrid_session_token` from websocket `Cookie` header - reads `chgrid_session_token` from websocket `Cookie` header

View File

@@ -13,13 +13,10 @@ Key options:
- `server.bind_ip`, `server.port` - `server.bind_ip`, `server.port`
- `network.max_message_bytes` - `network.max_message_bytes`
- `network.allow_insecure_ws` - `network.allow_insecure_ws`
- `network.allowed_origins`
- `tls.cert_file`, `tls.key_file` - `tls.cert_file`, `tls.key_file`
If `network.allow_insecure_ws = false`, TLS cert/key are required and server runs as `wss://`. If `network.allow_insecure_ws = false`, TLS cert/key are required and server runs as `wss://`.
For local/dev without TLS, either set `network.allow_insecure_ws = true` or pass `--allow-insecure-ws`. For local/dev without TLS, either set `network.allow_insecure_ws = true` or pass `--allow-insecure-ws`.
When insecure ws is disabled, `network.allowed_origins` must list your deployed `https://` origins.
When insecure ws is enabled and `network.allowed_origins` is empty, localhost dev origins are allowed automatically.
## Run ## Run

View File

@@ -20,7 +20,6 @@ class NetworkConfigSection(BaseModel):
max_message_bytes: int = Field(default=2_000_000, gt=0) max_message_bytes: int = Field(default=2_000_000, gt=0)
allow_insecure_ws: bool = False allow_insecure_ws: bool = False
allowed_origins: list[str] = Field(default_factory=list)
class TlsConfigSection(BaseModel): class TlsConfigSection(BaseModel):

View File

@@ -127,7 +127,6 @@ AUTH_SESSION_COOKIE_CLEAR_PATH = "/auth/session/clear"
AUTH_SESSION_COOKIE_CLIENT_HEADER = "X-Chgrid-Auth-Client" AUTH_SESSION_COOKIE_CLIENT_HEADER = "X-Chgrid-Auth-Client"
AUTH_LOGIN_FAILURE_MESSAGE = "We couldn't log you in. Check your details and try again." AUTH_LOGIN_FAILURE_MESSAGE = "We couldn't log you in. Check your details and try again."
AUTH_RESUME_FAILURE_MESSAGE = "We couldn't restore your session. Please log in again." AUTH_RESUME_FAILURE_MESSAGE = "We couldn't restore your session. Please log in again."
LOCAL_DEV_ALLOWED_ORIGINS: tuple[str, ...] = ("http://localhost:5173", "http://127.0.0.1:5173")
ADMIN_MENU_ACTION_DEFINITIONS: tuple[dict[str, str], ...] = ( ADMIN_MENU_ACTION_DEFINITIONS: tuple[dict[str, str], ...] = (
{"id": "manage_roles", "label": "Role management", "permission": "role.manage"}, {"id": "manage_roles", "label": "Role management", "permission": "role.manage"},
{"id": "change_user_role", "label": "Change user role", "permission": "user.change_role"}, {"id": "change_user_role", "label": "Change user role", "permission": "user.change_role"},
@@ -145,7 +144,6 @@ class SignalingServer:
port: int, port: int,
ssl_cert: str | None, ssl_cert: str | None,
ssl_key: str | None, ssl_key: str | None,
allowed_origins: tuple[str, ...] | list[str] | None = None,
auth_db_path: Path | None = None, auth_db_path: Path | None = None,
auth_token_hash_secret: str = "dev-secret", auth_token_hash_secret: str = "dev-secret",
password_min_length: int = 8, password_min_length: int = 8,
@@ -163,7 +161,6 @@ class SignalingServer:
self.host = host self.host = host
self.port = port self.port = port
self.max_message_size = max_message_size self.max_message_size = max_message_size
self.allowed_origins = tuple(allowed_origins or ())
self._ssl_context = self._build_ssl_context(ssl_cert, ssl_key) self._ssl_context = self._build_ssl_context(ssl_cert, ssl_key)
self.clients: dict[ServerConnection, ClientConnection] = {} self.clients: dict[ServerConnection, ClientConnection] = {}
resolved_auth_db_path = auth_db_path or Path.cwd() / "runtime" / "chatgrid.db" resolved_auth_db_path = auth_db_path or Path.cwd() / "runtime" / "chatgrid.db"
@@ -1305,7 +1302,6 @@ class SignalingServer:
self._handle_client, self._handle_client,
self.host, self.host,
self.port, self.port,
origins=self.allowed_origins if self.allowed_origins else None,
ssl=self._ssl_context, ssl=self._ssl_context,
max_size=self.max_message_size, max_size=self.max_message_size,
process_request=self._process_http_request, process_request=self._process_http_request,
@@ -2864,10 +2860,6 @@ def run() -> None:
raise SystemExit( raise SystemExit(
"TLS is required when insecure ws is disabled. Set tls.cert_file/tls.key_file in config.toml." "TLS is required when insecure ws is disabled. Set tls.cert_file/tls.key_file in config.toml."
) )
try:
allowed_origins = _resolve_allowed_origins(config.network.allowed_origins, allow_insecure_ws=allow_insecure_ws)
except ValueError as exc:
raise SystemExit(str(exc)) from exc
auth_secret = os.getenv("CHGRID_AUTH_SECRET", "").strip() auth_secret = os.getenv("CHGRID_AUTH_SECRET", "").strip()
if not auth_secret: if not auth_secret:
@@ -3000,31 +2992,6 @@ def run() -> None:
grid_size=config.world.grid_size, grid_size=config.world.grid_size,
state_save_debounce_ms=config.storage.state_save_debounce_ms, state_save_debounce_ms=config.storage.state_save_debounce_ms,
state_save_max_delay_ms=config.storage.state_save_max_delay_ms, state_save_max_delay_ms=config.storage.state_save_max_delay_ms,
allowed_origins=allowed_origins,
) )
asyncio.run(server.start()) asyncio.run(server.start())
ItemClockAnnouncePacket,
def _resolve_allowed_origins(raw_origins: list[str], *, allow_insecure_ws: bool) -> tuple[str, ...]:
"""Resolve websocket Origin allowlist from config and transport mode."""
normalized: list[str] = []
for origin in raw_origins:
candidate = str(origin or "").strip()
if not candidate or candidate in normalized:
continue
normalized.append(candidate)
if allow_insecure_ws:
if normalized:
return tuple(normalized)
return LOCAL_DEV_ALLOWED_ORIGINS
if not normalized:
raise ValueError(
"network.allowed_origins must list your https web origin(s) when insecure ws is disabled."
)
non_https = [origin for origin in normalized if not origin.lower().startswith("https://")]
if non_https:
raise ValueError("network.allowed_origins must use https origins when insecure ws is disabled.")
return tuple(normalized)

View File

@@ -9,10 +9,6 @@ port = 8765
max_message_bytes = 2000000 max_message_bytes = 2000000
# Secure-by-default: TLS is required unless you explicitly set this to true for local/dev. # Secure-by-default: TLS is required unless you explicitly set this to true for local/dev.
allow_insecure_ws = false allow_insecure_ws = false
# Allowed websocket request Origin values.
# Production: list your deployed https web origins explicitly.
# Local/dev: when allow_insecure_ws=true and this list is empty, localhost defaults are used.
allowed_origins = ["https://bestmidi.com", "https://www.bestmidi.com"]
[tls] [tls]
# Required when allow_insecure_ws = false. # Required when allow_insecure_ws = false.

View File

@@ -9,7 +9,6 @@ def test_load_config_defaults_when_path_none() -> None:
cfg = load_config(None) cfg = load_config(None)
assert cfg.server.bind_ip == "127.0.0.1" assert cfg.server.bind_ip == "127.0.0.1"
assert cfg.network.allow_insecure_ws is False assert cfg.network.allow_insecure_ws is False
assert cfg.network.allowed_origins == []
assert cfg.storage.state_file == "runtime/items.json" assert cfg.storage.state_file == "runtime/items.json"
assert cfg.storage.state_save_debounce_ms == 200 assert cfg.storage.state_save_debounce_ms == 200
assert cfg.storage.state_save_max_delay_ms == 1000 assert cfg.storage.state_save_max_delay_ms == 1000
@@ -44,16 +43,3 @@ state_save_max_delay_ms = 900
cfg = load_config(config_path) cfg = load_config(config_path)
assert cfg.storage.state_save_debounce_ms == 150 assert cfg.storage.state_save_debounce_ms == 150
assert cfg.storage.state_save_max_delay_ms == 900 assert cfg.storage.state_save_max_delay_ms == 900
def test_load_config_reads_allowed_origins(tmp_path: Path) -> None:
config_path = tmp_path / "config.toml"
config_path.write_text(
"""
[network]
allow_insecure_ws = true
allowed_origins = ["https://bestmidi.com", "https://www.bestmidi.com"]
""".strip()
)
cfg = load_config(config_path)
assert cfg.network.allowed_origins == ["https://bestmidi.com", "https://www.bestmidi.com"]

View File

@@ -1,28 +0,0 @@
from __future__ import annotations
import pytest
from app.server import LOCAL_DEV_ALLOWED_ORIGINS, _resolve_allowed_origins
def test_resolve_allowed_origins_defaults_localhost_for_insecure_mode() -> None:
origins = _resolve_allowed_origins([], allow_insecure_ws=True)
assert origins == LOCAL_DEV_ALLOWED_ORIGINS
def test_resolve_allowed_origins_requires_values_for_secure_mode() -> None:
with pytest.raises(ValueError):
_resolve_allowed_origins([], allow_insecure_ws=False)
def test_resolve_allowed_origins_requires_https_in_secure_mode() -> None:
with pytest.raises(ValueError):
_resolve_allowed_origins(["http://localhost:5173"], allow_insecure_ws=False)
def test_resolve_allowed_origins_normalizes_and_deduplicates() -> None:
origins = _resolve_allowed_origins(
[" https://bestmidi.com ", "https://bestmidi.com", "https://www.bestmidi.com"],
allow_insecure_ws=False,
)
assert origins == ("https://bestmidi.com", "https://www.bestmidi.com")