test: share gateway pairing authz setup

This commit is contained in:
Peter Steinberger
2026-04-20 22:18:50 +01:00
parent 4b4631cd48
commit f197ca503a

View File

@@ -20,36 +20,57 @@ import {
installGatewayTestHooks({ scope: "suite" });
async function issuePairingOnlyOperator(name: string) {
return await issueOperatorToken({
name,
approvedScopes: ["operator.admin"],
tokenScopes: ["operator.pairing"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
}
async function requestOperatorDevicePairing(params: {
identity: ReturnType<typeof loadDeviceIdentity>;
scopes: string[];
}) {
return await requestDevicePairing({
deviceId: params.identity.identity.deviceId,
publicKey: params.identity.publicKey,
role: "operator",
scopes: params.scopes,
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
}
async function openPairingSession(
port: number,
operator: Awaited<ReturnType<typeof issueOperatorToken>>,
): Promise<WebSocket> {
const pairingWs = await openTrackedWs(port);
await connectOk(pairingWs, {
skipDefaultAuth: true,
deviceToken: operator.token,
deviceIdentityPath: operator.identityPath,
scopes: ["operator.pairing"],
});
return pairingWs;
}
describe("gateway device.pair.approve caller scope guard", () => {
test("rejects approving device scopes above the caller session scopes", async () => {
const started = await startServerWithClient("secret");
const approver = await issueOperatorToken({
name: "approve-attacker",
approvedScopes: ["operator.admin"],
tokenScopes: ["operator.pairing"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
const approver = await issuePairingOnlyOperator("approve-attacker");
const approverIdentity = loadDeviceIdentity("approve-attacker");
let pairingWs: WebSocket | undefined;
try {
const request = await requestDevicePairing({
deviceId: approverIdentity.identity.deviceId,
publicKey: approverIdentity.publicKey,
role: "operator",
const request = await requestOperatorDevicePairing({
identity: approverIdentity,
scopes: ["operator.admin"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
pairingWs = await openTrackedWs(started.port);
await connectOk(pairingWs, {
skipDefaultAuth: true,
deviceToken: approver.token,
deviceIdentityPath: approver.identityPath,
scopes: ["operator.pairing"],
});
pairingWs = await openPairingSession(started.port, approver);
const approve = await rpcReq(pairingWs, "device.pair.approve", {
requestId: request.request.requestId,
@@ -70,33 +91,16 @@ describe("gateway device.pair.approve caller scope guard", () => {
test("rejects approving another device from a non-admin paired-device session", async () => {
const started = await startServerWithClient("secret");
const approver = await issueOperatorToken({
name: "approve-cross-device-attacker",
approvedScopes: ["operator.admin"],
tokenScopes: ["operator.pairing"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
const approver = await issuePairingOnlyOperator("approve-cross-device-attacker");
const pending = loadDeviceIdentity("approve-cross-device-target");
let pairingWs: WebSocket | undefined;
try {
const request = await requestDevicePairing({
deviceId: pending.identity.deviceId,
publicKey: pending.publicKey,
role: "operator",
scopes: ["operator.pairing"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
pairingWs = await openTrackedWs(started.port);
await connectOk(pairingWs, {
skipDefaultAuth: true,
deviceToken: approver.token,
deviceIdentityPath: approver.identityPath,
const request = await requestOperatorDevicePairing({
identity: pending,
scopes: ["operator.pairing"],
});
pairingWs = await openPairingSession(started.port, approver);
const approve = await rpcReq(pairingWs, "device.pair.approve", {
requestId: request.request.requestId,
@@ -116,33 +120,16 @@ describe("gateway device.pair.approve caller scope guard", () => {
test("rejects rejecting another device from a non-admin paired-device session", async () => {
const started = await startServerWithClient("secret");
const attacker = await issueOperatorToken({
name: "reject-cross-device-attacker",
approvedScopes: ["operator.admin"],
tokenScopes: ["operator.pairing"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
const attacker = await issuePairingOnlyOperator("reject-cross-device-attacker");
const pending = loadDeviceIdentity("reject-cross-device-target");
let pairingWs: WebSocket | undefined;
try {
const request = await requestDevicePairing({
deviceId: pending.identity.deviceId,
publicKey: pending.publicKey,
role: "operator",
scopes: ["operator.pairing"],
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
pairingWs = await openTrackedWs(started.port);
await connectOk(pairingWs, {
skipDefaultAuth: true,
deviceToken: attacker.token,
deviceIdentityPath: attacker.identityPath,
const request = await requestOperatorDevicePairing({
identity: pending,
scopes: ["operator.pairing"],
});
pairingWs = await openPairingSession(started.port, attacker);
const reject = await rpcReq(pairingWs, "device.pair.reject", {
requestId: request.request.requestId,