refactor: unify reaction ingress policy guards across channels

This commit is contained in:
Peter Steinberger
2026-02-26 01:34:37 +01:00
parent 876018f322
commit 8f8e46d898
6 changed files with 289 additions and 188 deletions

View File

@@ -7,8 +7,7 @@ import {
logTypingFailure,
recordPendingHistoryEntryIfEnabled,
resolveAckReaction,
resolveDmGroupAccessDecision,
resolveEffectiveAllowFromLists,
resolveDmGroupAccessWithLists,
resolveControlCommandGate,
stripMarkdown,
type HistoryEntry,
@@ -504,24 +503,13 @@ export async function processMessage(
const storeAllowFrom = await core.channel.pairing
.readAllowFromStore("bluebubbles")
.catch(() => []);
const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveEffectiveAllowFromLists({
allowFrom: account.config.allowFrom,
groupAllowFrom: account.config.groupAllowFrom,
storeAllowFrom,
dmPolicy,
});
const groupAllowEntry = formatGroupAllowlistEntry({
chatGuid: message.chatGuid,
chatId: message.chatId ?? undefined,
chatIdentifier: message.chatIdentifier ?? undefined,
});
const groupName = message.chatName?.trim() || undefined;
const accessDecision = resolveDmGroupAccessDecision({
const accessDecision = resolveDmGroupAccessWithLists({
isGroup,
dmPolicy,
groupPolicy,
effectiveAllowFrom,
effectiveGroupAllowFrom,
allowFrom: account.config.allowFrom,
groupAllowFrom: account.config.groupAllowFrom,
storeAllowFrom,
isSenderAllowed: (allowFrom) =>
isAllowedBlueBubblesSender({
allowFrom,
@@ -531,6 +519,14 @@ export async function processMessage(
chatIdentifier: message.chatIdentifier ?? undefined,
}),
});
const effectiveAllowFrom = accessDecision.effectiveAllowFrom;
const effectiveGroupAllowFrom = accessDecision.effectiveGroupAllowFrom;
const groupAllowEntry = formatGroupAllowlistEntry({
chatGuid: message.chatGuid,
chatId: message.chatId ?? undefined,
chatIdentifier: message.chatIdentifier ?? undefined,
});
const groupName = message.chatName?.trim() || undefined;
if (accessDecision.decision !== "allow") {
if (isGroup) {
@@ -1389,18 +1385,13 @@ export async function processReaction(
const storeAllowFrom = await core.channel.pairing
.readAllowFromStore("bluebubbles")
.catch(() => []);
const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveEffectiveAllowFromLists({
allowFrom: account.config.allowFrom,
groupAllowFrom: account.config.groupAllowFrom,
storeAllowFrom,
dmPolicy,
});
const accessDecision = resolveDmGroupAccessDecision({
const accessDecision = resolveDmGroupAccessWithLists({
isGroup: reaction.isGroup,
dmPolicy,
groupPolicy,
effectiveAllowFrom,
effectiveGroupAllowFrom,
allowFrom: account.config.allowFrom,
groupAllowFrom: account.config.groupAllowFrom,
storeAllowFrom,
isSenderAllowed: (allowFrom) =>
isAllowedBlueBubblesSender({
allowFrom,

View File

@@ -17,6 +17,7 @@ import {
recordPendingHistoryEntryIfEnabled,
isDangerousNameMatchingEnabled,
resolveControlCommandGate,
resolveDmGroupAccessWithLists,
resolveAllowlistProviderRuntimeGroupPolicy,
resolveDefaultGroupPolicy,
resolveChannelMediaMaxBytes,
@@ -883,68 +884,38 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
const kind = channelKind(channelInfo.type);
// Enforce DM/group policy and allowlist checks (same as normal messages)
if (kind === "direct") {
const dmPolicy = account.config.dmPolicy ?? "pairing";
if (dmPolicy === "disabled") {
logVerboseMessage(`mattermost: drop reaction (dmPolicy=disabled sender=${userId})`);
return;
}
// For pairing/allowlist modes, only allow reactions from approved senders
if (dmPolicy !== "open") {
const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
const storeAllowFrom = normalizeAllowList(
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []),
);
const effectiveAllowFrom = Array.from(new Set([...configAllowFrom, ...storeAllowFrom]));
const allowed = isSenderAllowed({
const dmPolicy = account.config.dmPolicy ?? "pairing";
const storeAllowFrom = normalizeAllowList(
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []),
);
const reactionAccess = resolveDmGroupAccessWithLists({
isGroup: kind !== "direct",
dmPolicy,
groupPolicy,
allowFrom: account.config.allowFrom,
groupAllowFrom: account.config.groupAllowFrom,
storeAllowFrom,
isSenderAllowed: (allowFrom) =>
isSenderAllowed({
senderId: userId,
senderName,
allowFrom: effectiveAllowFrom,
allowFrom: normalizeAllowList(allowFrom),
allowNameMatching,
});
if (!allowed) {
logVerboseMessage(
`mattermost: drop reaction (dmPolicy=${dmPolicy} sender=${userId} not allowed)`,
);
return;
}
}
} else if (kind) {
if (groupPolicy === "disabled") {
logVerboseMessage(`mattermost: drop reaction (groupPolicy=disabled channel=${channelId})`);
return;
}
if (groupPolicy === "allowlist") {
const dmPolicyForStore = account.config.dmPolicy ?? "pairing";
const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
const configGroupAllowFrom = normalizeAllowList(account.config.groupAllowFrom ?? []);
const storeAllowFrom = normalizeAllowList(
dmPolicyForStore === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []),
}),
});
if (reactionAccess.decision !== "allow") {
if (kind === "direct") {
logVerboseMessage(
`mattermost: drop reaction (dmPolicy=${dmPolicy} sender=${userId} reason=${reactionAccess.reason})`,
);
const effectiveGroupAllowFrom = Array.from(
new Set([
...(configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom),
...storeAllowFrom,
]),
} else {
logVerboseMessage(
`mattermost: drop reaction (groupPolicy=${groupPolicy} sender=${userId} reason=${reactionAccess.reason} channel=${channelId})`,
);
// Drop when allowlist is empty (same as normal message handler)
const allowed =
effectiveGroupAllowFrom.length > 0 &&
isSenderAllowed({
senderId: userId,
senderName,
allowFrom: effectiveGroupAllowFrom,
allowNameMatching,
});
if (!allowed) {
logVerboseMessage(`mattermost: drop reaction (groupPolicy=allowlist sender=${userId})`);
return;
}
}
return;
}
const teamId = channelInfo?.team_id ?? undefined;