fix(slack): fast-path wildcard open DM policy

This commit is contained in:
Peter Steinberger
2026-05-27 00:49:10 +01:00
parent d122839eb7
commit 9cd1d27a89
3 changed files with 104 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { describe, expect, it, vi } from "vitest";
import type { SlackMonitorContext } from "./context.js";
import { authorizeSlackDirectMessage } from "./dm-auth.js";
function makeCtx(dmPolicy: SlackMonitorContext["dmPolicy"]): SlackMonitorContext {
return {
allowNameMatching: false,
dmEnabled: true,
dmPolicy,
} as SlackMonitorContext;
}
function makeParams(
dmPolicy: SlackMonitorContext["dmPolicy"],
): Parameters<typeof authorizeSlackDirectMessage>[0] {
return {
ctx: makeCtx(dmPolicy),
accountId: "workspace",
senderId: "U123",
allowFromLower: [],
resolveSenderName: vi.fn(async () => ({ name: "Alice" })),
sendPairingReply: vi.fn(),
onDisabled: vi.fn(),
onUnauthorized: vi.fn(),
log: vi.fn(),
};
}
describe("authorizeSlackDirectMessage", () => {
it("allows open DM policy when effective allowFrom includes wildcard", async () => {
const params = makeParams("open");
params.allowFromLower = ["*"];
params.resolveSenderName = vi.fn(async () => {
throw new Error("users.info failed");
});
await expect(authorizeSlackDirectMessage(params)).resolves.toBe(true);
expect(params.onUnauthorized).not.toHaveBeenCalled();
expect(params.resolveSenderName).not.toHaveBeenCalled();
});
it("rejects open DM policy when effective allowFrom lacks wildcard", async () => {
const params = makeParams("open");
await expect(authorizeSlackDirectMessage(params)).resolves.toBe(false);
expect(params.onUnauthorized).toHaveBeenCalledWith({
allowMatchMeta: "matchKey=none matchSource=none",
senderName: "Alice",
});
});
it("keeps allowlist DM policy gated by allowFrom", async () => {
const params = makeParams("allowlist");
await expect(authorizeSlackDirectMessage(params)).resolves.toBe(false);
expect(params.onUnauthorized).toHaveBeenCalledWith({
allowMatchMeta: "matchKey=none matchSource=none",
senderName: "Alice",
});
});
});

View File

@@ -21,6 +21,10 @@ export async function authorizeSlackDirectMessage(params: {
return false;
}
if (params.ctx.dmPolicy === "open" && params.allowFromLower.includes("*")) {
return true;
}
const sender = await params.resolveSenderName(params.senderId);
const senderName = sender?.name ?? undefined;
const allowMatch = resolveSlackAllowListMatch({

View File

@@ -167,6 +167,42 @@ describe("slack prepareSlackMessage inbound contract", () => {
});
});
it("prepares wildcard open-policy account DMs", async () => {
const ctx = createInboundSlackCtx({
cfg: {
channels: {
slack: {
enabled: true,
accounts: {
soltea: {
dmPolicy: "open",
dm: { enabled: true, policy: "open" },
},
},
},
},
} as OpenClawConfig,
});
ctx.accountId = "soltea";
ctx.allowFrom = ["*"];
ctx.dmPolicy = "open";
ctx.resolveUserName = async () => ({ name: "External User" }) as any;
const prepared = await prepareSlackMessage({
ctx,
account: createSlackAccount({
dmPolicy: "open",
dm: { enabled: true, policy: "open" },
}),
message: createSlackMessage({ channel: "D999", user: "U123", text: "hello" }),
opts: { source: "message" },
});
assertPrepared(prepared, "open-policy Slack DM");
expect(prepared.ctxPayload.RawBody).toContain("hello");
expect(prepared.ctxPayload.From).toBe("slack:U123");
});
it("keeps Slack assistant DM threads in a thread-scoped session with assistant context", async () => {
const ctx = createDefaultSlackCtx();
ctx.saveSlackAssistantThreadContext({