mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-09 02:12:59 +00:00
65a69d9ecc
Node read endpoints now return a client-safe projection that omits the stored api_token and exposes a has_token boolean instead, so a node's long-lived proxy credential is never serialized to a browser or API token client. The token stays encrypted at rest and is read server-side only by the components that need it (the remote proxy, the connection test, and the mesh dialer). The edit form opens the API Token field blank, and a blank value keeps the existing credential, so saving an edit without retyping the token no longer clears it; a non-empty value rotates it. The backend enforces the same rule defensively. Node management actions (add, edit, delete, test connection, generate node token) are gated in the UI to match their server-side permission checks, so operators no longer see an action the API would reject. The test-connection route also gains the missing server-side permission and token-scope guards. Also validate the x-node-id header and fall back to the default node for malformed values instead of an obscure 404, and return 400 (not 500) when deleting the default node.
48 lines
2.1 KiB
TypeScript
48 lines
2.1 KiB
TypeScript
import type { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
import { NodeRegistry } from '../services/NodeRegistry';
|
|
import { DatabaseService } from '../services/DatabaseService';
|
|
import { isProxyExemptPath } from '../helpers/proxyExemptPaths';
|
|
import { sanitizeForLog } from '../utils/safeLog';
|
|
|
|
/**
|
|
* Resolve `req.nodeId` from the `x-node-id` header, `?nodeId=` query param,
|
|
* or the default node. Returns 404 for requests targeting a deleted node so
|
|
* downstream handlers don't fail with obscure errors.
|
|
*
|
|
* `/api/nodes` is intentionally exempt so the frontend can re-sync after a
|
|
* node is deleted (otherwise a stale `x-node-id` in localStorage triggers an
|
|
* unrecoverable 404 loop).
|
|
*/
|
|
export const nodeContextMiddleware: RequestHandler = (req: Request, res: Response, next: NextFunction) => {
|
|
const nodeIdHeader = req.headers['x-node-id'] as string;
|
|
const nodeIdQuery = req.query.nodeId as string;
|
|
// A malformed id (parseInt → NaN, or a non-positive value) must fall back to
|
|
// the default node rather than resolve to NaN and trip the obscure 404 below.
|
|
// A well-formed id for a node that does not exist still 404s further down;
|
|
// only malformed input falls through to the default.
|
|
const parseNodeId = (raw: string | undefined): number | null => {
|
|
if (!raw) return null;
|
|
const n = parseInt(raw, 10);
|
|
if (Number.isInteger(n) && n > 0) return n;
|
|
// Present but malformed is a client bug: warn so the fall-back to the
|
|
// default node is observable instead of a request silently landing on the
|
|
// wrong node during debugging.
|
|
console.warn(`[NodeContext] Ignoring malformed node id "${sanitizeForLog(raw)}"; using the default node.`);
|
|
return null;
|
|
};
|
|
req.nodeId =
|
|
parseNodeId(nodeIdHeader) ??
|
|
parseNodeId(nodeIdQuery) ??
|
|
NodeRegistry.getInstance().getDefaultNodeId();
|
|
|
|
if (req.path.startsWith('/api/') && !isProxyExemptPath(req.path)) {
|
|
const node = DatabaseService.getInstance().getNode(req.nodeId);
|
|
if (!node) {
|
|
res.status(404).json({ error: `Node with id ${req.nodeId} not found or was deleted.` });
|
|
return;
|
|
}
|
|
}
|
|
|
|
next();
|
|
};
|