refactor(channels): unify dm pairing policy flows

This commit is contained in:
Peter Steinberger
2026-02-26 22:36:05 +01:00
parent 7e0b3f16e3
commit 564be6b402
9 changed files with 443 additions and 252 deletions

View File

@@ -0,0 +1,127 @@
import {
formatAllowlistMatchMeta,
issuePairingChallenge,
readStoreAllowFromForDmPolicy,
resolveDmGroupAccessWithLists,
} from "openclaw/plugin-sdk";
import {
normalizeMatrixAllowList,
resolveMatrixAllowListMatch,
resolveMatrixAllowListMatches,
} from "./allowlist.js";
type MatrixDmPolicy = "open" | "pairing" | "allowlist" | "disabled";
type MatrixGroupPolicy = "open" | "allowlist" | "disabled";
export async function resolveMatrixAccessState(params: {
isDirectMessage: boolean;
resolvedAccountId: string;
dmPolicy: MatrixDmPolicy;
groupPolicy: MatrixGroupPolicy;
allowFrom: string[];
groupAllowFrom: Array<string | number>;
senderId: string;
readStoreForDmPolicy: (provider: string, accountId: string) => Promise<string[]>;
}) {
const storeAllowFrom = params.isDirectMessage
? await readStoreAllowFromForDmPolicy({
provider: "matrix",
accountId: params.resolvedAccountId,
dmPolicy: params.dmPolicy,
readStore: params.readStoreForDmPolicy,
})
: [];
const normalizedGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom);
const senderGroupPolicy =
params.groupPolicy === "disabled"
? "disabled"
: normalizedGroupAllowFrom.length > 0
? "allowlist"
: "open";
const access = resolveDmGroupAccessWithLists({
isGroup: !params.isDirectMessage,
dmPolicy: params.dmPolicy,
groupPolicy: senderGroupPolicy,
allowFrom: params.allowFrom,
groupAllowFrom: normalizedGroupAllowFrom,
storeAllowFrom,
groupAllowFromFallbackToAllowFrom: false,
isSenderAllowed: (allowFrom) =>
resolveMatrixAllowListMatches({
allowList: normalizeMatrixAllowList(allowFrom),
userId: params.senderId,
}),
});
const effectiveAllowFrom = normalizeMatrixAllowList(access.effectiveAllowFrom);
const effectiveGroupAllowFrom = normalizeMatrixAllowList(access.effectiveGroupAllowFrom);
return {
access,
effectiveAllowFrom,
effectiveGroupAllowFrom,
groupAllowConfigured: effectiveGroupAllowFrom.length > 0,
};
}
export async function enforceMatrixDirectMessageAccess(params: {
dmEnabled: boolean;
dmPolicy: MatrixDmPolicy;
accessDecision: "allow" | "block" | "pairing";
senderId: string;
senderName: string;
effectiveAllowFrom: string[];
upsertPairingRequest: (input: {
id: string;
meta?: Record<string, string | undefined>;
}) => Promise<{
code: string;
created: boolean;
}>;
sendPairingReply: (text: string) => Promise<void>;
logVerboseMessage: (message: string) => void;
}): Promise<boolean> {
if (!params.dmEnabled) {
return false;
}
if (params.accessDecision === "allow") {
return true;
}
const allowMatch = resolveMatrixAllowListMatch({
allowList: params.effectiveAllowFrom,
userId: params.senderId,
});
const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
if (params.accessDecision === "pairing") {
await issuePairingChallenge({
channel: "matrix",
senderId: params.senderId,
senderIdLine: `Matrix user id: ${params.senderId}`,
meta: { name: params.senderName },
upsertPairingRequest: params.upsertPairingRequest,
buildReplyText: ({ code }) =>
[
"OpenClaw: access not configured.",
"",
`Pairing code: ${code}`,
"",
"Ask the bot owner to approve with:",
"openclaw pairing approve matrix <code>",
].join("\n"),
sendPairingReply: params.sendPairingReply,
onCreated: () => {
params.logVerboseMessage(
`matrix pairing request sender=${params.senderId} name=${params.senderName ?? "unknown"} (${allowMatchMeta})`,
);
},
onReplyError: (err) => {
params.logVerboseMessage(
`matrix pairing reply failed for ${params.senderId}: ${String(err)}`,
);
},
});
return false;
}
params.logVerboseMessage(
`matrix: blocked dm sender ${params.senderId} (dmPolicy=${params.dmPolicy}, ${allowMatchMeta})`,
);
return false;
}

View File

@@ -7,9 +7,7 @@ import {
formatAllowlistMatchMeta,
logInboundDrop,
logTypingFailure,
readStoreAllowFromForDmPolicy,
resolveControlCommandGate,
resolveDmGroupAccessWithLists,
type PluginRuntime,
type RuntimeEnv,
type RuntimeLogger,
@@ -23,6 +21,7 @@ import {
type PollStartContent,
} from "../poll-types.js";
import { reactMatrixMessage, sendMessageMatrix, sendTypingMatrix } from "../send.js";
import { enforceMatrixDirectMessageAccess, resolveMatrixAccessState } from "./access-policy.js";
import {
normalizeMatrixAllowList,
resolveMatrixAllowListMatch,
@@ -234,81 +233,34 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
senderId,
senderUsername,
});
const storeAllowFrom = isDirectMessage
? await readStoreAllowFromForDmPolicy({
provider: "matrix",
accountId: resolvedAccountId,
dmPolicy,
readStore: pairing.readStoreForDmPolicy,
})
: [];
const groupAllowFrom = cfg.channels?.matrix?.groupAllowFrom ?? [];
const normalizedGroupAllowFrom = normalizeMatrixAllowList(groupAllowFrom);
const senderGroupPolicy =
groupPolicy === "disabled"
? "disabled"
: normalizedGroupAllowFrom.length > 0
? "allowlist"
: "open";
const access = resolveDmGroupAccessWithLists({
isGroup: isRoom,
dmPolicy,
groupPolicy: senderGroupPolicy,
allowFrom,
groupAllowFrom: normalizedGroupAllowFrom,
storeAllowFrom,
groupAllowFromFallbackToAllowFrom: false,
isSenderAllowed: (allowFrom) =>
resolveMatrixAllowListMatches({
allowList: normalizeMatrixAllowList(allowFrom),
userId: senderId,
}),
});
const effectiveAllowFrom = normalizeMatrixAllowList(access.effectiveAllowFrom);
const effectiveGroupAllowFrom = normalizeMatrixAllowList(access.effectiveGroupAllowFrom);
const groupAllowConfigured = effectiveGroupAllowFrom.length > 0;
const { access, effectiveAllowFrom, effectiveGroupAllowFrom, groupAllowConfigured } =
await resolveMatrixAccessState({
isDirectMessage,
resolvedAccountId,
dmPolicy,
groupPolicy,
allowFrom,
groupAllowFrom,
senderId,
readStoreForDmPolicy: pairing.readStoreForDmPolicy,
});
if (isDirectMessage) {
if (!dmEnabled) {
return;
}
if (access.decision !== "allow") {
const allowMatch = resolveMatrixAllowListMatch({
allowList: effectiveAllowFrom,
userId: senderId,
});
const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
if (access.decision === "pairing") {
const { code, created } = await pairing.upsertPairingRequest({
id: senderId,
meta: { name: senderName },
});
if (created) {
logVerboseMessage(
`matrix pairing request sender=${senderId} name=${senderName ?? "unknown"} (${allowMatchMeta})`,
);
try {
await sendMessageMatrix(
`room:${roomId}`,
[
"OpenClaw: access not configured.",
"",
`Pairing code: ${code}`,
"",
"Ask the bot owner to approve with:",
"openclaw pairing approve matrix <code>",
].join("\n"),
{ client },
);
} catch (err) {
logVerboseMessage(`matrix pairing reply failed for ${senderId}: ${String(err)}`);
}
}
} else {
logVerboseMessage(
`matrix: blocked dm sender ${senderId} (dmPolicy=${dmPolicy}, ${allowMatchMeta})`,
);
}
const allowedDirectMessage = await enforceMatrixDirectMessageAccess({
dmEnabled,
dmPolicy,
accessDecision: access.decision,
senderId,
senderName,
effectiveAllowFrom,
upsertPairingRequest: pairing.upsertPairingRequest,
sendPairingReply: async (text) => {
await sendMessageMatrix(`room:${roomId}`, text, { client });
},
logVerboseMessage,
});
if (!allowedDirectMessage) {
return;
}
}