b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
89 lines
2.3 KiB
JavaScript
89 lines
2.3 KiB
JavaScript
import path from 'path';
|
|
import { existsSync, readFileSync } from 'fs';
|
|
|
|
export function normalizeWhatsAppIdentifier(value) {
|
|
return String(value || '')
|
|
.trim()
|
|
.replace(/:.*@/, '@')
|
|
.replace(/@.*/, '')
|
|
.replace(/^\+/, '');
|
|
}
|
|
|
|
export function parseAllowedUsers(rawValue) {
|
|
return new Set(
|
|
String(rawValue || '')
|
|
.split(',')
|
|
.map((value) => normalizeWhatsAppIdentifier(value))
|
|
.filter(Boolean)
|
|
);
|
|
}
|
|
|
|
function readMappingFile(sessionDir, identifier, suffix = '') {
|
|
const filePath = path.join(sessionDir, `lid-mapping-${identifier}${suffix}.json`);
|
|
if (!existsSync(filePath)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
const normalized = normalizeWhatsAppIdentifier(parsed);
|
|
return normalized || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function expandWhatsAppIdentifiers(identifier, sessionDir) {
|
|
const normalized = normalizeWhatsAppIdentifier(identifier);
|
|
if (!normalized) {
|
|
return new Set();
|
|
}
|
|
|
|
// Walk both phone->LID and LID->phone mapping files so allowlists can use
|
|
// either form transparently in bot mode.
|
|
const resolved = new Set();
|
|
const queue = [normalized];
|
|
|
|
while (queue.length > 0) {
|
|
const current = queue.shift();
|
|
if (!current || resolved.has(current)) {
|
|
continue;
|
|
}
|
|
|
|
resolved.add(current);
|
|
|
|
for (const suffix of ['', '_reverse']) {
|
|
const mapped = readMappingFile(sessionDir, current, suffix);
|
|
if (mapped && !resolved.has(mapped)) {
|
|
queue.push(mapped);
|
|
}
|
|
}
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
export function matchesAllowedUser(senderId, allowedUsers, sessionDir) {
|
|
// Empty allowlist = NO ONE allowed (secure default, #8389). Operators
|
|
// who want an open bot must set ``WHATSAPP_ALLOWED_USERS=*`` explicitly.
|
|
// Previous behaviour (empty → return true) let any stranger DM the
|
|
// bridge and trigger a Python-side pairing-code reply.
|
|
if (!allowedUsers || allowedUsers.size === 0) {
|
|
return false;
|
|
}
|
|
|
|
// "*" means allow everyone (consistent with SIGNAL_GROUP_ALLOWED_USERS)
|
|
if (allowedUsers.has('*')) {
|
|
return true;
|
|
}
|
|
|
|
const aliases = expandWhatsAppIdentifiers(senderId, sessionDir);
|
|
for (const alias of aliases) {
|
|
if (allowedUsers.has(alias)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|