mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 16:03:43 +00:00
refactor(channels): unify dm pairing policy flows
This commit is contained in:
87
src/signal/monitor/access-policy.ts
Normal file
87
src/signal/monitor/access-policy.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { issuePairingChallenge } from "../../pairing/pairing-challenge.js";
|
||||
import { upsertChannelPairingRequest } from "../../pairing/pairing-store.js";
|
||||
import {
|
||||
readStoreAllowFromForDmPolicy,
|
||||
resolveDmGroupAccessWithLists,
|
||||
} from "../../security/dm-policy-shared.js";
|
||||
import { isSignalSenderAllowed, type SignalSender } from "../identity.js";
|
||||
|
||||
type SignalDmPolicy = "open" | "pairing" | "allowlist" | "disabled";
|
||||
type SignalGroupPolicy = "open" | "allowlist" | "disabled";
|
||||
|
||||
export async function resolveSignalAccessState(params: {
|
||||
accountId: string;
|
||||
dmPolicy: SignalDmPolicy;
|
||||
groupPolicy: SignalGroupPolicy;
|
||||
allowFrom: string[];
|
||||
groupAllowFrom: string[];
|
||||
sender: SignalSender;
|
||||
}) {
|
||||
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
|
||||
provider: "signal",
|
||||
accountId: params.accountId,
|
||||
dmPolicy: params.dmPolicy,
|
||||
});
|
||||
const resolveAccessDecision = (isGroup: boolean) =>
|
||||
resolveDmGroupAccessWithLists({
|
||||
isGroup,
|
||||
dmPolicy: params.dmPolicy,
|
||||
groupPolicy: params.groupPolicy,
|
||||
allowFrom: params.allowFrom,
|
||||
groupAllowFrom: params.groupAllowFrom,
|
||||
storeAllowFrom,
|
||||
isSenderAllowed: (allowEntries) => isSignalSenderAllowed(params.sender, allowEntries),
|
||||
});
|
||||
const dmAccess = resolveAccessDecision(false);
|
||||
return {
|
||||
resolveAccessDecision,
|
||||
dmAccess,
|
||||
effectiveDmAllow: dmAccess.effectiveAllowFrom,
|
||||
effectiveGroupAllow: dmAccess.effectiveGroupAllowFrom,
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleSignalDirectMessageAccess(params: {
|
||||
dmPolicy: SignalDmPolicy;
|
||||
dmAccessDecision: "allow" | "block" | "pairing";
|
||||
senderId: string;
|
||||
senderIdLine: string;
|
||||
senderDisplay: string;
|
||||
senderName?: string;
|
||||
accountId: string;
|
||||
sendPairingReply: (text: string) => Promise<void>;
|
||||
log: (message: string) => void;
|
||||
}): Promise<boolean> {
|
||||
if (params.dmAccessDecision === "allow") {
|
||||
return true;
|
||||
}
|
||||
if (params.dmAccessDecision === "block") {
|
||||
if (params.dmPolicy !== "disabled") {
|
||||
params.log(`Blocked signal sender ${params.senderDisplay} (dmPolicy=${params.dmPolicy})`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (params.dmPolicy === "pairing") {
|
||||
await issuePairingChallenge({
|
||||
channel: "signal",
|
||||
senderId: params.senderId,
|
||||
senderIdLine: params.senderIdLine,
|
||||
meta: { name: params.senderName },
|
||||
upsertPairingRequest: async ({ id, meta }) =>
|
||||
await upsertChannelPairingRequest({
|
||||
channel: "signal",
|
||||
id,
|
||||
accountId: params.accountId,
|
||||
meta,
|
||||
}),
|
||||
sendPairingReply: params.sendPairingReply,
|
||||
onCreated: () => {
|
||||
params.log(`signal pairing request sender=${params.senderId}`);
|
||||
},
|
||||
onReplyError: (err) => {
|
||||
params.log(`signal pairing reply failed for ${params.senderId}: ${String(err)}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -30,14 +30,8 @@ import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js
|
||||
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import { mediaKindFromMime } from "../../media/constants.js";
|
||||
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||
import { upsertChannelPairingRequest } from "../../pairing/pairing-store.js";
|
||||
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||
import {
|
||||
DM_GROUP_ACCESS_REASON,
|
||||
readStoreAllowFromForDmPolicy,
|
||||
resolveDmGroupAccessWithLists,
|
||||
} from "../../security/dm-policy-shared.js";
|
||||
import { DM_GROUP_ACCESS_REASON } from "../../security/dm-policy-shared.js";
|
||||
import { normalizeE164 } from "../../utils.js";
|
||||
import {
|
||||
formatSignalPairingIdLine,
|
||||
@@ -50,6 +44,7 @@ import {
|
||||
type SignalSender,
|
||||
} from "../identity.js";
|
||||
import { sendMessageSignal, sendReadReceiptSignal, sendTypingSignal } from "../send.js";
|
||||
import { handleSignalDirectMessageAccess, resolveSignalAccessState } from "./access-policy.js";
|
||||
import type {
|
||||
SignalEnvelope,
|
||||
SignalEventHandlerDeps,
|
||||
@@ -454,24 +449,15 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
const hasBodyContent =
|
||||
Boolean(messageText || quoteText) || Boolean(!reaction && dataMessage?.attachments?.length);
|
||||
const senderDisplay = formatSignalSenderDisplay(sender);
|
||||
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
|
||||
provider: "signal",
|
||||
accountId: deps.accountId,
|
||||
dmPolicy: deps.dmPolicy,
|
||||
});
|
||||
const resolveAccessDecision = (isGroup: boolean) =>
|
||||
resolveDmGroupAccessWithLists({
|
||||
isGroup,
|
||||
const { resolveAccessDecision, dmAccess, effectiveDmAllow, effectiveGroupAllow } =
|
||||
await resolveSignalAccessState({
|
||||
accountId: deps.accountId,
|
||||
dmPolicy: deps.dmPolicy,
|
||||
groupPolicy: deps.groupPolicy,
|
||||
allowFrom: deps.allowFrom,
|
||||
groupAllowFrom: deps.groupAllowFrom,
|
||||
storeAllowFrom,
|
||||
isSenderAllowed: (allowEntries) => isSignalSenderAllowed(sender, allowEntries),
|
||||
sender,
|
||||
});
|
||||
const dmAccess = resolveAccessDecision(false);
|
||||
const effectiveDmAllow = dmAccess.effectiveAllowFrom;
|
||||
const effectiveGroupAllow = dmAccess.effectiveGroupAllowFrom;
|
||||
|
||||
if (
|
||||
reaction &&
|
||||
@@ -502,43 +488,25 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
const isGroup = Boolean(groupId);
|
||||
|
||||
if (!isGroup) {
|
||||
if (dmAccess.decision === "block") {
|
||||
if (deps.dmPolicy !== "disabled") {
|
||||
logVerbose(`Blocked signal sender ${senderDisplay} (dmPolicy=${deps.dmPolicy})`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (dmAccess.decision === "pairing") {
|
||||
if (deps.dmPolicy === "pairing") {
|
||||
const senderId = senderAllowId;
|
||||
const { code, created } = await upsertChannelPairingRequest({
|
||||
channel: "signal",
|
||||
id: senderId,
|
||||
const allowedDirectMessage = await handleSignalDirectMessageAccess({
|
||||
dmPolicy: deps.dmPolicy,
|
||||
dmAccessDecision: dmAccess.decision,
|
||||
senderId: senderAllowId,
|
||||
senderIdLine,
|
||||
senderDisplay,
|
||||
senderName: envelope.sourceName ?? undefined,
|
||||
accountId: deps.accountId,
|
||||
sendPairingReply: async (text) => {
|
||||
await sendMessageSignal(`signal:${senderRecipient}`, text, {
|
||||
baseUrl: deps.baseUrl,
|
||||
account: deps.account,
|
||||
maxBytes: deps.mediaMaxBytes,
|
||||
accountId: deps.accountId,
|
||||
meta: { name: envelope.sourceName ?? undefined },
|
||||
});
|
||||
if (created) {
|
||||
logVerbose(`signal pairing request sender=${senderId}`);
|
||||
try {
|
||||
await sendMessageSignal(
|
||||
`signal:${senderRecipient}`,
|
||||
buildPairingReply({
|
||||
channel: "signal",
|
||||
idLine: senderIdLine,
|
||||
code,
|
||||
}),
|
||||
{
|
||||
baseUrl: deps.baseUrl,
|
||||
account: deps.account,
|
||||
maxBytes: deps.mediaMaxBytes,
|
||||
accountId: deps.accountId,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
logVerbose(`signal pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
log: logVerbose,
|
||||
});
|
||||
if (!allowedDirectMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user