fix(pair): guard setup fallback subcommands

This commit is contained in:
Agustin Rivera
2026-04-03 20:52:48 +00:00
committed by Peter Steinberger
parent 9bb97b54fe
commit cb0b15a195
2 changed files with 32 additions and 1 deletions

View File

@@ -453,6 +453,23 @@ describe("device-pair /pair default setup code", () => {
text: "⚠️ This command requires operator.pairing for internal gateway callers.",
});
});
it("rejects unknown subcommands that fall back to setup code issuance without operator.pairing", async () => {
const command = registerPairCommand();
const result = await command.handler(
createCommandContext({
channel: "webchat",
args: "foo",
commandBody: "/pair foo",
gatewayClientScopes: ["operator.write"],
}),
);
expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
expect(result).toEqual({
text: "⚠️ This command requires operator.pairing for internal gateway callers.",
});
});
});
describe("device-pair notify pending formatting", () => {

View File

@@ -495,6 +495,20 @@ function isMissingPairingScope(gatewayClientScopes: string[] | null): boolean {
);
}
const PAIR_SETUP_NON_ISSUING_ACTIONS = new Set([
"approve",
"cleanup",
"clear",
"notify",
"pending",
"revoke",
"status",
]);
function issuesPairSetupCode(action: string): boolean {
return !action || action === "qr" || !PAIR_SETUP_NON_ISSUING_ACTIONS.has(action);
}
async function issueSetupPayload(url: string): Promise<SetupPayload> {
const issuedBootstrap = await issueDeviceBootstrapToken({
profile: PAIRING_SETUP_BOOTSTRAP_PROFILE,
@@ -642,7 +656,7 @@ export default definePluginEntry({
if (authLabelResult.error) {
return { text: `Error: ${authLabelResult.error}` };
}
if ((!action || action === "qr") && isMissingPairingScope(gatewayClientScopes)) {
if (issuesPairSetupCode(action) && isMissingPairingScope(gatewayClientScopes)) {
return buildMissingPairingScopeReply();
}