refactor: share allowlist match metadata
Co-authored-by: thewilloftheshadow <thewilloftheshadow@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AllowlistMatch } from "../../../../../src/channels/plugins/allowlist-match.js";
|
||||||
|
|
||||||
function normalizeAllowList(list?: Array<string | number>) {
|
function normalizeAllowList(list?: Array<string | number>) {
|
||||||
return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean);
|
return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean);
|
||||||
}
|
}
|
||||||
@@ -10,11 +12,9 @@ function normalizeMatrixUser(raw?: string | null): string {
|
|||||||
return (raw ?? "").trim().toLowerCase();
|
return (raw ?? "").trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MatrixAllowListMatch = {
|
export type MatrixAllowListMatch = AllowlistMatch<
|
||||||
allowed: boolean;
|
"wildcard" | "id" | "prefixed-id" | "prefixed-user" | "name" | "localpart"
|
||||||
matchKey?: string;
|
>;
|
||||||
matchSource?: "wildcard" | "id" | "prefixed-id" | "prefixed-user" | "name" | "localpart";
|
|
||||||
};
|
|
||||||
|
|
||||||
export function resolveMatrixAllowListMatch(params: {
|
export function resolveMatrixAllowListMatch(params: {
|
||||||
allowList: string[];
|
allowList: string[];
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { createReplyDispatcherWithTyping } from "../../../../../src/auto-reply/reply/reply-dispatcher.js";
|
import { createReplyDispatcherWithTyping } from "../../../../../src/auto-reply/reply/reply-dispatcher.js";
|
||||||
import type { ReplyPayload } from "../../../../../src/auto-reply/types.js";
|
import type { ReplyPayload } from "../../../../../src/auto-reply/types.js";
|
||||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../../../../src/channels/command-gating.js";
|
import { resolveCommandAuthorizedFromAuthorizers } from "../../../../../src/channels/command-gating.js";
|
||||||
|
import { formatAllowlistMatchMeta } from "../../../../../src/channels/plugins/allowlist-match.js";
|
||||||
import { loadConfig } from "../../../../../src/config/config.js";
|
import { loadConfig } from "../../../../../src/config/config.js";
|
||||||
import { resolveStorePath, updateLastRoute } from "../../../../../src/config/sessions.js";
|
import { resolveStorePath, updateLastRoute } from "../../../../../src/config/sessions.js";
|
||||||
import { danger, logVerbose, shouldLogVerbose } from "../../../../../src/globals.js";
|
import { danger, logVerbose, shouldLogVerbose } from "../../../../../src/globals.js";
|
||||||
@@ -326,9 +327,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
|||||||
userId: senderId,
|
userId: senderId,
|
||||||
userName: senderName,
|
userName: senderName,
|
||||||
});
|
});
|
||||||
const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${
|
const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
|
||||||
allowMatch.matchSource ?? "none"
|
|
||||||
}`;
|
|
||||||
if (!allowMatch.allowed) {
|
if (!allowMatch.allowed) {
|
||||||
if (dmPolicy === "pairing") {
|
if (dmPolicy === "pairing") {
|
||||||
const { code, created } = await upsertChannelPairingRequest({
|
const { code, created } = await upsertChannelPairingRequest({
|
||||||
@@ -369,14 +368,16 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isRoom && roomConfigInfo.config?.users?.length) {
|
if (isRoom && roomConfigInfo.config?.users?.length) {
|
||||||
const userAllowed = resolveMatrixAllowListMatches({
|
const userMatch = resolveMatrixAllowListMatch({
|
||||||
allowList: normalizeAllowListLower(roomConfigInfo.config.users),
|
allowList: normalizeAllowListLower(roomConfigInfo.config.users),
|
||||||
userId: senderId,
|
userId: senderId,
|
||||||
userName: senderName,
|
userName: senderName,
|
||||||
});
|
});
|
||||||
if (!userAllowed) {
|
if (!userMatch.allowed) {
|
||||||
logVerbose(
|
logVerbose(
|
||||||
`matrix: blocked sender ${senderId} (room users allowlist, ${roomMatchMeta})`,
|
`matrix: blocked sender ${senderId} (room users allowlist, ${roomMatchMeta}, ${formatAllowlistMatchMeta(
|
||||||
|
userMatch,
|
||||||
|
)})`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "../../../../src/auto-reply/reply/history.js";
|
} from "../../../../src/auto-reply/reply/history.js";
|
||||||
import { resolveMentionGating } from "../../../../src/channels/mention-gating.js";
|
import { resolveMentionGating } from "../../../../src/channels/mention-gating.js";
|
||||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../../../src/channels/command-gating.js";
|
import { resolveCommandAuthorizedFromAuthorizers } from "../../../../src/channels/command-gating.js";
|
||||||
|
import { formatAllowlistMatchMeta } from "../../../../src/channels/plugins/allowlist-match.js";
|
||||||
import { danger, logVerbose, shouldLogVerbose } from "../../../../src/globals.js";
|
import { danger, logVerbose, shouldLogVerbose } from "../../../../src/globals.js";
|
||||||
import { enqueueSystemEvent } from "../../../../src/infra/system-events.js";
|
import { enqueueSystemEvent } from "../../../../src/infra/system-events.js";
|
||||||
import {
|
import {
|
||||||
@@ -41,6 +42,7 @@ import {
|
|||||||
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
|
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
|
||||||
import {
|
import {
|
||||||
isMSTeamsGroupAllowed,
|
isMSTeamsGroupAllowed,
|
||||||
|
resolveMSTeamsAllowlistMatch,
|
||||||
resolveMSTeamsReplyPolicy,
|
resolveMSTeamsReplyPolicy,
|
||||||
resolveMSTeamsRouteConfig,
|
resolveMSTeamsRouteConfig,
|
||||||
} from "../policy.js";
|
} from "../policy.js";
|
||||||
@@ -141,19 +143,14 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dmPolicy !== "open") {
|
if (dmPolicy !== "open") {
|
||||||
const effectiveAllowFrom = [
|
const effectiveAllowFrom = [...allowFrom.map((v) => String(v)), ...storedAllowFrom];
|
||||||
...allowFrom.map((v) => String(v).toLowerCase()),
|
const allowMatch = resolveMSTeamsAllowlistMatch({
|
||||||
...storedAllowFrom,
|
allowFrom: effectiveAllowFrom,
|
||||||
];
|
senderId,
|
||||||
|
senderName,
|
||||||
|
});
|
||||||
|
|
||||||
const senderLower = senderId.toLowerCase();
|
if (!allowMatch.allowed) {
|
||||||
const senderNameLower = senderName.toLowerCase();
|
|
||||||
const allowed =
|
|
||||||
effectiveAllowFrom.includes("*") ||
|
|
||||||
effectiveAllowFrom.includes(senderLower) ||
|
|
||||||
effectiveAllowFrom.includes(senderNameLower);
|
|
||||||
|
|
||||||
if (!allowed) {
|
|
||||||
if (dmPolicy === "pairing") {
|
if (dmPolicy === "pairing") {
|
||||||
const request = await upsertChannelPairingRequest({
|
const request = await upsertChannelPairingRequest({
|
||||||
channel: "msteams",
|
channel: "msteams",
|
||||||
@@ -170,6 +167,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
log.debug("dropping dm (not allowlisted)", {
|
log.debug("dropping dm (not allowlisted)", {
|
||||||
sender: senderId,
|
sender: senderId,
|
||||||
label: senderName,
|
label: senderName,
|
||||||
|
allowlistMatch: formatAllowlistMatchMeta(allowMatch),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -213,6 +211,10 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
||||||
log.debug("dropping group message (not in team/channel allowlist)", {
|
log.debug("dropping group message (not in team/channel allowlist)", {
|
||||||
conversationId,
|
conversationId,
|
||||||
|
teamKey: channelGate.teamKey ?? "none",
|
||||||
|
channelKey: channelGate.channelKey ?? "none",
|
||||||
|
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
||||||
|
channelMatchSource: channelGate.channelMatchSource ?? "none",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -223,16 +225,17 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (effectiveGroupAllowFrom.length > 0) {
|
if (effectiveGroupAllowFrom.length > 0) {
|
||||||
const allowed = isMSTeamsGroupAllowed({
|
const allowMatch = resolveMSTeamsAllowlistMatch({
|
||||||
groupPolicy,
|
groupPolicy,
|
||||||
allowFrom: effectiveGroupAllowFrom,
|
allowFrom: effectiveGroupAllowFrom,
|
||||||
senderId,
|
senderId,
|
||||||
senderName,
|
senderName,
|
||||||
});
|
});
|
||||||
if (!allowed) {
|
if (!allowMatch.allowed) {
|
||||||
log.debug("dropping group message (not in groupAllowFrom)", {
|
log.debug("dropping group message (not in groupAllowFrom)", {
|
||||||
sender: senderId,
|
sender: senderId,
|
||||||
label: senderName,
|
label: senderName,
|
||||||
|
allowlistMatch: formatAllowlistMatchMeta(allowMatch),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
resolveChannelEntryMatchWithFallback,
|
resolveChannelEntryMatchWithFallback,
|
||||||
resolveNestedAllowlistDecision,
|
resolveNestedAllowlistDecision,
|
||||||
} from "../../../src/channels/plugins/channel-config.js";
|
} from "../../../src/channels/plugins/channel-config.js";
|
||||||
|
import type { AllowlistMatch } from "../../../src/channels/plugins/allowlist-match.js";
|
||||||
|
|
||||||
export type MSTeamsResolvedRouteConfig = {
|
export type MSTeamsResolvedRouteConfig = {
|
||||||
teamConfig?: MSTeamsTeamConfig;
|
teamConfig?: MSTeamsTeamConfig;
|
||||||
@@ -90,6 +91,31 @@ export type MSTeamsReplyPolicy = {
|
|||||||
replyStyle: MSTeamsReplyStyle;
|
replyStyle: MSTeamsReplyStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MSTeamsAllowlistMatch = AllowlistMatch<"wildcard" | "id" | "name">;
|
||||||
|
|
||||||
|
export function resolveMSTeamsAllowlistMatch(params: {
|
||||||
|
allowFrom: Array<string | number>;
|
||||||
|
senderId: string;
|
||||||
|
senderName?: string | null;
|
||||||
|
}): MSTeamsAllowlistMatch {
|
||||||
|
const allowFrom = params.allowFrom
|
||||||
|
.map((entry) => String(entry).trim().toLowerCase())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (allowFrom.length === 0) return { allowed: false };
|
||||||
|
if (allowFrom.includes("*")) {
|
||||||
|
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
||||||
|
}
|
||||||
|
const senderId = params.senderId.toLowerCase();
|
||||||
|
if (allowFrom.includes(senderId)) {
|
||||||
|
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
||||||
|
}
|
||||||
|
const senderName = params.senderName?.toLowerCase();
|
||||||
|
if (senderName && allowFrom.includes(senderName)) {
|
||||||
|
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
||||||
|
}
|
||||||
|
return { allowed: false };
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveMSTeamsReplyPolicy(params: {
|
export function resolveMSTeamsReplyPolicy(params: {
|
||||||
isDirectMessage: boolean;
|
isDirectMessage: boolean;
|
||||||
globalConfig?: MSTeamsConfig;
|
globalConfig?: MSTeamsConfig;
|
||||||
@@ -126,12 +152,5 @@ export function isMSTeamsGroupAllowed(params: {
|
|||||||
const { groupPolicy } = params;
|
const { groupPolicy } = params;
|
||||||
if (groupPolicy === "disabled") return false;
|
if (groupPolicy === "disabled") return false;
|
||||||
if (groupPolicy === "open") return true;
|
if (groupPolicy === "open") return true;
|
||||||
const allowFrom = params.allowFrom
|
return resolveMSTeamsAllowlistMatch(params).allowed;
|
||||||
.map((entry) => String(entry).trim().toLowerCase())
|
|
||||||
.filter(Boolean);
|
|
||||||
if (allowFrom.length === 0) return false;
|
|
||||||
if (allowFrom.includes("*")) return true;
|
|
||||||
const senderId = params.senderId.toLowerCase();
|
|
||||||
const senderName = params.senderName?.toLowerCase();
|
|
||||||
return allowFrom.includes(senderId) || (senderName ? allowFrom.includes(senderName) : false);
|
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/channels/allowlist-match.ts
Normal file
21
src/channels/allowlist-match.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export type AllowlistMatchSource =
|
||||||
|
| "wildcard"
|
||||||
|
| "id"
|
||||||
|
| "name"
|
||||||
|
| "tag"
|
||||||
|
| "username"
|
||||||
|
| "prefixed-id"
|
||||||
|
| "prefixed-user"
|
||||||
|
| "prefixed-name"
|
||||||
|
| "slug"
|
||||||
|
| "localpart";
|
||||||
|
|
||||||
|
export type AllowlistMatch<TSource extends string = AllowlistMatchSource> = {
|
||||||
|
allowed: boolean;
|
||||||
|
matchKey?: string;
|
||||||
|
matchSource?: TSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatAllowlistMatchMeta(match?: AllowlistMatch | null): string {
|
||||||
|
return `matchKey=${match?.matchKey ?? "none"} matchSource=${match?.matchSource ?? "none"}`;
|
||||||
|
}
|
||||||
2
src/channels/plugins/allowlist-match.ts
Normal file
2
src/channels/plugins/allowlist-match.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export type { AllowlistMatch, AllowlistMatchSource } from "../allowlist-match.js";
|
||||||
|
export { formatAllowlistMatchMeta } from "../allowlist-match.js";
|
||||||
@@ -93,4 +93,9 @@ export {
|
|||||||
type ChannelEntryMatch,
|
type ChannelEntryMatch,
|
||||||
type ChannelMatchSource,
|
type ChannelMatchSource,
|
||||||
} from "./channel-config.js";
|
} from "./channel-config.js";
|
||||||
|
export {
|
||||||
|
formatAllowlistMatchMeta,
|
||||||
|
type AllowlistMatch,
|
||||||
|
type AllowlistMatchSource,
|
||||||
|
} from "./allowlist-match.js";
|
||||||
export type { ChannelId, ChannelPlugin } from "./types.js";
|
export type { ChannelId, ChannelPlugin } from "./types.js";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
buildChannelKeyCandidates,
|
buildChannelKeyCandidates,
|
||||||
resolveChannelEntryMatchWithFallback,
|
resolveChannelEntryMatchWithFallback,
|
||||||
} from "../../channels/channel-config.js";
|
} from "../../channels/channel-config.js";
|
||||||
|
import type { AllowlistMatch } from "../../channels/allowlist-match.js";
|
||||||
import { formatDiscordUserTag } from "./format.js";
|
import { formatDiscordUserTag } from "./format.js";
|
||||||
|
|
||||||
export type DiscordAllowList = {
|
export type DiscordAllowList = {
|
||||||
@@ -12,11 +13,7 @@ export type DiscordAllowList = {
|
|||||||
names: Set<string>;
|
names: Set<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiscordAllowListMatch = {
|
export type DiscordAllowListMatch = AllowlistMatch<"wildcard" | "id" | "name" | "tag">;
|
||||||
allowed: boolean;
|
|
||||||
matchKey?: string;
|
|
||||||
matchSource?: "wildcard" | "id" | "name" | "tag";
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DiscordGuildEntryResolved = {
|
export type DiscordGuildEntryResolved = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AllowlistMatch } from "../../channels/allowlist-match.js";
|
||||||
|
|
||||||
export function normalizeSlackSlug(raw?: string) {
|
export function normalizeSlackSlug(raw?: string) {
|
||||||
const trimmed = raw?.trim().toLowerCase() ?? "";
|
const trimmed = raw?.trim().toLowerCase() ?? "";
|
||||||
if (!trimmed) return "";
|
if (!trimmed) return "";
|
||||||
@@ -14,18 +16,9 @@ export function normalizeAllowListLower(list?: Array<string | number>) {
|
|||||||
return normalizeAllowList(list).map((entry) => entry.toLowerCase());
|
return normalizeAllowList(list).map((entry) => entry.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SlackAllowListMatch = {
|
export type SlackAllowListMatch = AllowlistMatch<
|
||||||
allowed: boolean;
|
"wildcard" | "id" | "prefixed-id" | "prefixed-user" | "name" | "prefixed-name" | "slug"
|
||||||
matchKey?: string;
|
>;
|
||||||
matchSource?:
|
|
||||||
| "wildcard"
|
|
||||||
| "id"
|
|
||||||
| "prefixed-id"
|
|
||||||
| "prefixed-user"
|
|
||||||
| "name"
|
|
||||||
| "prefixed-name"
|
|
||||||
| "slug";
|
|
||||||
};
|
|
||||||
|
|
||||||
export function resolveSlackAllowListMatch(params: {
|
export function resolveSlackAllowListMatch(params: {
|
||||||
allowList: string[];
|
allowList: string[];
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AllowlistMatch } from "../channels/allowlist-match.js";
|
||||||
|
|
||||||
export type NormalizedAllowFrom = {
|
export type NormalizedAllowFrom = {
|
||||||
entries: string[];
|
entries: string[];
|
||||||
entriesLower: string[];
|
entriesLower: string[];
|
||||||
@@ -5,11 +7,7 @@ export type NormalizedAllowFrom = {
|
|||||||
hasEntries: boolean;
|
hasEntries: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AllowFromMatch = {
|
export type AllowFromMatch = AllowlistMatch<"wildcard" | "id" | "username">;
|
||||||
allowed: boolean;
|
|
||||||
matchKey?: string;
|
|
||||||
matchSource?: "wildcard" | "id" | "username";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const normalizeAllowFrom = (list?: Array<string | number>): NormalizedAllowFrom => {
|
export const normalizeAllowFrom = (list?: Array<string | number>): NormalizedAllowFrom => {
|
||||||
const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);
|
const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);
|
||||||
|
|||||||
Reference in New Issue
Block a user