test(bus): prove a registered identity is accepted on the live cluster
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.
This commit is contained in:
@@ -13,9 +13,11 @@
|
|||||||
// data plane); only the allowlist gate stops it. Issue uniweb/0001, Phase 3.
|
// data plane); only the allowlist gate stops it. Issue uniweb/0001, Phase 3.
|
||||||
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { readFileSync, existsSync } from "node:fs";
|
||||||
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
||||||
import { concatBytes } from "@noble/hashes/utils.js";
|
import { concatBytes } from "@noble/hashes/utils.js";
|
||||||
import { signedHeaders, freshNonce } from "./busauth.js";
|
import { signedHeaders, freshNonce } from "./busauth.js";
|
||||||
|
import { hexToBytes } from "./crypto.js";
|
||||||
import { WsNatsTransport } from "./wstransport.js";
|
import { WsNatsTransport } from "./wstransport.js";
|
||||||
import type { Identity } from "./client.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 BUS_WS = process.env.BUS_WS;
|
||||||
const live = !!(BUS_HTTP && 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 {
|
function freshIdentity(): Identity {
|
||||||
const seed = crypto.getRandomValues(new Uint8Array(32));
|
const seed = crypto.getRandomValues(new Uint8Array(32));
|
||||||
const signPub = ed25519.getPublicKey(seed);
|
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);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user