tighten phone-control scope helper extraction

This commit is contained in:
Robin Waslander
2026-03-30 16:17:17 +02:00
parent c5c10adc02
commit 85647949a4
2 changed files with 15 additions and 9 deletions

View File

@@ -167,7 +167,7 @@ describe("phone-control plugin", () => {
});
});
it("blocks non-webchat gateway callers with operator.write from mutating phone control", async () => {
it("regression: blocks non-webchat gateway callers with operator.write from arm/disarm", async () => {
await withRegisteredPhoneControl(async ({ command, writeConfigFile }) => {
const armRes = await command.handler({
...createCommandContext("arm writes 30s"),
@@ -183,6 +183,7 @@ describe("phone-control plugin", () => {
gatewayClientScopes: ["operator.write"],
});
expect(String(disarmRes?.text ?? "")).toContain("requires operator.admin");
expect(writeConfigFile).not.toHaveBeenCalled();
});
});

View File

@@ -29,6 +29,7 @@ type ArmStateFile = ArmStateFileV1 | ArmStateFileV2;
const STATE_VERSION = 2;
const STATE_REL_PATH = ["plugins", "phone-control", "armed.json"] as const;
const PHONE_ADMIN_SCOPE = "operator.admin";
const GROUP_COMMANDS: Record<Exclude<ArmGroup, "all">, string[]> = {
camera: ["camera.snap", "camera.clip"],
@@ -268,6 +269,16 @@ function parseGroup(raw: string | undefined): ArmGroup | null {
return null;
}
function requiresAdminToMutatePhoneControl(
channel: string,
gatewayClientScopes?: readonly string[],
): boolean {
if (Array.isArray(gatewayClientScopes)) {
return !gatewayClientScopes.includes(PHONE_ADMIN_SCOPE);
}
return channel === "webchat";
}
function formatStatus(state: ArmStateFile | null): string {
if (!state) {
return "Phone control: disarmed.";
@@ -358,10 +369,7 @@ export default definePluginEntry({
}
if (action === "disarm") {
if (
(ctx.channel === "webchat" || Array.isArray(ctx.gatewayClientScopes)) &&
!ctx.gatewayClientScopes?.includes("operator.admin")
) {
if (requiresAdminToMutatePhoneControl(ctx.channel, ctx.gatewayClientScopes)) {
return {
text: "⚠️ /phone disarm requires operator.admin.",
};
@@ -383,10 +391,7 @@ export default definePluginEntry({
}
if (action === "arm") {
if (
(ctx.channel === "webchat" || Array.isArray(ctx.gatewayClientScopes)) &&
!ctx.gatewayClientScopes?.includes("operator.admin")
) {
if (requiresAdminToMutatePhoneControl(ctx.channel, ctx.gatewayClientScopes)) {
return {
text: "⚠️ /phone arm requires operator.admin.",
};