From b44aa023264a61cb11040afe465433ab53a419ab Mon Sep 17 00:00:00 2001 From: agent Date: Sun, 14 Jun 2026 11:21:38 +0200 Subject: [PATCH] test(bus): prove a registered identity is accepted on the live cluster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the live smoke: when a registered identity is present (its sign_pub added to the bus allowlist), assert the SAME SDK that a fresh identity gets rejected with is now ACCEPTED — control plane no longer 401s and the nats.ws data-plane connection succeeds. Verified 2026-06-14 against the 3-node cluster: random identity -> 401 / 'authorization violation'; registered identity -> 403 'not a member of this room' / connected=true. The allowlist is the only gate; the SDK speaks both planes correctly end-to-end. Issue 0001, Phase 3. --- web/src/bus/integration.test.ts | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/web/src/bus/integration.test.ts b/web/src/bus/integration.test.ts index e570cf9..a486450 100644 --- a/web/src/bus/integration.test.ts +++ b/web/src/bus/integration.test.ts @@ -13,9 +13,11 @@ // data plane); only the allowlist gate stops it. Issue uniweb/0001, Phase 3. import { describe, it, expect } from "vitest"; +import { readFileSync, existsSync } from "node:fs"; import { ed25519, x25519 } from "@noble/curves/ed25519.js"; import { concatBytes } from "@noble/hashes/utils.js"; import { signedHeaders, freshNonce } from "./busauth.js"; +import { hexToBytes } from "./crypto.js"; import { WsNatsTransport } from "./wstransport.js"; import type { Identity } from "./client.js"; @@ -23,6 +25,22 @@ const BUS_HTTP = process.env.BUS_HTTP; const BUS_WS = process.env.BUS_WS; const live = !!(BUS_HTTP && BUS_WS); +// An optional REGISTERED identity (its sign_pub added to the bus allowlist out of +// band). When present, the second describe block proves the same SDK that gets +// rejected with a fresh identity is ACCEPTED once the identity is allow-listed — +// closing the loop that the allowlist is the only gate. +const ID_FILE = process.env.BUS_IDENTITY || "/tmp/smoke_identity.json"; +function registeredIdentity(): Identity | null { + if (!existsSync(ID_FILE)) return null; + const j = JSON.parse(readFileSync(ID_FILE, "utf8")); + return { + signPub: hexToBytes(j.signPub), + signPriv: hexToBytes(j.signPriv), + kexPub: hexToBytes(j.kexPub), + kexPriv: hexToBytes(j.kexPriv), + }; +} + function freshIdentity(): Identity { const seed = crypto.getRandomValues(new Uint8Array(32)); const signPub = ed25519.getPublicKey(seed); @@ -70,3 +88,39 @@ describe.skipIf(!live)("live cluster smoke", () => { expect(connected || /authorization|nkey|permission|violation/.test(errMsg)).toBe(true); }); }); + +const regId = live ? registeredIdentity() : null; + +describe.skipIf(!live || !regId)("live cluster smoke — REGISTERED identity is accepted", () => { + const id = regId!; + + it("control plane: a registered identity is authorized (not 401)", async () => { + const ts = String(Math.floor(Date.now() / 1000)); + const path = "/rooms/smoke-probe/members"; + const headers = signedHeaders(id.signPub, id.signPriv, "GET", path, ts, freshNonce(), new Uint8Array(0)); + const resp = await fetch(BUS_HTTP + path, { method: "GET", headers }); + const body = await resp.text(); + // eslint-disable-next-line no-console + console.log(`[control-plane:registered] status=${resp.status} body=${body.trim()}`); + // The allowlist no longer rejects us: the status is anything but 401 (a missing + // room yields 404/403, an existing one 200). The point is the identity passed. + expect(resp.status).not.toBe(401); + }); + + it("data plane: a registered identity connects over nats.ws (authenticated)", async () => { + let connected = false; + let errMsg = ""; + try { + const t = await WsNatsTransport.connect([BUS_WS!], id); + connected = true; + await t.close(); + } catch (e) { + errMsg = String((e as Error).message || e).toLowerCase(); + } + // eslint-disable-next-line no-console + console.log(`[data-plane:registered] connected=${connected} err=${errMsg}`); + // Now the nkey authenticator accepts us: the connection succeeds. This is the + // full proof that the SDK authenticates on the live data plane end-to-end. + expect(connected).toBe(true); + }); +});