fix: harden pairing flow

This commit is contained in:
Peter Steinberger
2026-01-07 05:06:04 +01:00
parent 6ffece68b0
commit 42ae2341aa
22 changed files with 679 additions and 265 deletions

View File

@@ -399,4 +399,43 @@ describe("monitorSlackProvider tool results", () => {
"Pairing code: PAIRCODE",
);
});
it("does not resend pairing code when a request is already pending", async () => {
config = {
...config,
slack: { dm: { enabled: true, policy: "pairing", allowFrom: [] } },
};
upsertPairingRequestMock
.mockResolvedValueOnce({ code: "PAIRCODE", created: true })
.mockResolvedValueOnce({ code: "PAIRCODE", created: false });
const controller = new AbortController();
const run = monitorSlackProvider({
botToken: "bot-token",
appToken: "app-token",
abortSignal: controller.signal,
});
await waitForEvent("message");
const handler = getSlackHandlers()?.get("message");
if (!handler) throw new Error("Slack message handler not registered");
const baseEvent = {
type: "message",
user: "U1",
text: "hello",
ts: "123",
channel: "C1",
channel_type: "im",
};
await handler({ event: baseEvent });
await handler({ event: { ...baseEvent, ts: "124", text: "hello again" } });
await flush();
controller.abort();
await run;
expect(sendMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -653,31 +653,33 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
if (dmPolicy === "pairing") {
const sender = await resolveUserName(message.user);
const senderName = sender?.name ?? undefined;
const { code } = await upsertProviderPairingRequest({
const { code, created } = await upsertProviderPairingRequest({
provider: "slack",
id: message.user,
meta: { name: senderName },
});
logVerbose(
`slack pairing request sender=${message.user} name=${senderName ?? "unknown"} code=${code}`,
);
try {
await sendMessageSlack(
message.channel,
[
"Clawdbot: access not configured.",
"",
`Pairing code: ${code}`,
"",
"Ask the bot owner to approve with:",
"clawdbot pairing approve --provider slack <code>",
].join("\n"),
{ token: botToken, client: app.client },
);
} catch (err) {
if (created) {
logVerbose(
`slack pairing reply failed for ${message.user}: ${String(err)}`,
`slack pairing request sender=${message.user} name=${senderName ?? "unknown"}`,
);
try {
await sendMessageSlack(
message.channel,
[
"Clawdbot: access not configured.",
"",
`Pairing code: ${code}`,
"",
"Ask the bot owner to approve with:",
"clawdbot pairing approve --provider slack <code>",
].join("\n"),
{ token: botToken, client: app.client },
);
} catch (err) {
logVerbose(
`slack pairing reply failed for ${message.user}: ${String(err)}`,
);
}
}
} else {
logVerbose(
@@ -1468,22 +1470,24 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
});
if (!permitted) {
if (dmPolicy === "pairing") {
const { code } = await upsertProviderPairingRequest({
const { code, created } = await upsertProviderPairingRequest({
provider: "slack",
id: command.user_id,
meta: { name: senderName },
});
await respond({
text: [
"Clawdbot: access not configured.",
"",
`Pairing code: ${code}`,
"",
"Ask the bot owner to approve with:",
"clawdbot pairing approve --provider slack <code>",
].join("\n"),
response_type: "ephemeral",
});
if (created) {
await respond({
text: [
"Clawdbot: access not configured.",
"",
`Pairing code: ${code}`,
"",
"Ask the bot owner to approve with:",
"clawdbot pairing approve --provider slack <code>",
].join("\n"),
response_type: "ephemeral",
});
}
} else {
await respond({
text: "You are not authorized to use this command.",