diff --git a/src/web/inbound/access-control.dm-policy.test.ts b/src/web/inbound/access-control.dm-policy.test.ts new file mode 100644 index 00000000000..04a0a8371b6 --- /dev/null +++ b/src/web/inbound/access-control.dm-policy.test.ts @@ -0,0 +1,105 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { checkInboundAccessControl } from "./access-control.js"; + +const sendMessageMock = vi.fn(); +const readAllowFromStoreMock = vi.fn(); +const upsertPairingRequestMock = vi.fn(); + +let config: Record = {}; + +vi.mock("../../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => config, + }; +}); + +vi.mock("../../pairing/pairing-store.js", () => ({ + readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), +})); + +beforeEach(() => { + config = { + channels: { + whatsapp: { + dmPolicy: "pairing", + allowFrom: [], + }, + }, + }; + sendMessageMock.mockReset().mockResolvedValue(undefined); + readAllowFromStoreMock.mockReset().mockResolvedValue([]); + upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true }); +}); + +describe("WhatsApp dmPolicy precedence", () => { + it("uses account-level dmPolicy instead of channel-level (#8736)", async () => { + // Channel-level says "pairing" but the account-level says "allowlist". + // The account-level override should take precedence, so an unauthorized + // sender should be blocked silently (no pairing reply). + config = { + channels: { + whatsapp: { + dmPolicy: "pairing", + accounts: { + work: { + dmPolicy: "allowlist", + allowFrom: ["+15559999999"], + }, + }, + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "work", + from: "+15550001111", + selfE164: "+15550009999", + senderE164: "+15550001111", + group: false, + pushName: "Stranger", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "15550001111@s.whatsapp.net", + }); + + expect(result.allowed).toBe(false); + expect(upsertPairingRequestMock).not.toHaveBeenCalled(); + expect(sendMessageMock).not.toHaveBeenCalled(); + }); + + it("inherits channel-level dmPolicy when account-level dmPolicy is unset", async () => { + // Account has allowFrom set, but no dmPolicy override. Should inherit the channel default. + // With dmPolicy=allowlist, unauthorized senders are silently blocked. + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + accounts: { + work: { + allowFrom: ["+15559999999"], + }, + }, + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "work", + from: "+15550001111", + selfE164: "+15550009999", + senderE164: "+15550001111", + group: false, + pushName: "Stranger", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "15550001111@s.whatsapp.net", + }); + + expect(result.allowed).toBe(false); + expect(upsertPairingRequestMock).not.toHaveBeenCalled(); + expect(sendMessageMock).not.toHaveBeenCalled(); + }); +}); diff --git a/src/web/inbound/access-control.pairing-history.test.ts b/src/web/inbound/access-control.pairing.test.ts similarity index 68% rename from src/web/inbound/access-control.pairing-history.test.ts rename to src/web/inbound/access-control.pairing.test.ts index d907637e398..b5d5b721cda 100644 --- a/src/web/inbound/access-control.pairing-history.test.ts +++ b/src/web/inbound/access-control.pairing.test.ts @@ -83,41 +83,3 @@ describe("checkInboundAccessControl", () => { expect(sendMessageMock).toHaveBeenCalled(); }); }); - -describe("account-level dmPolicy override (#8736)", () => { - it("uses account-level dmPolicy instead of channel-level", async () => { - // Channel-level says "pairing" but the account-level says "allowlist". - // The account-level override should take precedence, so an unauthorized - // sender should be blocked silently (no pairing reply). - config = { - channels: { - whatsapp: { - dmPolicy: "pairing", - accounts: { - work: { - dmPolicy: "allowlist", - allowFrom: ["+15559999999"], - }, - }, - }, - }, - }; - - const result = await checkInboundAccessControl({ - accountId: "work", - from: "+15550001111", - selfE164: "+15550009999", - senderE164: "+15550001111", - group: false, - pushName: "Stranger", - isFromMe: false, - sock: { sendMessage: sendMessageMock }, - remoteJid: "15550001111@s.whatsapp.net", - }); - - expect(result.allowed).toBe(false); - // dmPolicy "allowlist" should silently block — no pairing request, no reply - expect(upsertPairingRequestMock).not.toHaveBeenCalled(); - expect(sendMessageMock).not.toHaveBeenCalled(); - }); -});