Matrix: extract monitor access state

This commit is contained in:
Gustavo Madeira Santana
2026-03-09 04:06:05 -04:00
parent baa39faa5b
commit 327dc42070
4 changed files with 162 additions and 57 deletions

View File

@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import { resolveMatrixMonitorAccessState } from "./access-state.js";
describe("resolveMatrixMonitorAccessState", () => {
it("normalizes effective allowlists once and exposes reusable matches", () => {
const state = resolveMatrixMonitorAccessState({
allowFrom: ["matrix:@Alice:Example.org"],
storeAllowFrom: ["user:@bob:example.org"],
groupAllowFrom: ["@Carol:Example.org"],
roomUsers: ["user:@Dana:Example.org"],
senderId: "@dana:example.org",
isRoom: true,
});
expect(state.effectiveAllowFrom).toEqual([
"matrix:@alice:example.org",
"user:@bob:example.org",
]);
expect(state.effectiveGroupAllowFrom).toEqual(["@carol:example.org"]);
expect(state.effectiveRoomUsers).toEqual(["user:@dana:example.org"]);
expect(state.directAllowMatch.allowed).toBe(false);
expect(state.roomUserMatch?.allowed).toBe(true);
expect(state.groupAllowMatch?.allowed).toBe(false);
expect(state.commandAuthorizers).toEqual([
{ configured: true, allowed: false },
{ configured: true, allowed: true },
{ configured: true, allowed: false },
]);
});
it("keeps room-user matching disabled for dm traffic", () => {
const state = resolveMatrixMonitorAccessState({
allowFrom: [],
storeAllowFrom: [],
groupAllowFrom: ["@carol:example.org"],
roomUsers: ["@dana:example.org"],
senderId: "@dana:example.org",
isRoom: false,
});
expect(state.roomUserMatch).toBeNull();
expect(state.commandAuthorizers[1]).toEqual({ configured: true, allowed: false });
expect(state.commandAuthorizers[2]).toEqual({ configured: true, allowed: false });
});
});

View File

@@ -0,0 +1,77 @@
import { normalizeMatrixAllowList, resolveMatrixAllowListMatch } from "./allowlist.js";
import type { MatrixAllowListMatch } from "./allowlist.js";
type MatrixCommandAuthorizer = {
configured: boolean;
allowed: boolean;
};
export type MatrixMonitorAccessState = {
effectiveAllowFrom: string[];
effectiveGroupAllowFrom: string[];
effectiveRoomUsers: string[];
groupAllowConfigured: boolean;
directAllowMatch: MatrixAllowListMatch;
roomUserMatch: MatrixAllowListMatch | null;
groupAllowMatch: MatrixAllowListMatch | null;
commandAuthorizers: [MatrixCommandAuthorizer, MatrixCommandAuthorizer, MatrixCommandAuthorizer];
};
export function resolveMatrixMonitorAccessState(params: {
allowFrom: Array<string | number>;
storeAllowFrom: Array<string | number>;
groupAllowFrom: Array<string | number>;
roomUsers: Array<string | number>;
senderId: string;
isRoom: boolean;
}): MatrixMonitorAccessState {
const effectiveAllowFrom = normalizeMatrixAllowList([
...params.allowFrom,
...params.storeAllowFrom,
]);
const effectiveGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom);
const effectiveRoomUsers = normalizeMatrixAllowList(params.roomUsers);
const directAllowMatch = resolveMatrixAllowListMatch({
allowList: effectiveAllowFrom,
userId: params.senderId,
});
const roomUserMatch =
params.isRoom && effectiveRoomUsers.length > 0
? resolveMatrixAllowListMatch({
allowList: effectiveRoomUsers,
userId: params.senderId,
})
: null;
const groupAllowMatch =
effectiveGroupAllowFrom.length > 0
? resolveMatrixAllowListMatch({
allowList: effectiveGroupAllowFrom,
userId: params.senderId,
})
: null;
return {
effectiveAllowFrom,
effectiveGroupAllowFrom,
effectiveRoomUsers,
groupAllowConfigured: effectiveGroupAllowFrom.length > 0,
directAllowMatch,
roomUserMatch,
groupAllowMatch,
commandAuthorizers: [
{
configured: effectiveAllowFrom.length > 0,
allowed: directAllowMatch.allowed,
},
{
configured: effectiveRoomUsers.length > 0,
allowed: roomUserMatch?.allowed ?? false,
},
{
configured: effectiveGroupAllowFrom.length > 0,
allowed: groupAllowMatch?.allowed ?? false,
},
],
};
}

View File

@@ -24,12 +24,8 @@ import {
sendReadReceiptMatrix,
sendTypingMatrix,
} from "../send.js";
import { resolveMatrixMonitorAccessState } from "./access-state.js";
import { resolveMatrixAckReactionConfig } from "./ack-config.js";
import {
normalizeMatrixAllowList,
resolveMatrixAllowListMatch,
resolveMatrixAllowListMatches,
} from "./allowlist.js";
import { resolveMatrixLocation, type MatrixLocationPayload } from "./location.js";
import { downloadMatrixMedia } from "./media.js";
import { resolveMentions } from "./mentions.js";
@@ -55,6 +51,7 @@ export type MatrixMonitorHandlerParams = {
logger: RuntimeLogger;
logVerboseMessage: (message: string) => void;
allowFrom: string[];
groupAllowFrom?: string[];
roomsConfig?: Record<string, MatrixRoomConfig>;
mentionRegexes: ReturnType<PluginRuntime["channel"]["mentions"]["buildMentionRegexes"]>;
groupPolicy: "open" | "allowlist" | "disabled";
@@ -89,6 +86,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
logger,
logVerboseMessage,
allowFrom,
groupAllowFrom = [],
roomsConfig,
mentionRegexes,
groupPolicy,
@@ -290,22 +288,33 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
const senderName = await getMemberDisplayName(roomId, senderId);
const storeAllowFrom = await readStoreAllowFrom();
const effectiveAllowFrom = normalizeMatrixAllowList([...allowFrom, ...storeAllowFrom]);
const groupAllowFrom = cfg.channels?.["matrix"]?.groupAllowFrom ?? [];
const effectiveGroupAllowFrom = normalizeMatrixAllowList(groupAllowFrom);
const groupAllowConfigured = effectiveGroupAllowFrom.length > 0;
const roomUsers = roomConfig?.users ?? [];
const accessState = resolveMatrixMonitorAccessState({
allowFrom,
storeAllowFrom,
groupAllowFrom,
roomUsers,
senderId,
isRoom,
});
const {
effectiveAllowFrom,
effectiveGroupAllowFrom,
effectiveRoomUsers,
groupAllowConfigured,
directAllowMatch,
roomUserMatch,
groupAllowMatch,
commandAuthorizers,
} = accessState;
if (isDirectMessage) {
if (!dmEnabled || dmPolicy === "disabled") {
return;
}
if (dmPolicy !== "open") {
const allowMatch = resolveMatrixAllowListMatch({
allowList: effectiveAllowFrom,
userId: senderId,
});
const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
if (!allowMatch.allowed) {
const allowMatchMeta = formatAllowlistMatchMeta(directAllowMatch);
if (!directAllowMatch.allowed) {
if (!isReactionEvent && dmPolicy === "pairing") {
const { code, created } = await core.channel.pairing.upsertPairingRequest({
channel: "matrix",
@@ -351,27 +360,21 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
}
}
const roomUsers = roomConfig?.users ?? [];
if (isRoom && roomUsers.length > 0) {
const userMatch = resolveMatrixAllowListMatch({
allowList: normalizeMatrixAllowList(roomUsers),
userId: senderId,
});
if (!userMatch.allowed) {
logVerboseMessage(
`matrix: blocked sender ${senderId} (room users allowlist, ${roomMatchMeta}, ${formatAllowlistMatchMeta(
userMatch,
)})`,
);
return;
}
if (isRoom && roomUserMatch && !roomUserMatch.allowed) {
logVerboseMessage(
`matrix: blocked sender ${senderId} (room users allowlist, ${roomMatchMeta}, ${formatAllowlistMatchMeta(
roomUserMatch,
)})`,
);
return;
}
if (isRoom && groupPolicy === "allowlist" && roomUsers.length === 0 && groupAllowConfigured) {
const groupAllowMatch = resolveMatrixAllowListMatch({
allowList: effectiveGroupAllowFrom,
userId: senderId,
});
if (!groupAllowMatch.allowed) {
if (
isRoom &&
groupPolicy === "allowlist" &&
effectiveRoomUsers.length === 0 &&
groupAllowConfigured
) {
if (groupAllowMatch && !groupAllowMatch.allowed) {
logVerboseMessage(
`matrix: blocked sender ${senderId} (groupAllowFrom, ${roomMatchMeta}, ${formatAllowlistMatchMeta(
groupAllowMatch,
@@ -456,31 +459,10 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
surface: "matrix",
});
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
const senderAllowedForCommands = resolveMatrixAllowListMatches({
allowList: effectiveAllowFrom,
userId: senderId,
});
const senderAllowedForGroup = groupAllowConfigured
? resolveMatrixAllowListMatches({
allowList: effectiveGroupAllowFrom,
userId: senderId,
})
: false;
const senderAllowedForRoomUsers =
isRoom && roomUsers.length > 0
? resolveMatrixAllowListMatches({
allowList: normalizeMatrixAllowList(roomUsers),
userId: senderId,
})
: false;
const hasControlCommandInMessage = core.channel.text.hasControlCommand(bodyText, cfg);
const commandGate = resolveControlCommandGate({
useAccessGroups,
authorizers: [
{ configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands },
{ configured: roomUsers.length > 0, allowed: senderAllowedForRoomUsers },
{ configured: groupAllowConfigured, allowed: senderAllowedForGroup },
],
authorizers: commandAuthorizers,
allowTextCommands,
hasControlCommand: hasControlCommandInMessage,
});

View File

@@ -316,6 +316,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
logger,
logVerboseMessage,
allowFrom,
groupAllowFrom,
roomsConfig,
mentionRegexes,
groupPolicy,