mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 11:17:07 +00:00
fix(mesh): proxy peer learns its central-namespace nodeId at tunnel upgrade (#1055)
Proxy-mode peers receive an alias overlay from central with nodeIds in central's namespace, but had no source for their own nodeId in that namespace. selfCentralNodeId fell back to the peer's local DB default (always 1), and handleAccept falsely matched cross-node aliases (nodeId=1, central's Local) against the peer's self-id. Cross-node aliases routed to openSameNode and surfaced as route.resolve.denied. Central now appends ?nodeId=<central-namespace-id> to the proxy-tunnel WS URL. The peer's upgrade handler parses it with a strict integer regex, installs into MeshService before the reverse dialer is registered, and clears on disconnect. handleAccept resolves self-id in priority order: proxy-tunnel install > SENCHO_ENROLL_TOKEN > local default. A non-null overwrite to a different value warns loudly (misconfigured deployment). Missing or malformed query param logs a warn and proceeds, leaving reverse-direction dispatch undefined until both sides are upgraded. Adds 5 test cases: nodeId install + teardown, missing/malformed input (including decimal, leading zeros, exponent, whitespace), overwrite warn, and CAS-rejected reverse-dialer leaving the slot unchanged.
This commit is contained in:
@@ -48,14 +48,38 @@ export async function handleMeshProxyTunnel(req: IncomingMessage, socket: Duplex
|
||||
return reject(socket, 404, 'Not Found');
|
||||
}
|
||||
|
||||
// Central appends `?nodeId=<central-namespace-id>` so this peer can tag
|
||||
// its own MeshService with the right nodeId for `handleAccept` dispatch.
|
||||
// The value is unsigned, but the Bearer credential at the upgrade has
|
||||
// already proven the caller is the upstream central (auth + scope gate
|
||||
// in `upgradeHandler.ts`); the query param's trust ceiling is the
|
||||
// token's trust ceiling.
|
||||
// Strict regex (no parseInt) so `?nodeId=14.5`, `?nodeId=00014`,
|
||||
// `?nodeId=1e2`, leading whitespace, etc. are rejected rather than
|
||||
// silently truncated.
|
||||
let peerNodeId: number | null = null;
|
||||
try {
|
||||
const parsed = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
||||
const raw = parsed.searchParams.get('nodeId');
|
||||
if (raw != null && /^[1-9][0-9]*$/.test(raw)) {
|
||||
const n = Number(raw);
|
||||
if (Number.isSafeInteger(n)) peerNodeId = n;
|
||||
}
|
||||
} catch {
|
||||
// Malformed URL; treat as missing param.
|
||||
}
|
||||
if (peerNodeId === null) {
|
||||
console.warn('[MeshProxy] proxy-tunnel upgrade missing or malformed nodeId query param; reverse-direction mesh dispatch will be undefined until central upgrades');
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
wss.handleUpgrade(req, socket as Parameters<typeof wss.handleUpgrade>[1], head, (ws) => {
|
||||
void attachSwitchboard(ws).finally(resolve);
|
||||
void attachSwitchboard(ws, peerNodeId).finally(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function attachSwitchboard(ws: WebSocket): Promise<void> {
|
||||
async function attachSwitchboard(ws: WebSocket, peerNodeId: number | null): Promise<void> {
|
||||
let switchboard: TcpStreamSwitchboard | null = null;
|
||||
let meshServiceCleanup: (() => void) | null = null;
|
||||
|
||||
@@ -87,8 +111,17 @@ async function attachSwitchboard(ws: WebSocket): Promise<void> {
|
||||
switchboard = null;
|
||||
return;
|
||||
}
|
||||
// Install central's view of this peer's nodeId so handleAccept
|
||||
// dispatches cross-node aliases correctly. Done after setReverseDialer
|
||||
// succeeds so a CAS-rejected tunnel does not leak nodeId state.
|
||||
if (peerNodeId !== null) {
|
||||
meshService.setProxyTunnelSelfCentralNodeId(peerNodeId);
|
||||
}
|
||||
meshServiceCleanup = () => {
|
||||
meshService.setReverseDialer(null, localDialer);
|
||||
if (peerNodeId !== null) {
|
||||
meshService.setProxyTunnelSelfCentralNodeId(null);
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
if (isDebugEnabled()) {
|
||||
|
||||
Reference in New Issue
Block a user