Compare commits
2
Commits
ba7a69c1ce
...
d80e7b155e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d80e7b155e | ||
|
|
2a88f55689 |
No files matched your search
+4
-2
@@ -44,8 +44,10 @@ MAX_POSITION_PCT=1.0
|
||||
MAX_TRADES_PER_DAY=0
|
||||
|
||||
# --- API token (optional) ---
|
||||
# When set, /api requires this token. The frontend nginx injects it server-side,
|
||||
# so the browser never sees it. Leave empty to disable.
|
||||
# When set, /api and the /api/ws live feed require this token. The frontend
|
||||
# nginx injects it server-side, so the browser never sees it. Leave empty for
|
||||
# no token; the app works fully without it. (The Traefik Basic auth still
|
||||
# guards the UI in the deployed stack.)
|
||||
API_TOKEN=
|
||||
API_TOKEN_HEADER=X-API-Token
|
||||
|
||||
|
||||
@@ -147,13 +147,13 @@ jobs:
|
||||
echo "::error::CRYPTO_FRONTEND_AUTH_USERS is not set (repo/org variable or secret); refusing to deploy"
|
||||
exit 1
|
||||
fi
|
||||
# /api/ws bypasses Basic auth (browsers don't reliably replay
|
||||
# credentials on a WebSocket upgrade) and relies on the API token
|
||||
# instead. Refuse to ship if that token is missing, or the live feed
|
||||
# would be unauthenticated.
|
||||
# CRYPTO_API_TOKEN is optional. When unset, the API and the /api/ws
|
||||
# live feed are unauthenticated beyond the Traefik Basic auth that
|
||||
# still guards everything except /api/ws. Set it to require a bearer
|
||||
# token on /api (nginx injects it server-side, so the browser never
|
||||
# sees it).
|
||||
if [ -z "$CRYPTO_API_TOKEN" ]; then
|
||||
echo "::error::CRYPTO_API_TOKEN must be set because /api/ws skips Basic auth; refusing to deploy"
|
||||
exit 1
|
||||
echo "Note: CRYPTO_API_TOKEN is not set; /api and /api/ws will not require a bearer token."
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub (optional)
|
||||
|
||||
@@ -164,10 +164,10 @@ real migration step.
|
||||
depend on `GuardedRuntimeDep`, not `RuntimeDep` (which no longer exists).
|
||||
- `/api/ws` skips Traefik Basic auth via a dedicated higher-priority router
|
||||
(`crypto-trading-ws`) because browsers don't reliably replay Basic creds on a
|
||||
WS upgrade. The WS is gated by `API_TOKEN` instead (header, injected by nginx,
|
||||
or `?token=` for manual clients) — see `websocket_endpoint`. The deploy job
|
||||
fails closed if `CRYPTO_API_TOKEN` is unset. Keep the `/api/ws` block in
|
||||
`nginx.conf` separate from `/api/` so upgrade headers are only set there.
|
||||
WS upgrade. When `API_TOKEN` is set the WS requires it (header, injected by
|
||||
nginx, or `?token=` for manual clients); when unset the path is open, matching
|
||||
the rest of `/api`. `CRYPTO_API_TOKEN` is optional. Keep the `/api/ws` block
|
||||
in `nginx.conf` separate from `/api/` so upgrade headers are only set there.
|
||||
|
||||
## Backtesting
|
||||
|
||||
|
||||
@@ -136,9 +136,10 @@ auth, not instead of it.
|
||||
`/api/ws` is special: browsers do not reliably replay Basic-auth credentials on
|
||||
a WebSocket upgrade (which left the live feed stuck on "reconnecting"), so
|
||||
Traefik serves that one path on a separate higher-priority router **without**
|
||||
Basic auth. It is still gated by `API_TOKEN`, which nginx injects on the
|
||||
handshake. Because of this the deploy **requires** `CRYPTO_API_TOKEN` to be set —
|
||||
without it the WebSocket would be unauthenticated.
|
||||
Basic auth. If `API_TOKEN` is set, the WebSocket still requires it (nginx
|
||||
injects it on the handshake). If `API_TOKEN` is unset, `/api/ws` is as open as
|
||||
the rest of `/api`, which is fine for a trusted/private deployment but is the
|
||||
reason to set a token if the host is exposed.
|
||||
|
||||
## Risk controls
|
||||
|
||||
|
||||
@@ -145,6 +145,29 @@ def test_api_token_enforced(runtime: Runtime) -> None:
|
||||
assert client.get("/api/health").status_code == 200
|
||||
|
||||
|
||||
def test_websocket_connects_without_any_token(runtime: Runtime) -> None:
|
||||
# API_TOKEN is unset by default: the API and the live feed must work with no
|
||||
# token at all, even when (as nginx does) an empty token header is sent.
|
||||
runtime.settings.api_token = ""
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with (
|
||||
TestClient(app) as client,
|
||||
client.websocket_connect("/api/ws", headers={"X-API-Token": ""}) as websocket,
|
||||
):
|
||||
message = websocket.receive_json()
|
||||
assert message["type"] == "status"
|
||||
assert message["data"]["running"] is False
|
||||
|
||||
|
||||
def test_api_works_without_token(runtime: Runtime) -> None:
|
||||
runtime.settings.api_token = ""
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
assert client.get("/api/status").status_code == 200
|
||||
assert client.get("/api/strategies").status_code == 200
|
||||
assert client.post("/api/backtest", json={"strategy_key": "sma_cross"}).status_code != 401
|
||||
|
||||
|
||||
def test_websocket_rejects_missing_token(runtime: Runtime) -> None:
|
||||
runtime.settings.api_token = "secret"
|
||||
app = create_app(runtime.settings, runtime)
|
||||
|
||||
+3
-4
@@ -98,10 +98,9 @@ services:
|
||||
- "traefik.http.middlewares.crypto-trading-auth.basicauth.users=${CRYPTO_FRONTEND_AUTH_USERS:?CRYPTO_FRONTEND_AUTH_USERS is required (htpasswd string, e.g. htpasswd -nbB user pass)}"
|
||||
# Browsers do not reliably replay Basic-auth credentials on a WebSocket
|
||||
# upgrade, which left the live feed stuck "reconnecting". Give /api/ws its
|
||||
# own higher-priority router that skips Basic auth; the backend still
|
||||
# requires the API token, which nginx injects server-side. When no API
|
||||
# token is configured this path is unauthenticated, so only use it on a
|
||||
# trusted network.
|
||||
# own higher-priority router that skips Basic auth so the feed connects.
|
||||
# If CRYPTO_API_TOKEN is set, the backend still requires it (nginx injects
|
||||
# it server-side); if it is unset, /api/ws is as open as the rest of /api.
|
||||
- "traefik.http.routers.crypto-trading-ws.rule=Host(`crypto.${BASE_DOMAIN:-matitos.org}`) && PathPrefix(`/api/ws`)"
|
||||
- "traefik.http.routers.crypto-trading-ws.entrypoints=websecure"
|
||||
- "traefik.http.routers.crypto-trading-ws.tls.certresolver=myresolvercd"
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ server {
|
||||
}
|
||||
|
||||
# WebSocket feed. Kept in its own block so the upgrade headers are only set
|
||||
# here, and so the API token is injected server-side on the handshake (the
|
||||
# browser cannot set headers on a WebSocket).
|
||||
# here, and so the optional API token is injected server-side on the
|
||||
# handshake (the browser cannot set headers on a WebSocket).
|
||||
location /api/ws {
|
||||
proxy_pass http://backend:8000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
Reference in new issue
Block a user