





















@@ -258,6 +258,7 @@ describe("device-pair /pair qr", () => {
258258259259it("returns an inline QR image for webchat surfaces", async () => {
260260const command = registerPairCommand();
261+expect(command.requiredScopes).toEqual(["operator.pairing"]);
261262const result = await command.handler(
262263createCommandContext({
263264channel: "webchat",
@@ -296,7 +297,24 @@ describe("device-pair /pair qr", () => {
296297297298expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
298299expect(result).toEqual({
299-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
300+text: "⚠️ This command requires operator.pairing.",
301+});
302+});
303+304+it("rejects qr setup for non-gateway command surfaces without pairing scopes", async () => {
305+const command = registerPairCommand();
306+const result = await command.handler(
307+createCommandContext({
308+channel: "telegram",
309+args: "qr",
310+commandBody: "/pair qr",
311+gatewayClientScopes: undefined,
312+}),
313+);
314+315+expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
316+expect(result).toEqual({
317+text: "⚠️ This command requires operator.pairing.",
300318});
301319});
302320@@ -433,7 +451,12 @@ describe("device-pair /pair qr", () => {
433451runtime: createChannelRuntime(testCase.runtimeKey, testCase.sendKey, sendMessage),
434452});
435453436-const result = await command.handler(createCommandContext(testCase.ctx));
454+const result = await command.handler(
455+createCommandContext({
456+ ...testCase.ctx,
457+gatewayClientScopes: INTERNAL_PAIRING_SCOPES,
458+}),
459+);
437460const text = requireText(result);
438461439462expect(sendMessage).toHaveBeenCalledTimes(1);
@@ -479,6 +502,7 @@ describe("device-pair /pair qr", () => {
479502createCommandContext({
480503channel: "discord",
481504senderId: "123",
505+gatewayClientScopes: INTERNAL_PAIRING_SCOPES,
482506}),
483507);
484508const text = requireText(result);
@@ -497,6 +521,7 @@ describe("device-pair /pair qr", () => {
497521createCommandContext({
498522channel: "msteams",
499523senderId: "8:orgid:123",
524+gatewayClientScopes: INTERNAL_PAIRING_SCOPES,
500525}),
501526);
502527const text = requireText(result);
@@ -514,6 +539,7 @@ describe("device-pair /pair qr", () => {
514539channel: "telegram",
515540args: "cleanup",
516541commandBody: "/pair cleanup",
542+gatewayClientScopes: INTERNAL_PAIRING_SCOPES,
517543}),
518544);
519545@@ -534,7 +560,7 @@ describe("device-pair /pair qr", () => {
534560535561expect(pluginApiMocks.clearDeviceBootstrapTokens).not.toHaveBeenCalled();
536562expect(result).toEqual({
537-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
563+text: "⚠️ This command requires operator.pairing.",
538564});
539565});
540566@@ -551,7 +577,24 @@ describe("device-pair /pair qr", () => {
551577552578expect(pluginApiMocks.clearDeviceBootstrapTokens).not.toHaveBeenCalled();
553579expect(result).toEqual({
554-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
580+text: "⚠️ This command requires operator.pairing.",
581+});
582+});
583+584+it("rejects status for non-gateway command surfaces without pairing scopes", async () => {
585+const command = registerPairCommand();
586+const result = await command.handler(
587+createCommandContext({
588+channel: "telegram",
589+args: "status",
590+commandBody: "/pair status",
591+gatewayClientScopes: undefined,
592+}),
593+);
594+595+expect(vi.mocked(listDevicePairing)).not.toHaveBeenCalled();
596+expect(result).toEqual({
597+text: "⚠️ This command requires operator.pairing.",
555598});
556599});
557600});
@@ -578,7 +621,7 @@ describe("device-pair /pair default setup code", () => {
578621579622expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
580623expect(result).toEqual({
581-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
624+text: "⚠️ This command requires operator.pairing.",
582625});
583626});
584627@@ -595,7 +638,7 @@ describe("device-pair /pair default setup code", () => {
595638596639expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
597640expect(result).toEqual({
598-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
641+text: "⚠️ This command requires operator.pairing.",
599642});
600643});
601644@@ -612,10 +655,44 @@ describe("device-pair /pair default setup code", () => {
612655613656expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
614657expect(result).toEqual({
615-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
658+text: "⚠️ This command requires operator.pairing.",
616659});
617660});
618661662+it("fails closed for non-gateway setup code issuance when scopes are absent", async () => {
663+const command = registerPairCommand();
664+const result = await command.handler(
665+createCommandContext({
666+channel: "telegram",
667+args: "",
668+commandBody: "/pair",
669+gatewayClientScopes: undefined,
670+}),
671+);
672+673+expect(pluginApiMocks.issueDeviceBootstrapToken).not.toHaveBeenCalled();
674+expect(result).toEqual({
675+text: "⚠️ This command requires operator.pairing.",
676+});
677+});
678+679+it("allows command owners to issue setup codes from non-gateway command surfaces", async () => {
680+const command = registerPairCommand();
681+const result = await command.handler(
682+createCommandContext({
683+channel: "telegram",
684+args: "",
685+commandBody: "/pair",
686+gatewayClientScopes: undefined,
687+senderIsOwner: true,
688+}),
689+);
690+const text = requireText(result);
691+692+expect(pluginApiMocks.issueDeviceBootstrapToken).toHaveBeenCalledTimes(1);
693+expect(text).toContain("Pairing setup code generated.");
694+});
695+619696it("normalizes secure bare publicUrl host ports before issuing setup codes", async () => {
620697const command = registerPairCommand({
621698config: {
@@ -909,7 +986,7 @@ describe("device-pair /pair approve", () => {
909986910987expect(vi.mocked(approveDevicePairing)).not.toHaveBeenCalled();
911988expect(result).toEqual({
912-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
989+text: "⚠️ This command requires operator.pairing.",
913990});
914991});
915992@@ -924,7 +1001,24 @@ describe("device-pair /pair approve", () => {
9241001expect(result).toEqual({ text: "✅ Paired Victim Phone (ios)." });
9251002});
9261003927-it("does not force an empty caller scope context for external approvals", async () => {
1004+it("rejects non-gateway approvals without pairing scopes", async () => {
1005+const command = registerPairCommand();
1006+const result = await command.handler(
1007+createCommandContext({
1008+channel: "telegram",
1009+args: "approve latest",
1010+commandBody: "/pair approve latest",
1011+gatewayClientScopes: undefined,
1012+}),
1013+);
1014+1015+expect(vi.mocked(approveDevicePairing)).not.toHaveBeenCalled();
1016+expect(result).toEqual({
1017+text: "⚠️ This command requires operator.pairing.",
1018+});
1019+});
1020+1021+it("allows command owners to approve from non-gateway command surfaces", async () => {
9281022mockPendingPairingList();
9291023vi.mocked(approveDevicePairing).mockResolvedValueOnce(makeApprovedPairingResult());
9301024@@ -935,10 +1029,32 @@ describe("device-pair /pair approve", () => {
9351029args: "approve latest",
9361030commandBody: "/pair approve latest",
9371031gatewayClientScopes: undefined,
1032+senderIsOwner: true,
9381033}),
9391034);
9401035941-expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1");
1036+expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1", {
1037+callerScopes: ["operator.pairing"],
1038+});
1039+expect(result).toEqual({ text: "✅ Paired Victim Phone (ios)." });
1040+});
1041+1042+it("preserves gateway caller scopes for command-owner approvals", async () => {
1043+mockPendingPairingList();
1044+vi.mocked(approveDevicePairing).mockResolvedValueOnce(makeApprovedPairingResult());
1045+1046+const command = registerPairCommand();
1047+const result = await command.handler(
1048+createCommandContext({
1049+channel: "telegram",
1050+args: "approve latest",
1051+commandBody: "/pair approve latest",
1052+gatewayClientScopes: INTERNAL_PAIRING_SCOPES,
1053+senderIsOwner: true,
1054+}),
1055+);
1056+1057+expectApproveCalledWithInternalPairingScopes();
9421058expect(result).toEqual({ text: "✅ Paired Victim Phone (ios)." });
9431059});
9441060@@ -957,7 +1073,7 @@ describe("device-pair /pair approve", () => {
95710739581074expect(vi.mocked(approveDevicePairing)).not.toHaveBeenCalled();
9591075expect(result).toEqual({
960-text: "⚠️ This command requires operator.pairing for internal gateway callers.",
1076+text: "⚠️ This command requires operator.pairing.",
9611077});
9621078});
9631079@@ -978,36 +1094,21 @@ describe("device-pair /pair approve", () => {
9781094});
9791095});
9801096981-it("preserves approvals for non-gateway command surfaces", async () => {
1097+it("approves from command surfaces that carry pairing scopes", async () => {
9821098mockPendingPairingList();
983-vi.mocked(approveDevicePairing).mockResolvedValueOnce(
984-makeApprovedPairingResult({
985-device: {
986-scopes: ["operator.admin"],
987-approvedScopes: ["operator.admin"],
988-tokens: {
989-operator: {
990-token: "token-1",
991-role: "operator",
992-scopes: ["operator.admin"],
993-createdAtMs: Date.now(),
994-},
995-},
996-},
997-}),
998-);
1099+vi.mocked(approveDevicePairing).mockResolvedValueOnce(makeApprovedPairingResult());
999110010001101const command = registerPairCommand();
10011102const result = await command.handler(
10021103createCommandContext({
10031104channel: "telegram",
10041105args: "approve latest",
10051106commandBody: "/pair approve latest",
1006-gatewayClientScopes: undefined,
1107+gatewayClientScopes: INTERNAL_PAIRING_SCOPES,
10071108}),
10081109);
100911101010-expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1");
1111+expectApproveCalledWithInternalPairingScopes();
10111112expect(result).toEqual({ text: "✅ Paired Victim Phone (ios)." });
10121113});
10131114});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。