Skip to content

Commit 6690550

Browse files
chore: add note about admin permission when managing permissions
closes #17
1 parent 4d660fb commit 6690550

File tree

4 files changed

+58
-10
lines changed

4 files changed

+58
-10
lines changed

src/interactions/commands/chatInput/config.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { FastifyInstance } from "fastify";
1919

2020
import { discordAPIBaseURL, embedPink } from "../../../constants";
21+
import { DiscordPermissions } from "../../../consts";
2122
import {
2223
ExpectedFailure,
2324
ExpectedPermissionFailure,
@@ -26,6 +27,7 @@ import {
2627
} from "../../../errors";
2728
import { checkIfRoleIsBelowUsersHighestRole } from "../../../lib/permissions/checks";
2829
import { InternalPermissions } from "../../../lib/permissions/consts";
30+
import { checkDiscordPermissionValue } from "../../../lib/permissions/utils";
2931
import { GuildSession } from "../../../lib/session";
3032
import { addTipToEmbed } from "../../../lib/tips";
3133
import { InternalInteractionType } from "../../interaction";
@@ -226,13 +228,13 @@ async function handlePermissionsManageSubcommand({
226228

227229
const resolvedData = interaction.data.resolved;
228230
let targetType: "role" | "user";
231+
let targetPermissions: Snowflake | undefined;
229232

230233
if (resolvedData?.roles && resolvedData.roles[targetId] !== undefined) {
231234
targetType = "role";
235+
targetPermissions = resolvedData.roles[targetId].permissions;
232236
} else if (
233-
resolvedData?.members &&
234-
resolvedData.members[targetId] !== undefined &&
235-
resolvedData.users &&
237+
resolvedData?.users &&
236238
resolvedData.users[targetId] !== undefined
237239
) {
238240
targetType = "user";
@@ -243,6 +245,13 @@ async function handlePermissionsManageSubcommand({
243245
"The target cannot be a bot"
244246
);
245247
}
248+
// If the user is in the guild, then it will be in the members resolved data
249+
// Otherwise not
250+
// If the user is in the guild, set the permissions value to it's permissions
251+
if (resolvedData?.members && resolvedData.members[targetId] !== undefined) {
252+
targetPermissions =
253+
resolvedData.members[targetId].permissions ?? undefined;
254+
}
246255
} else {
247256
throw new UnexpectedFailure(
248257
InteractionOrRequestFinalStatus.APPLICATION_COMMAND_RESOLVED_MISSING_EXPECTED_VALUE,
@@ -279,13 +288,21 @@ async function handlePermissionsManageSubcommand({
279288

280289
// Not deferred as no logic is 'heavy'
281290

291+
const hasAdminPermission =
292+
targetPermissions !== undefined &&
293+
checkDiscordPermissionValue(
294+
BigInt(targetPermissions),
295+
DiscordPermissions.ADMINISTRATOR
296+
);
297+
282298
const permissionReturnData = await createPermissionsEmbed({
283299
targetType,
284300
targetId,
285301
channelId: channel?.id ?? null,
286302
guildId: session.guildId,
287303
instance,
288304
first: true,
305+
hasAdminPermission,
289306
});
290307

291308
// Void function that waits a bit then fetches the original interaction message
@@ -348,14 +365,14 @@ async function handlePermissionsQuickstartSubcommand({
348365
);
349366
}
350367
let targetType: "role" | "user";
368+
let targetPermissions: Snowflake | undefined;
351369
const resolvedData = interaction.data.resolved;
352370

353371
if (resolvedData?.roles && resolvedData.roles[targetId] !== undefined) {
354372
targetType = "role";
373+
targetPermissions = resolvedData.roles[targetId].permissions;
355374
} else if (
356-
resolvedData?.members &&
357-
resolvedData.members[targetId] !== undefined &&
358-
resolvedData.users &&
375+
resolvedData?.users &&
359376
resolvedData.users[targetId] !== undefined
360377
) {
361378
targetType = "user";
@@ -366,6 +383,13 @@ async function handlePermissionsQuickstartSubcommand({
366383
"The target cannot be a bot"
367384
);
368385
}
386+
// If the user is in the guild, then it will be in the members resolved data
387+
// Otherwise not
388+
// If the user is in the guild, set the permissions value to it's permissions
389+
if (resolvedData?.members && resolvedData.members[targetId] !== undefined) {
390+
targetPermissions =
391+
resolvedData.members[targetId].permissions ?? undefined;
392+
}
369393
} else {
370394
throw new UnexpectedFailure(
371395
InteractionOrRequestFinalStatus.APPLICATION_COMMAND_RESOLVED_MISSING_EXPECTED_VALUE,
@@ -430,6 +454,7 @@ async function handlePermissionsQuickstartSubcommand({
430454
channelId: channel?.id,
431455
});
432456
}
457+
433458
return {
434459
type: InteractionResponseType.ChannelMessageWithSource,
435460
data: {
@@ -451,7 +476,15 @@ async function handlePermissionsQuickstartSubcommand({
451476
targetType === "user" ? `<@${targetId}>` : `<@&${targetId}>`
452477
}${
453478
channel !== undefined ? ` on the channel <#${channel.id}>` : ""
454-
}.`,
479+
}.` + // Add note if the target has admin perms about how they bypass permissions
480+
(targetPermissions !== undefined
481+
? checkDiscordPermissionValue(
482+
BigInt(targetPermissions),
483+
DiscordPermissions.ADMINISTRATOR
484+
)
485+
? "\n\nNote: The target has the discord `ADMINISTRATOR` permission. Any user with this permission will bypass bot permission checks (all will be allowed)"
486+
: ""
487+
: ""),
455488
color: embedPink,
456489
timestamp: new Date().toISOString(),
457490
}),

src/interactions/selects/manage-permissions-select.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default async function handleManagePermissionsSelect(
2525
const targetType = customIdData[2] as "role" | "user";
2626
const targetId = customIdData[3];
2727
const channelId = JSON.parse(customIdData[4]) as Snowflake | null;
28+
const hasAdminPermission = JSON.parse(customIdData[5]) as boolean;
2829

2930
// No permission checks are required here as they are either checked when the /config ... command is ran
3031
// or when the permissions are updated
@@ -167,6 +168,7 @@ export default async function handleManagePermissionsSelect(
167168
guildId: session.guildId,
168169
instance,
169170
first: false,
171+
hasAdminPermission,
170172
});
171173
// register interaction with permission interaction cache
172174
instance.permissionManager.interactionCacheManager.registerInteraction({

src/interactions/shared/permissions-config.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,27 @@ interface CreatePermissionsEmbedOptions {
2222
guildId: Snowflake;
2323
instance: FastifyInstance;
2424
first: boolean;
25+
hasAdminPermission: boolean;
2526
}
2627

2728
interface CreatePermissionsEmbedResult {
2829
embed: APIEmbed;
2930
components: APIActionRowComponent<APIMessageActionRowComponent>[];
3031
}
3132

33+
/*
34+
Custom Id Format:
35+
<name: manage-permissions-select>:<action: allow | deny>:<targetType: role | user>:<targetId: snowflake>:<channelId: snowflake | null>:<hasAdminPermissions: true | false>
36+
*/
37+
3238
const createPermissionsEmbed = async ({
3339
targetType,
3440
targetId,
3541
channelId,
3642
guildId,
3743
instance,
3844
first,
45+
hasAdminPermission,
3946
}: CreatePermissionsEmbedOptions): Promise<CreatePermissionsEmbedResult> => {
4047
// No permission checks are required here as they are either checked when the /config ... command is ran
4148
// or when the permissions are updated
@@ -85,7 +92,7 @@ const createPermissionsEmbed = async ({
8592
components: [
8693
{
8794
type: ComponentType.SelectMenu,
88-
custom_id: `manage-permissions-select:null:${targetType}:${targetId}:null`,
95+
custom_id: `manage-permissions-select:null:${targetType}:${targetId}:null:${hasAdminPermission.toString()}`,
8996
options,
9097
max_values: options.length,
9198
min_values: 0,
@@ -169,6 +176,11 @@ const createPermissionsEmbed = async ({
169176
})
170177
.join("\n");
171178

179+
// Add note about admin permissions
180+
if (hasAdminPermission) {
181+
description +=
182+
"\n\nNote: The target has the discord `ADMINISTRATOR` permission. Any user with this permission will bypass bot permission checks (all will be allowed)";
183+
}
172184
return {
173185
embed: addTipToEmbed({
174186
title: "Managing permissions",
@@ -199,7 +211,7 @@ const createPermissionsEmbed = async ({
199211
type: ComponentType.SelectMenu,
200212
custom_id: `manage-permissions-select:allow:${targetType}:${targetId}:${JSON.stringify(
201213
channelId
202-
)}`,
214+
)}:${hasAdminPermission.toString()}`,
203215
options: allowOptions,
204216
placeholder: "Select permissions to allow",
205217
max_values: allowOptions.length,
@@ -229,7 +241,7 @@ const createPermissionsEmbed = async ({
229241
type: ComponentType.SelectMenu,
230242
custom_id: `manage-permissions-select:deny:${targetType}:${targetId}:${JSON.stringify(
231243
channelId
232-
)}`,
244+
)}:${hasAdminPermission.toString()}`,
233245
options: denyOptions,
234246
placeholder: "Select permissions to deny",
235247
max_values: denyOptions.length,

src/lib/permissions/interactionCache.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class PermissionInteractionCache {
184184
guildId,
185185
instance: this._instance,
186186
first: false,
187+
hasAdminPermission: false, // It's just a hint, doesn't matter if it goes away. Too hard / inaccurate to track this
187188
});
188189
for (const messageCacheId of messageCacheIds.messageIds) {
189190
const interactionCache = this._interactionCache[messageCacheId];

0 commit comments

Comments
 (0)