mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 20:00:08 +00:00
fix: proxy forwards browser cookie to remote causing 401; fix nodeContextMiddleware loop
Two backend fixes: 1. proxyReq was forwarding the browser's sencho_token cookie to the remote Sencho. The remote's authMiddleware uses cookieToken || bearerToken, so the invalid local-signed cookie was verified before the valid Bearer token, returning 401 on every proxied API call. Strip the cookie header in proxyReq so remote auth uses only the Bearer token. 2. nodeContextMiddleware blocked /api/nodes when x-node-id referenced a deleted node, trapping the frontend in an unrecoverable 404 loop (it could not fetch the nodes list to discover the node was gone). Exempt /api/nodes alongside /api/auth/ so the app can always self-heal.
This commit is contained in:
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
- **Fixed:** Remote node proxy forwarding the browser's `sencho_token` cookie to the remote Sencho instance — the remote's `authMiddleware` evaluates `cookieToken || bearerToken` and the cookie (signed with the local JWT secret) was validated before the valid Bearer token, causing 401 on all proxied API calls. Fixed by stripping the `cookie` header in `proxyReq` so only the Bearer token is used for remote authentication.
|
||||
- **Fixed:** `nodeContextMiddleware` blocking `/api/nodes` when `x-node-id` references a deleted/non-existent node — the nodes list endpoint must always succeed so the frontend can re-sync a stale node ID in localStorage; exempted alongside `/api/auth/`.
|
||||
- **Fixed:** Remote node proxy stripping the `/api` path prefix — `remoteNodeProxy` is mounted at `app.use('/api/', ...)` so Express strips that prefix from `req.url` before `http-proxy-middleware` sees it; added `pathRewrite: (path) => '/api' + path` to restore the full path when forwarding to the remote Sencho instance (e.g. `/stats` → `/api/stats`). This was the root cause of all remote API calls returning the remote's SPA HTML instead of JSON.
|
||||
- **Fixed:** Dashboard cards (Active Containers, Host CPU, Host RAM, Docker Network) showing stale local-node data after switching to a remote node — `HomeDashboard` polling effects now depend on `activeNode?.id` and clear state immediately on node change.
|
||||
- **Fixed:** `refreshStacks` crashing with `SyntaxError` or `TypeError` when the remote proxy returns a non-JSON response (e.g., connection refused to unreachable remote node) — now checks `res.ok` before calling `res.json()` and iterates a typed `fileList` instead of the raw parsed value.
|
||||
|
||||
+15
-3
@@ -82,8 +82,15 @@ const nodeContextMiddleware = (req: Request, res: Response, next: NextFunction)
|
||||
req.nodeId = NodeRegistry.getInstance().getDefaultNodeId();
|
||||
}
|
||||
|
||||
// Intercept requests to deleted nodes to prevent downstream errors
|
||||
if (req.path.startsWith('/api/') && !req.path.startsWith('/api/auth/')) {
|
||||
// Intercept requests to deleted nodes to prevent downstream errors.
|
||||
// /api/nodes is intentionally exempt: it must always be reachable so the
|
||||
// frontend can re-sync after a node is deleted (otherwise a stale x-node-id
|
||||
// in localStorage causes an unrecoverable 404 loop).
|
||||
if (
|
||||
req.path.startsWith('/api/') &&
|
||||
!req.path.startsWith('/api/auth/') &&
|
||||
!req.path.startsWith('/api/nodes')
|
||||
) {
|
||||
const node = DatabaseService.getInstance().getNode(req.nodeId);
|
||||
if (!node) {
|
||||
res.status(404).json({ error: `Node with id ${req.nodeId} not found or was deleted.` });
|
||||
@@ -338,8 +345,13 @@ const remoteNodeProxy = createProxyMiddleware<Request, Response>({
|
||||
on: {
|
||||
proxyReq: (proxyReq, req) => {
|
||||
const node = NodeRegistry.getInstance().getNode(req.nodeId);
|
||||
// Remote Sencho sees itself as local - strip node context and inject bearer auth
|
||||
// Strip headers that must not reach the remote instance:
|
||||
// - x-node-id: remote Sencho treats all requests as local
|
||||
// - cookie: the browser's sencho_token is signed with THIS instance's JWT secret;
|
||||
// the remote would try to verify it with its own secret and return 401.
|
||||
// Authentication is handled exclusively via the Bearer token below.
|
||||
proxyReq.removeHeader('x-node-id');
|
||||
proxyReq.removeHeader('cookie');
|
||||
if (node?.api_token) {
|
||||
proxyReq.setHeader('Authorization', `Bearer ${node.api_token}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user