Groups: policy, group-JID allowlist, and that participants are still authorised by the gateway sender allowlist or pairing (`open` alone admits nobody without one); `require_mention` defaults to false; WHATSAPP_GROUP_POLICY / WHATSAPP_GROUP_ALLOWED_USERS rows in the environment reference. The alt-id node tests collapse to one (the participantAlt case duplicated the first-contact case; the "still resolves via mapping files" case only re-asserted matchesAllowedUser).
125 lines
5.4 KiB
JavaScript
125 lines
5.4 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
|
|
import {
|
|
expandWhatsAppIdentifiers,
|
|
matchesAllowedSender,
|
|
matchesInboundWhatsAppGroup,
|
|
matchesAllowedUser,
|
|
normalizeWhatsAppIdentifier,
|
|
parseAllowedUsers,
|
|
} from './allowlist.js';
|
|
|
|
test('normalizeWhatsAppIdentifier strips jid syntax and plus prefix', () => {
|
|
assert.equal(normalizeWhatsAppIdentifier('+19175395595@s.whatsapp.net'), '19175395595');
|
|
assert.equal(normalizeWhatsAppIdentifier('267383306489914@lid'), '267383306489914');
|
|
assert.equal(normalizeWhatsAppIdentifier('19175395595:12@s.whatsapp.net'), '19175395595');
|
|
});
|
|
|
|
test('expandWhatsAppIdentifiers resolves phone and lid aliases from session files', () => {
|
|
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
|
|
|
try {
|
|
writeFileSync(path.join(sessionDir, 'lid-mapping-19175395595.json'), JSON.stringify('267383306489914'));
|
|
writeFileSync(path.join(sessionDir, 'lid-mapping-267383306489914_reverse.json'), JSON.stringify('19175395595'));
|
|
|
|
const aliases = expandWhatsAppIdentifiers('267383306489914@lid', sessionDir);
|
|
assert.deepEqual([...aliases].sort(), ['19175395595', '267383306489914']);
|
|
} finally {
|
|
rmSync(sessionDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('matchesAllowedUser accepts mapped lid sender when allowlist only contains phone number', () => {
|
|
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
|
|
|
try {
|
|
writeFileSync(path.join(sessionDir, 'lid-mapping-19175395595.json'), JSON.stringify('267383306489914'));
|
|
writeFileSync(path.join(sessionDir, 'lid-mapping-267383306489914_reverse.json'), JSON.stringify('19175395595'));
|
|
|
|
const allowedUsers = parseAllowedUsers('+19175395595');
|
|
assert.equal(matchesAllowedUser('267383306489914@lid', allowedUsers, sessionDir), true);
|
|
assert.equal(matchesAllowedUser('188012763865257@lid', allowedUsers, sessionDir), false);
|
|
} finally {
|
|
rmSync(sessionDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('matchesAllowedSender accepts the Baileys alt phone JID when a first-contact LID has no mapping yet (#63415, #72529)', () => {
|
|
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
|
|
|
try {
|
|
const allowedUsers = parseAllowedUsers('+19175395595');
|
|
// On a first message from a WhatsApp Multi-Device contact, Baileys can
|
|
// emit a LID as senderId before it has written LID mapping files, while
|
|
// key.remoteJidAlt / key.participantAlt carries the phone JID.
|
|
assert.equal(
|
|
matchesAllowedSender('267383306489914@lid', '19175395595@s.whatsapp.net', allowedUsers, sessionDir),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
matchesAllowedSender('267383306489914@lid', '18881234567@s.whatsapp.net', allowedUsers, sessionDir),
|
|
false,
|
|
);
|
|
} finally {
|
|
rmSync(sessionDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('matchesAllowedUser treats * as allow-all wildcard', () => {
|
|
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
|
|
|
try {
|
|
const allowedUsers = parseAllowedUsers('*');
|
|
assert.equal(matchesAllowedUser('19175395595@s.whatsapp.net', allowedUsers, sessionDir), true);
|
|
assert.equal(matchesAllowedUser('267383306489914@lid', allowedUsers, sessionDir), true);
|
|
} finally {
|
|
rmSync(sessionDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('group intake follows group policy and group-JID allowlist independently of DM users', () => {
|
|
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
|
|
|
try {
|
|
const groupAllowedUsers = parseAllowedUsers('120363001234567890@g.us');
|
|
const base = {
|
|
chatId: '120363001234567890@g.us',
|
|
groupAllowedUsers,
|
|
sessionDir,
|
|
};
|
|
|
|
assert.equal(matchesInboundWhatsAppGroup({ ...base, groupPolicy: 'allowlist' }), true);
|
|
assert.equal(matchesInboundWhatsAppGroup({ ...base, chatId: 'other@g.us', groupPolicy: 'allowlist' }), false);
|
|
assert.equal(matchesInboundWhatsAppGroup({ ...base, chatId: 'other@g.us', groupPolicy: 'open' }), true);
|
|
assert.equal(matchesInboundWhatsAppGroup({ ...base, groupPolicy: 'disabled' }), false);
|
|
assert.equal(matchesInboundWhatsAppGroup({ ...base, groupPolicy: 'pairing' }), false);
|
|
} finally {
|
|
rmSync(sessionDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('matchesAllowedUser rejects everyone when allowlist is empty (#8389)', () => {
|
|
// Regression guard: empty allowlist used to return true (allow-everyone),
|
|
// which let any stranger DM the bridge and trigger a Python-side
|
|
// pairing-code reply. Secure default is now "reject unless explicitly
|
|
// configured"; operators who want an open bot must set `*`.
|
|
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
|
|
|
try {
|
|
const empty = parseAllowedUsers('');
|
|
assert.equal(empty.size, 0);
|
|
assert.equal(matchesAllowedUser('19175395595@s.whatsapp.net', empty, sessionDir), false);
|
|
assert.equal(matchesAllowedUser('267383306489914@lid', empty, sessionDir), false);
|
|
|
|
// Null/undefined allowlist (defensive) also rejects.
|
|
assert.equal(matchesAllowedUser('19175395595@s.whatsapp.net', null, sessionDir), false);
|
|
assert.equal(matchesAllowedUser('19175395595@s.whatsapp.net', undefined, sessionDir), false);
|
|
} finally {
|
|
rmSync(sessionDir, { recursive: true, force: true });
|
|
}
|
|
});
|