Skip to content

Commit 743632f

Browse files
Merge pull request #4 from message-manager-discord/permissions-rewrite
feat: permissions rewrite
2 parents edb8f17 + 10f448e commit 743632f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3887
-2220
lines changed

.eslintrc.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"root": true,
33
"parser": "@typescript-eslint/parser",
4-
"plugins": ["@typescript-eslint"],
4+
"plugins": ["@typescript-eslint", "simple-import-sort"],
55
"parserOptions": {
66
"tsconfigRootDir": ".",
77
"project": ["./tsconfig.json"]
@@ -12,5 +12,10 @@
1212
"plugin:@typescript-eslint/recommended",
1313
"prettier",
1414
"plugin:@typescript-eslint/recommended-requiring-type-checking"
15-
]
15+
],
16+
"rules": {
17+
"@typescript-eslint/strict-boolean-expressions": "warn",
18+
"simple-import-sort/imports": "warn",
19+
"simple-import-sort/exports": "warn"
20+
}
1621
}

package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"discord-api-types": "^0.27.3",
5959
"eslint": "^8.12.0",
6060
"eslint-config-prettier": "^8.5.0",
61+
"eslint-plugin-simple-import-sort": "^7.0.0",
6162
"prettier": "2.6.1",
6263
"prisma": "^3.9.2",
6364
"tap": "^16.0.1",

src/authRoutes.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { FastifyInstance } from "fastify";
33
import httpErrors from "http-errors";
44
const { Forbidden } = httpErrors;
55
import { Static, Type } from "@sinclair/typebox";
6-
import { v5 as uuidv5 } from "uuid";
76
import crypto from "crypto";
7+
import { v5 as uuidv5 } from "uuid";
8+
89
import DiscordOauthRequests from "./discordOauth";
910

1011
const CallbackQuerystring = Type.Object({
@@ -43,10 +44,7 @@ const addPlugin = async (instance: FastifyInstance) => {
4344
const { redirect_to } = request.query;
4445

4546
const state = crypto.randomBytes(16).toString("hex");
46-
await instance.redisCache.setState(
47-
state,
48-
redirect_to ? redirect_to : null
49-
);
47+
await instance.redisCache.setState(state, redirect_to ?? null);
5048

5149
return reply.redirect(
5250
307,
@@ -66,7 +64,7 @@ const addPlugin = async (instance: FastifyInstance) => {
6664
},
6765
async (request, reply) => {
6866
const { code, state } = request.query;
69-
if (!state) {
67+
if (state === undefined) {
7068
return new Forbidden("Missing state");
7169
}
7270
const cachedState = await instance.redisCache.getState(state);
@@ -103,7 +101,7 @@ const addPlugin = async (instance: FastifyInstance) => {
103101
const date = new Date();
104102
date.setDate(date.getDate() + 7);
105103

106-
const redirectPath = cachedState.redirectPath || "/";
104+
const redirectPath = cachedState.redirectPath ?? "/";
107105
return reply
108106
.setCookie("_HOST-session", session, {
109107
secure: true,

src/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ const inviteUrl =
1111

1212
export {
1313
discordAPIBaseURL,
14-
requiredScopes,
1514
embedPink,
16-
successGreen,
1715
failureRed,
1816
inviteUrl,
17+
requiredScopes,
18+
successGreen,
1919
};

src/consts.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// https://github.com/detritusjs/client/blob/b27cbaa5bfb48506b059be178da0e871b83ba95e/src/constants.ts#L917
2-
export const Permissions = Object.freeze({
2+
const DiscordPermissions = Object.freeze({
33
NONE: 0n,
44
CREATE_INSTANT_INVITE: 1n << 0n,
55
KICK_MEMBERS: 1n << 1n,
@@ -41,3 +41,35 @@ export const Permissions = Object.freeze({
4141
USE_EXTERNAL_STICKERS: 1n << 37n,
4242
SEND_MESSAGES_IN_THREADS: 1n << 38n,
4343
});
44+
45+
const _permissionsByName: { [name: string]: bigint } = {};
46+
const _permissionsByValue: { [value: string]: string } = {};
47+
// Find the names and values of all the permissions from Permissions
48+
49+
for (const [key, value] of Object.entries(DiscordPermissions)) {
50+
_permissionsByName[key] = value;
51+
_permissionsByValue[value.toString()] = key;
52+
}
53+
54+
const getDiscordPermissionByName = (name: string): bigint | undefined => {
55+
return _permissionsByName[name];
56+
};
57+
58+
const getDiscordPermissionByValue = (value: bigint): string | undefined => {
59+
return _permissionsByValue[value.toString()];
60+
};
61+
62+
const parseDiscordPermissionValuesToStringNames = (
63+
permissions: bigint[]
64+
): string[] => {
65+
const parsed = permissions.map((permission) => {
66+
return getDiscordPermissionByValue(permission);
67+
});
68+
return parsed.filter((permission) => permission !== undefined) as string[];
69+
};
70+
export {
71+
DiscordPermissions,
72+
getDiscordPermissionByName,
73+
getDiscordPermissionByValue,
74+
parseDiscordPermissionValuesToStringNames,
75+
};

src/discordOauth.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import axios, { AxiosError, AxiosResponse } from "axios";
22
import {
3+
RESTGetAPICurrentUserGuildsResult,
34
RESTGetAPICurrentUserResult,
5+
RESTGetCurrentUserGuildMemberResult,
46
RESTPostOAuth2AccessTokenResult,
57
Snowflake,
6-
RESTGetAPICurrentUserGuildsResult,
7-
RESTGetCurrentUserGuildMemberResult,
88
} from "discord-api-types/v9";
99
import { FastifyInstance } from "fastify";
1010
import { URLSearchParams } from "url";
11+
1112
import { discordAPIBaseURL, requiredScopes } from "./constants";
1213
import {
1314
ExpectedOauth2Failure,
@@ -83,15 +84,15 @@ class DiscordOauthRequests {
8384
cacheExpiry?: number;
8485
userId?: Snowflake;
8586
}): Promise<UncachedResponse | CachedResponse> {
86-
if (cacheExpiry && userId) {
87+
if (cacheExpiry !== undefined && userId !== undefined) {
8788
// TODO: Should cacheExpiry be checked / used
8889
// Requests without a token are not cached
8990
const cachedResponse = (await this._instance.redisCache.getOauthCache(
9091
path,
9192
userId
9293
)) as string | null;
9394

94-
if (cachedResponse) {
95+
if (cachedResponse !== null) {
9596
return { cached: true, data: cachedResponse };
9697
}
9798
const response = await this._makeRequest({
@@ -108,7 +109,7 @@ class DiscordOauthRequests {
108109
);
109110
return response;
110111
}
111-
if (token) {
112+
if (token !== undefined) {
112113
headers = { ...headers, Authorization: `Bearer ${token}` };
113114
}
114115
let response: AxiosResponse;
@@ -139,7 +140,8 @@ class DiscordOauthRequests {
139140
return new UnexpectedFailure(
140141
InteractionOrRequestFinalStatus.OAUTH_REQUEST_FAILED,
141142
`Oauth request to ${
142-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions
143+
// TODO: Fix this type mess. Most likely by changing request libs
144+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions, @typescript-eslint/strict-boolean-expressions
143145
response.request.path || "Unknown path"
144146
} failed with the status ${statusCode}`
145147
);
@@ -152,7 +154,7 @@ class DiscordOauthRequests {
152154
}): Promise<RESTGetAPICurrentUserResult> {
153155
let cacheExpiry: number | undefined;
154156
let response;
155-
if (user.userId) {
157+
if (user.userId !== undefined) {
156158
cacheExpiry = 1000 * 60 * 5; // 5 minutes
157159
response = await this._makeRequest({
158160
path: "/users/@me",

src/discord_commands/global.json

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -42,71 +42,57 @@
4242
"description": "Manage bot level permissions. Requires being a manager on the bot.",
4343
"options": [
4444
{
45-
"name": "set",
45+
"name": "manage",
4646
"type": 1,
47-
"description": "Set a user's or a role's bot permission",
47+
"description": "Manage and view the target's permissions",
4848
"options": [
4949
{
5050
"type": 9,
5151
"name": "target",
5252
"required": true,
53-
"description": "The user or role to set the permission for"
54-
},
55-
{
56-
"type": 4,
57-
"name": "permission",
58-
"required": true,
59-
"description": "The permission to set",
60-
"choices": [
61-
{
62-
"name": "View Messages",
63-
"value": 1
64-
},
65-
{
66-
"name": "Edit Messages",
67-
"value": 2
68-
},
69-
{
70-
"name": "Send Messages",
71-
"value": 3
72-
},
73-
{
74-
"name": "Delete Messages",
75-
"value": 4
76-
},
77-
{
78-
"name": "Manage Permissions",
79-
"value": 5
80-
},
81-
{
82-
"name": "Manage Config",
83-
"value": 6
84-
}
85-
]
53+
"description": "The user or role to manage / view permissions for"
8654
},
8755
{
8856
"type": 7,
8957
"name": "channel",
90-
"description": "The channel to set the permission on. Leave empty to set the permission on the guild level.",
58+
"required": false,
59+
"description": "The channel to manage / view permissions of the target on. Leave this blank to manage permissions on the entire server",
9160
"channel_types": [0, 5]
9261
}
9362
]
9463
},
9564
{
96-
"name": "remove",
65+
"name": "quickstart",
9766
"type": 1,
98-
"description": "Remove a user's or a role's bot permission",
67+
"description": "Get permissions setup with presets. Use the manage command to have finer control over permissions",
9968
"options": [
10069
{
10170
"type": 9,
10271
"name": "target",
10372
"required": true,
104-
"description": "The user or role to remove permissions for"
73+
"description": "The user or role to setup permissions for"
74+
},
75+
{
76+
"type": 3,
77+
"name": "preset",
78+
"required": true,
79+
"description": "The preset to grant to the target",
80+
"choices": [
81+
{
82+
"name": "message-access",
83+
"value": "message-access"
84+
},
85+
{
86+
"name": "management-access",
87+
"value": "management-access"
88+
}
89+
]
10590
},
10691
{
10792
"type": 7,
10893
"name": "channel",
109-
"description": "The channel to remove the permissions on. Leave empty to remove the permissions on the guild level.",
94+
"required": false,
95+
"description": "The channel to setup permissions of the target on. Leave this blank to setup permissions on the entire server",
11096
"channel_types": [0, 5]
11197
}
11298
]
@@ -121,21 +107,6 @@
121107
"name": "channel",
122108
"description": "The channel to list the permissions on. Leave empty to list the permissions on the guild level.",
123109
"channel_types": [0, 5]
124-
},
125-
{
126-
"type": 3,
127-
"name": "type-filter",
128-
"description": "Filter the set permissions by a specific type (user or role)",
129-
"choices": [
130-
{
131-
"name": "Users",
132-
"value": "users"
133-
},
134-
{
135-
"name": "Roles",
136-
"value": "roles"
137-
}
138-
]
139110
}
140111
]
141112
}

src/errors.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ enum InteractionOrRequestFinalStatus {
2626
BOT_FOUND_WHEN_USER_EXPECTED,
2727
NO_PERMISSION_TO_REMOVE,
2828
TAG_NOT_FOUND,
29-
MANAGE_CONFIG_PERMISSION_CANNOT_BE_SET_ON_CHANNEL_LEVEL,
29+
NO_PERMISSIONS_PRESET_SELECTED,
30+
MANAGEMENT_PERMISSIONS_CANNOT_BE_SET_ON_CHANNEL_LEVEL,
3031
GENERIC_EXPECTED_PERMISSIONS_FAILURE = 3000,
3132
USER_MISSING_DISCORD_PERMISSION,
3233
BOT_MISSING_DISCORD_PERMISSION,
@@ -60,6 +61,7 @@ enum InteractionOrRequestFinalStatus {
6061
OAUTH_REQUEST_FAILED,
6162
CREATE_WEBHOOK_RESULT_MISSING_TOKEN,
6263
ROLE_NOT_IN_CACHE,
64+
PERMISSIONS_CANNOT_CROSSOVER_WHEN_UPDATING,
6365
}
6466

6567
class CustomError extends Error {
@@ -97,11 +99,11 @@ class UnexpectedFailure extends CustomError {
9799
}
98100

99101
export {
100-
InteractionOrRequestFinalStatus,
102+
CustomError,
101103
ExpectedFailure,
102-
ExpectedPermissionFailure,
103104
ExpectedOauth2Failure,
105+
ExpectedPermissionFailure,
106+
InteractionOrRequestFinalStatus,
104107
LimitHit,
105108
UnexpectedFailure,
106-
CustomError,
107109
};

0 commit comments

Comments
 (0)