dsh-im-ops/plugin-src/host/channels/shared/access-policy-production.mjs
2026-09-01 10:46:41 +08:00

137 lines
4.5 KiB
JavaScript

import {
createAccessPolicy,
createAccessPolicyScope,
} from '../../../../src/channels/shared/access-policy.mjs';
function policyUsers(users) {
return users.map((id) => ({ id, canExecuteCommands: true }));
}
function openScope(allowlistUsers = []) {
return createAccessPolicyScope({
mode: 'open',
open: {
defaultCanExecuteCommands: true,
commandPermissionOverrides: [],
},
allowlist: { users: policyUsers(allowlistUsers) },
});
}
function allowlistScope(users = []) {
return createAccessPolicyScope({
mode: 'allowlist',
open: {
defaultCanExecuteCommands: false,
commandPermissionOverrides: [],
},
allowlist: { users: policyUsers(users) },
});
}
function cleanIds(values) {
return [...new Set((Array.isArray(values) ? values : [values])
.filter((value) => typeof value === 'string' || typeof value === 'number'
|| typeof value === 'bigint')
.map((value) => String(value).trim())
.filter(Boolean))];
}
function whatsappNumberJids(values) {
return cleanIds(values).map((value) => `${value.replace(/^\+/, '')}@s.whatsapp.net`);
}
/**
* Build the one-time, backwards-compatible seed for a bot whose workspace
* document does not yet contain an access policy.
*/
export function initialAccessPolicyFor(channel, config = {}) {
const key = String(channel ?? '').trim().toLowerCase();
if (key === 'weixin') {
return createAccessPolicy({
direct: allowlistScope(),
group: allowlistScope(),
});
}
if (key === 'feishu') {
const owners = cleanIds(config.ownerOpenIds ?? config.ownerOpenId);
const scope = owners.includes('*') ? openScope() : allowlistScope();
return createAccessPolicy({ direct: scope, group: scope });
}
if (key === 'qq') {
const owners = cleanIds(config.ownerUserOpenid);
return createAccessPolicy({
direct: owners.includes('*') ? openScope() : allowlistScope(),
group: openScope(),
});
}
if (key === 'telegram') {
const users = cleanIds(config.allowedUsers);
if ((config.accessMode ?? 'compatible') === 'private-allowlist') {
return createAccessPolicy({
direct: allowlistScope(users),
group: allowlistScope(),
});
}
return createAccessPolicy({
direct: openScope(users),
group: openScope(),
});
}
if (key === 'whatsapp') {
const mode = config.accessMode ?? 'self-only';
const allowed = whatsappNumberJids(config.allowedNumbers);
if (mode === 'open') {
return createAccessPolicy({ direct: openScope(allowed), group: openScope() });
}
return createAccessPolicy({
direct: allowlistScope(mode === 'private-allowlist'
? allowed
: []),
group: allowlistScope(),
});
}
if (['dingtalk', 'wecom', 'slack', 'discord'].includes(key)) {
return createAccessPolicy({ direct: openScope(), group: openScope() });
}
throw new TypeError(`Unsupported access-policy channel: ${channel}`);
}
export function privilegedSenderIdsFor(channel, config = {}) {
const key = String(channel ?? '').trim().toLowerCase();
if (key === 'weixin') return cleanIds(config.ownerUserId);
if (key === 'feishu') {
return cleanIds(config.ownerOpenIds ?? config.ownerOpenId).filter((id) => id !== '*');
}
if (key === 'dingtalk') {
const approved = Array.isArray(config.approvedSenders) ? config.approvedSenders : [];
return cleanIds(approved.map((entry) => entry?.staffId));
}
if (key === 'qq') return cleanIds(config.ownerUserOpenid).filter((id) => id !== '*');
if (key === 'whatsapp') return cleanIds(config.accountJid);
return [];
}
export function accessPolicyProvider(workspaces, botId, { channel, config, equals } = {}) {
if (!workspaces || typeof workspaces.accessPolicyFor !== 'function') {
throw new TypeError('A workspace store with access policies is required');
}
const privilegedSenderIds = new Set(privilegedSenderIdsFor(channel, config));
const sameSender = typeof equals === 'function' ? equals : (left, right) => left === right;
return Object.freeze({
botId,
getSettings: () => workspaces.accessPolicyFor(botId),
isPrivileged(senderIds, conversationType) {
if (!['direct', 'group'].includes(conversationType)) return false;
const candidates = Array.isArray(senderIds) ? senderIds : [senderIds];
try {
return candidates.some((senderId) => typeof senderId === 'string'
&& [...privilegedSenderIds].some((privilegedId) => (
sameSender(senderId.trim(), privilegedId) === true
)));
} catch {
return false;
}
},
});
}