Merge 433ab78f7f into da71eaebd2
This commit is contained in:
commit
251a1fc7eb
209
src/web/inbound/access-control.groups-allowlist.test.ts
Normal file
209
src/web/inbound/access-control.groups-allowlist.test.ts
Normal file
@ -0,0 +1,209 @@
|
||||
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<string, unknown> = {};
|
||||
|
||||
vi.mock("../../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../config/config.js")>();
|
||||
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: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "allowlist",
|
||||
},
|
||||
},
|
||||
};
|
||||
sendMessageMock.mockReset().mockResolvedValue(undefined);
|
||||
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
||||
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
});
|
||||
|
||||
describe("checkInboundAccessControl group allowlist (#3375)", () => {
|
||||
it("allows messages from any sender when group is in groups allowlist", async () => {
|
||||
config = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"123456789@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await checkInboundAccessControl({
|
||||
accountId: "default",
|
||||
from: "123456789@g.us",
|
||||
selfE164: "+15550009999",
|
||||
senderE164: "+15550001111", // Sender NOT in allowFrom
|
||||
group: true,
|
||||
groupId: "123456789@g.us",
|
||||
pushName: "Unknown User",
|
||||
isFromMe: false,
|
||||
sock: { sendMessage: sendMessageMock },
|
||||
remoteJid: "123456789@g.us",
|
||||
});
|
||||
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
it("blocks messages when group is NOT in groups allowlist and sender NOT in groupAllowFrom", async () => {
|
||||
config = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15550009999"],
|
||||
// No groups config - so group is not in allowlist
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await checkInboundAccessControl({
|
||||
accountId: "default",
|
||||
from: "123456789@g.us",
|
||||
selfE164: "+15550009999",
|
||||
senderE164: "+15550001111", // Sender NOT in groupAllowFrom
|
||||
group: true,
|
||||
groupId: "123456789@g.us",
|
||||
pushName: "Unknown User",
|
||||
isFromMe: false,
|
||||
sock: { sendMessage: sendMessageMock },
|
||||
remoteJid: "123456789@g.us",
|
||||
});
|
||||
|
||||
expect(result.allowed).toBe(false);
|
||||
});
|
||||
|
||||
it("allows messages when sender is in groupAllowFrom even if group not in allowlist", async () => {
|
||||
config = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15550001111"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await checkInboundAccessControl({
|
||||
accountId: "default",
|
||||
from: "123456789@g.us",
|
||||
selfE164: "+15550009999",
|
||||
senderE164: "+15550001111", // Sender IS in groupAllowFrom
|
||||
group: true,
|
||||
groupId: "123456789@g.us",
|
||||
pushName: "Allowed User",
|
||||
isFromMe: false,
|
||||
sock: { sendMessage: sendMessageMock },
|
||||
remoteJid: "123456789@g.us",
|
||||
});
|
||||
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
|
||||
it("blocks all group messages when groupPolicy is disabled", async () => {
|
||||
config = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "disabled",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await checkInboundAccessControl({
|
||||
accountId: "default",
|
||||
from: "123456789@g.us",
|
||||
selfE164: "+15550009999",
|
||||
senderE164: "+15550001111",
|
||||
group: true,
|
||||
groupId: "123456789@g.us",
|
||||
pushName: "User",
|
||||
isFromMe: false,
|
||||
sock: { sendMessage: sendMessageMock },
|
||||
remoteJid: "123456789@g.us",
|
||||
});
|
||||
|
||||
expect(result.allowed).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks group messages when groupPolicy is allowlist but no groups configured and no groupAllowFrom", async () => {
|
||||
config = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "allowlist",
|
||||
// No groups and no groupAllowFrom
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await checkInboundAccessControl({
|
||||
accountId: "default",
|
||||
from: "123456789@g.us",
|
||||
selfE164: "+15550009999",
|
||||
senderE164: "+15550001111",
|
||||
group: true,
|
||||
groupId: "123456789@g.us",
|
||||
pushName: "User",
|
||||
isFromMe: false,
|
||||
sock: { sendMessage: sendMessageMock },
|
||||
remoteJid: "123456789@g.us",
|
||||
});
|
||||
|
||||
expect(result.allowed).toBe(false);
|
||||
});
|
||||
|
||||
it("allows messages with groupAllowFrom wildcard", async () => {
|
||||
config = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15550009999"],
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await checkInboundAccessControl({
|
||||
accountId: "default",
|
||||
from: "123456789@g.us",
|
||||
selfE164: "+15550009999",
|
||||
senderE164: "+15550001111",
|
||||
group: true,
|
||||
groupId: "123456789@g.us",
|
||||
pushName: "Any User",
|
||||
isFromMe: false,
|
||||
sock: { sendMessage: sendMessageMock },
|
||||
remoteJid: "123456789@g.us",
|
||||
});
|
||||
|
||||
expect(result.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -23,6 +23,8 @@ export async function checkInboundAccessControl(params: {
|
||||
selfE164: string | null;
|
||||
senderE164: string | null;
|
||||
group: boolean;
|
||||
/** Group JID for checking groups allowlist (only applicable when group=true) */
|
||||
groupId?: string;
|
||||
pushName?: string;
|
||||
isFromMe: boolean;
|
||||
messageTimestampMs?: number;
|
||||
@ -90,28 +92,41 @@ export async function checkInboundAccessControl(params: {
|
||||
};
|
||||
}
|
||||
if (params.group && groupPolicy === "allowlist") {
|
||||
if (!groupAllowFrom || groupAllowFrom.length === 0) {
|
||||
logVerbose("Blocked group message (groupPolicy: allowlist, no groupAllowFrom)");
|
||||
return {
|
||||
allowed: false,
|
||||
shouldMarkRead: false,
|
||||
isSelfChat,
|
||||
resolvedAccountId: account.accountId,
|
||||
};
|
||||
}
|
||||
const senderAllowed =
|
||||
groupHasWildcard ||
|
||||
(params.senderE164 != null && normalizedGroupAllowFrom.includes(params.senderE164));
|
||||
if (!senderAllowed) {
|
||||
logVerbose(
|
||||
`Blocked group message from ${params.senderE164 ?? "unknown sender"} (groupPolicy: allowlist)`,
|
||||
);
|
||||
return {
|
||||
allowed: false,
|
||||
shouldMarkRead: false,
|
||||
isSelfChat,
|
||||
resolvedAccountId: account.accountId,
|
||||
};
|
||||
// Check if group is explicitly in the groups allowlist.
|
||||
// If so, allow messages from all participants in this group (#3375).
|
||||
const groups = account.groups;
|
||||
const groupInAllowlist = params.groupId && groups && params.groupId in groups;
|
||||
|
||||
if (groupInAllowlist) {
|
||||
logVerbose(`Allowing message from allowlisted group ${params.groupId}`);
|
||||
// Continue to allow; don't return early so DM checks are skipped but message proceeds.
|
||||
} else {
|
||||
// Group not in allowlist - fall back to sender-based filtering via groupAllowFrom
|
||||
if (!groupAllowFrom || groupAllowFrom.length === 0) {
|
||||
logVerbose(
|
||||
`Blocked group message from ${params.groupId ?? "unknown group"} (groupPolicy: allowlist, group not in allowlist, no groupAllowFrom)`,
|
||||
);
|
||||
return {
|
||||
allowed: false,
|
||||
shouldMarkRead: false,
|
||||
isSelfChat,
|
||||
resolvedAccountId: account.accountId,
|
||||
};
|
||||
}
|
||||
const senderAllowed =
|
||||
groupHasWildcard ||
|
||||
(params.senderE164 != null && normalizedGroupAllowFrom.includes(params.senderE164));
|
||||
if (!senderAllowed) {
|
||||
logVerbose(
|
||||
`Blocked group message from ${params.senderE164 ?? "unknown sender"} in ${params.groupId ?? "unknown group"} (groupPolicy: allowlist, sender not in groupAllowFrom)`,
|
||||
);
|
||||
return {
|
||||
allowed: false,
|
||||
shouldMarkRead: false,
|
||||
isSelfChat,
|
||||
resolvedAccountId: account.accountId,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,6 +183,7 @@ export async function monitorWebInbox(options: {
|
||||
selfE164,
|
||||
senderE164,
|
||||
group,
|
||||
groupId: group ? remoteJid : undefined,
|
||||
pushName: msg.pushName ?? undefined,
|
||||
isFromMe: Boolean(msg.key?.fromMe),
|
||||
messageTimestampMs,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user