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;
|
selfE164: string | null;
|
||||||
senderE164: string | null;
|
senderE164: string | null;
|
||||||
group: boolean;
|
group: boolean;
|
||||||
|
/** Group JID for checking groups allowlist (only applicable when group=true) */
|
||||||
|
groupId?: string;
|
||||||
pushName?: string;
|
pushName?: string;
|
||||||
isFromMe: boolean;
|
isFromMe: boolean;
|
||||||
messageTimestampMs?: number;
|
messageTimestampMs?: number;
|
||||||
@ -90,28 +92,41 @@ export async function checkInboundAccessControl(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (params.group && groupPolicy === "allowlist") {
|
if (params.group && groupPolicy === "allowlist") {
|
||||||
if (!groupAllowFrom || groupAllowFrom.length === 0) {
|
// Check if group is explicitly in the groups allowlist.
|
||||||
logVerbose("Blocked group message (groupPolicy: allowlist, no groupAllowFrom)");
|
// If so, allow messages from all participants in this group (#3375).
|
||||||
return {
|
const groups = account.groups;
|
||||||
allowed: false,
|
const groupInAllowlist = params.groupId && groups && params.groupId in groups;
|
||||||
shouldMarkRead: false,
|
|
||||||
isSelfChat,
|
if (groupInAllowlist) {
|
||||||
resolvedAccountId: account.accountId,
|
logVerbose(`Allowing message from allowlisted group ${params.groupId}`);
|
||||||
};
|
// Continue to allow; don't return early so DM checks are skipped but message proceeds.
|
||||||
}
|
} else {
|
||||||
const senderAllowed =
|
// Group not in allowlist - fall back to sender-based filtering via groupAllowFrom
|
||||||
groupHasWildcard ||
|
if (!groupAllowFrom || groupAllowFrom.length === 0) {
|
||||||
(params.senderE164 != null && normalizedGroupAllowFrom.includes(params.senderE164));
|
logVerbose(
|
||||||
if (!senderAllowed) {
|
`Blocked group message from ${params.groupId ?? "unknown group"} (groupPolicy: allowlist, group not in allowlist, no groupAllowFrom)`,
|
||||||
logVerbose(
|
);
|
||||||
`Blocked group message from ${params.senderE164 ?? "unknown sender"} (groupPolicy: allowlist)`,
|
return {
|
||||||
);
|
allowed: false,
|
||||||
return {
|
shouldMarkRead: false,
|
||||||
allowed: false,
|
isSelfChat,
|
||||||
shouldMarkRead: false,
|
resolvedAccountId: account.accountId,
|
||||||
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,
|
selfE164,
|
||||||
senderE164,
|
senderE164,
|
||||||
group,
|
group,
|
||||||
|
groupId: group ? remoteJid : undefined,
|
||||||
pushName: msg.pushName ?? undefined,
|
pushName: msg.pushName ?? undefined,
|
||||||
isFromMe: Boolean(msg.key?.fromMe),
|
isFromMe: Boolean(msg.key?.fromMe),
|
||||||
messageTimestampMs,
|
messageTimestampMs,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user