mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 04:07:39 +00:00
Discord: add per-button component allowlist
This commit is contained in:
@@ -292,6 +292,48 @@ async function ensureGuildComponentMemberAllowed(params: {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function ensureComponentUserAllowed(params: {
|
||||
entry: DiscordComponentEntry;
|
||||
interaction: AgentComponentInteraction;
|
||||
user: DiscordUser;
|
||||
replyOpts: { ephemeral?: boolean };
|
||||
componentLabel: string;
|
||||
unauthorizedReply: string;
|
||||
}): Promise<boolean> {
|
||||
const allowList = normalizeDiscordAllowList(params.entry.allowedUsers, [
|
||||
"discord:",
|
||||
"user:",
|
||||
"pk:",
|
||||
]);
|
||||
if (!allowList) {
|
||||
return true;
|
||||
}
|
||||
const match = resolveDiscordAllowListMatch({
|
||||
allowList,
|
||||
candidate: {
|
||||
id: params.user.id,
|
||||
name: params.user.username,
|
||||
tag: formatDiscordUserTag(params.user),
|
||||
},
|
||||
});
|
||||
if (match.allowed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
logVerbose(
|
||||
`discord component ${params.componentLabel}: blocked user ${params.user.id} (not in allowedUsers)`,
|
||||
);
|
||||
try {
|
||||
await params.interaction.reply({
|
||||
content: params.unauthorizedReply,
|
||||
...params.replyOpts,
|
||||
});
|
||||
} catch {
|
||||
// Interaction may have expired
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function ensureAgentComponentInteractionAllowed(params: {
|
||||
ctx: AgentComponentContext;
|
||||
interaction: AgentComponentInteraction;
|
||||
@@ -919,6 +961,7 @@ async function handleDiscordComponentEvent(params: {
|
||||
guildEntries: params.ctx.guildEntries,
|
||||
});
|
||||
const channelCtx = resolveDiscordChannelContext(params.interaction);
|
||||
const unauthorizedReply = `You are not authorized to use this ${params.componentLabel}.`;
|
||||
const memberAllowed = await ensureGuildComponentMemberAllowed({
|
||||
interaction: params.interaction,
|
||||
guildInfo,
|
||||
@@ -929,12 +972,24 @@ async function handleDiscordComponentEvent(params: {
|
||||
user,
|
||||
replyOpts,
|
||||
componentLabel: params.componentLabel,
|
||||
unauthorizedReply: `You are not authorized to use this ${params.componentLabel}.`,
|
||||
unauthorizedReply,
|
||||
});
|
||||
if (!memberAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const componentAllowed = await ensureComponentUserAllowed({
|
||||
entry,
|
||||
interaction: params.interaction,
|
||||
user,
|
||||
replyOpts,
|
||||
componentLabel: params.componentLabel,
|
||||
unauthorizedReply,
|
||||
});
|
||||
if (!componentAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consumed = resolveDiscordComponentEntry({
|
||||
id: parsed.componentId,
|
||||
consume: !entry.reusable,
|
||||
@@ -1056,6 +1111,7 @@ async function handleDiscordModalTrigger(params: {
|
||||
guildEntries: params.ctx.guildEntries,
|
||||
});
|
||||
const channelCtx = resolveDiscordChannelContext(params.interaction);
|
||||
const unauthorizedReply = "You are not authorized to use this form.";
|
||||
const memberAllowed = await ensureGuildComponentMemberAllowed({
|
||||
interaction: params.interaction,
|
||||
guildInfo,
|
||||
@@ -1066,12 +1122,24 @@ async function handleDiscordModalTrigger(params: {
|
||||
user,
|
||||
replyOpts,
|
||||
componentLabel: "form",
|
||||
unauthorizedReply: "You are not authorized to use this form.",
|
||||
unauthorizedReply,
|
||||
});
|
||||
if (!memberAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const componentAllowed = await ensureComponentUserAllowed({
|
||||
entry,
|
||||
interaction: params.interaction,
|
||||
user,
|
||||
replyOpts,
|
||||
componentLabel: "form",
|
||||
unauthorizedReply,
|
||||
});
|
||||
if (!componentAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consumed = resolveDiscordComponentEntry({
|
||||
id: parsed.componentId,
|
||||
consume: !entry.reusable,
|
||||
|
||||
@@ -321,6 +321,29 @@ describe("discord component interactions", () => {
|
||||
expect(resolveDiscordComponentEntry({ id: "btn_1", consume: false })).not.toBeNull();
|
||||
});
|
||||
|
||||
it("blocks buttons when allowedUsers does not match", async () => {
|
||||
registerDiscordComponentEntries({
|
||||
entries: [
|
||||
{
|
||||
id: "btn_1",
|
||||
kind: "button",
|
||||
label: "Approve",
|
||||
allowedUsers: ["999"],
|
||||
},
|
||||
],
|
||||
modals: [],
|
||||
});
|
||||
|
||||
const button = createDiscordComponentButton(createComponentContext());
|
||||
const { interaction, reply } = createComponentButtonInteraction();
|
||||
|
||||
await button.run(interaction, { cid: "btn_1" } as ComponentData);
|
||||
|
||||
expect(reply).toHaveBeenCalledWith({ content: "You are not authorized to use this button." });
|
||||
expect(dispatchReplyMock).not.toHaveBeenCalled();
|
||||
expect(resolveDiscordComponentEntry({ id: "btn_1", consume: false })).not.toBeNull();
|
||||
});
|
||||
|
||||
it("routes modal submissions with field values", async () => {
|
||||
registerDiscordComponentEntries({
|
||||
entries: [],
|
||||
|
||||
Reference in New Issue
Block a user