fix(security): enforce trusted sender auth for discord moderation

This commit is contained in:
Peter Steinberger
2026-02-19 15:18:00 +01:00
parent baa335f258
commit 775816035e
15 changed files with 498 additions and 22 deletions

View File

@@ -1,8 +1,8 @@
import type { RequestClient } from "@buape/carbon";
import type { APIChannel, APIGuild, APIGuildMember, APIRole } from "discord-api-types/v10";
import { ChannelType, PermissionFlagsBits, Routes } from "discord-api-types/v10";
import { resolveDiscordRest } from "./client.js";
import type { DiscordPermissionsSummary, DiscordReactOpts } from "./send.types.js";
import { resolveDiscordRest } from "./client.js";
const PERMISSION_ENTRIES = Object.entries(PermissionFlagsBits).filter(
([, value]) => typeof value === "bigint",
@@ -34,6 +34,10 @@ function hasAdministrator(bitfield: bigint) {
return (bitfield & ADMINISTRATOR_BIT) === ADMINISTRATOR_BIT;
}
function hasPermissionBit(bitfield: bigint, permission: bigint) {
return (bitfield & permission) === permission;
}
export function isThreadChannelType(channelType?: number) {
return (
channelType === ChannelType.GuildNewsThread ||
@@ -50,6 +54,58 @@ async function fetchBotUserId(rest: RequestClient) {
return me.id;
}
/**
* Fetch guild-level permissions for a user. This does not include channel-specific overwrites.
*/
export async function fetchMemberGuildPermissionsDiscord(
guildId: string,
userId: string,
opts: DiscordReactOpts = {},
): Promise<bigint | null> {
const rest = resolveDiscordRest(opts);
try {
const [guild, member] = await Promise.all([
rest.get(Routes.guild(guildId)) as Promise<APIGuild>,
rest.get(Routes.guildMember(guildId, userId)) as Promise<APIGuildMember>,
]);
const rolesById = new Map<string, APIRole>((guild.roles ?? []).map((role) => [role.id, role]));
const everyoneRole = rolesById.get(guildId);
let permissions = 0n;
if (everyoneRole?.permissions) {
permissions = addPermissionBits(permissions, everyoneRole.permissions);
}
for (const roleId of member.roles ?? []) {
const role = rolesById.get(roleId);
if (role?.permissions) {
permissions = addPermissionBits(permissions, role.permissions);
}
}
return permissions;
} catch {
// Not a guild member, guild not found, or API failure.
return null;
}
}
/**
* Returns true when the user has ADMINISTRATOR or any required permission bit.
*/
export async function hasGuildPermissionDiscord(
guildId: string,
userId: string,
requiredPermissions: bigint[],
opts: DiscordReactOpts = {},
): Promise<boolean> {
const permissions = await fetchMemberGuildPermissionsDiscord(guildId, userId, opts);
if (permissions === null) {
return false;
}
if (hasAdministrator(permissions)) {
return true;
}
return requiredPermissions.some((permission) => hasPermissionBit(permissions, permission));
}
export async function fetchChannelPermissionsDiscord(
channelId: string,
opts: DiscordReactOpts = {},