From e42c706e0ad80c2fc6a545b6c7be4dd7c9b157ee Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Sat, 2 Jul 2022 18:55:18 +1000 Subject: [PATCH 1/8] Skip check for rate limit bypass if no user id is provided --- api/src/middlewares/RateLimit.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index ca6de98f..13f1602c 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -46,12 +46,14 @@ export default function rateLimit(opts: { }): any { return async (req: Request, res: Response, next: NextFunction): Promise => { // exempt user? if so, immediately short circuit - const rights = await getRights(req.user_id); - if (rights.has("BYPASS_RATE_LIMITS")) return; - + if (req.user_id) { + const rights = await getRights(req.user_id); + if (rights.has("BYPASS_RATE_LIMITS")) return; + } + const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); var executor_id = getIpAdress(req); - if (!opts.onlyIp && req.user_id) executor_id = req.user_id; + if (!opts.onlyIp && req.user_id) executor_id = req.user_id; var max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; @@ -161,7 +163,7 @@ export async function initRateLimits(app: Router) { app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number; }) { const id = opts.executor_id + opts.bucket_id; var limit = Cache.get(id); if (!limit) { From a72b8a0bd95fb4d03ee9e308facc9a4a07b5d90a Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 00:14:26 +1000 Subject: [PATCH 2/8] Create role subdirectory and add GET api route --- .../guilds/#guild_id/roles/#role_id/index.ts | 145 ++++++++++++++++++ .../#guild_id/{roles.ts => roles/index.ts} | 0 2 files changed, 145 insertions(+) create mode 100644 api/src/routes/guilds/#guild_id/roles/#role_id/index.ts rename api/src/routes/guilds/#guild_id/{roles.ts => roles/index.ts} (100%) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts new file mode 100644 index 00000000..69c2143d --- /dev/null +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -0,0 +1,145 @@ +import { Router, Request, Response } from "express"; +import { + Role, + getPermission, + Member, + GuildRoleCreateEvent, + GuildRoleUpdateEvent, + GuildRoleDeleteEvent, + emitEvent, + Config, + DiscordApiErrors, + handleFile +} from "@fosscord/util"; +import { route } from "@fosscord/api"; +import {RoleModifySchema, RolePositionUpdateSchema} from '../' + +const router = Router(); + +router.get("/",route({}), async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params + await Member.IsInGuildOrFail(req.user_id, guild_id); + const role = await Role.find({ guild_id: guild_id }).find((r: {id: string}) => r.id === role_id); + return res.json(role); +}); + +// router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +// const guild_id = req.params.guild_id; +// const body = req.body as RoleModifySchema; + +// const role_count = await Role.count({ guild_id }); +// const { maxRoles } = Config.get().limits.guild; + +// if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); + +// const role = new Role({ +// // values before ...body are default and can be overriden +// position: 0, +// hoist: false, +// color: 0, +// mentionable: false, +// ...body, +// guild_id: guild_id, +// managed: false, +// permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")), +// tags: undefined, +// icon: null, +// unicode_emoji: null +// }); + +// await Promise.all([ +// role.save(), +// emitEvent({ +// event: "GUILD_ROLE_CREATE", +// guild_id, +// data: { +// guild_id, +// role: role +// } +// } as GuildRoleCreateEvent) +// ]); + +// res.json(role); +// }); + +// router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +// const guild_id = req.params.guild_id; +// const { role_id } = req.params; +// if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); + +// await Promise.all([ +// Role.delete({ +// id: role_id, +// guild_id: guild_id +// }), +// emitEvent({ +// event: "GUILD_ROLE_DELETE", +// guild_id, +// data: { +// guild_id, +// role_id +// } +// } as GuildRoleDeleteEvent) +// ]); + +// res.sendStatus(204); +// }); + +// // TODO: check role hierarchy + +// router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +// const { role_id, guild_id } = req.params; +// const body = req.body as RoleModifySchema; + +// if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); + +// const role = new Role({ +// ...body, +// id: role_id, +// guild_id, +// permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")) +// }); + +// await Promise.all([ +// role.save(), +// emitEvent({ +// event: "GUILD_ROLE_UPDATE", +// guild_id, +// data: { +// guild_id, +// role +// } +// } as GuildRoleUpdateEvent) +// ]); + +// res.json(role); +// }); + +// router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { +// const { guild_id } = req.params; +// const body = req.body as RolePositionUpdateSchema; + +// const perms = await getPermission(req.user_id, guild_id); +// perms.hasThrow("MANAGE_ROLES"); + +// await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); + +// const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) }); + +// await Promise.all( +// roles.map((x) => +// emitEvent({ +// event: "GUILD_ROLE_UPDATE", +// guild_id, +// data: { +// guild_id, +// role: x +// } +// } as GuildRoleUpdateEvent) +// ) +// ); + +// res.json(roles); +// }); + +export default router; diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles/index.ts similarity index 100% rename from api/src/routes/guilds/#guild_id/roles.ts rename to api/src/routes/guilds/#guild_id/roles/index.ts From e91a8deceab9793022c18ee735937b5180f15c0d Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 00:19:33 +1000 Subject: [PATCH 3/8] Fix compile error in get request --- .../guilds/#guild_id/roles/#role_id/index.ts | 46 ++----------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index 69c2143d..f01b81c9 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -19,52 +19,14 @@ const router = Router(); router.get("/",route({}), async (req: Request, res: Response) => { const { guild_id, role_id } = req.params await Member.IsInGuildOrFail(req.user_id, guild_id); - const role = await Role.find({ guild_id: guild_id }).find((r: {id: string}) => r.id === role_id); + const roles = await Role.find({ guild_id: guild_id }) + const role = roles.find((r: {id: string}) => r.id === role_id); return res.json(role); }); -// router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { -// const guild_id = req.params.guild_id; -// const body = req.body as RoleModifySchema; -// const role_count = await Role.count({ guild_id }); -// const { maxRoles } = Config.get().limits.guild; - -// if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); - -// const role = new Role({ -// // values before ...body are default and can be overriden -// position: 0, -// hoist: false, -// color: 0, -// mentionable: false, -// ...body, -// guild_id: guild_id, -// managed: false, -// permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")), -// tags: undefined, -// icon: null, -// unicode_emoji: null -// }); - -// await Promise.all([ -// role.save(), -// emitEvent({ -// event: "GUILD_ROLE_CREATE", -// guild_id, -// data: { -// guild_id, -// role: role -// } -// } as GuildRoleCreateEvent) -// ]); - -// res.json(role); -// }); - -// router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { -// const guild_id = req.params.guild_id; -// const { role_id } = req.params; +// router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +// const { guild_id, role_id } = req.params; // if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); // await Promise.all([ From 6a801eafbf2813373ef7c3324366ef0147da06bd Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 00:42:03 +1000 Subject: [PATCH 4/8] Move role_id specific api routes to correct file --- .../guilds/#guild_id/roles/#role_id/index.ts | 123 +++++++----------- .../routes/guilds/#guild_id/roles/index.ts | 53 -------- 2 files changed, 46 insertions(+), 130 deletions(-) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index f01b81c9..88c1219b 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -1,18 +1,15 @@ import { Router, Request, Response } from "express"; import { Role, - getPermission, Member, - GuildRoleCreateEvent, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, - Config, - DiscordApiErrors, - handleFile + handleFile } from "@fosscord/util"; import { route } from "@fosscord/api"; -import {RoleModifySchema, RolePositionUpdateSchema} from '../' +import { HTTPError } from "lambert-server"; +import {RoleModifySchema} from '../' const router = Router(); @@ -24,84 +21,56 @@ router.get("/",route({}), async (req: Request, res: Response) => { return res.json(role); }); +router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); -// router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { -// const { guild_id, role_id } = req.params; -// if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); + await Promise.all([ + Role.delete({ + id: role_id, + guild_id: guild_id + }), + emitEvent({ + event: "GUILD_ROLE_DELETE", + guild_id, + data: { + guild_id, + role_id + } + } as GuildRoleDeleteEvent) + ]); -// await Promise.all([ -// Role.delete({ -// id: role_id, -// guild_id: guild_id -// }), -// emitEvent({ -// event: "GUILD_ROLE_DELETE", -// guild_id, -// data: { -// guild_id, -// role_id -// } -// } as GuildRoleDeleteEvent) -// ]); + res.sendStatus(204); +}); -// res.sendStatus(204); -// }); +// TODO: check role hierarchy -// // TODO: check role hierarchy +router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { + const { role_id, guild_id } = req.params; + const body = req.body as RoleModifySchema; -// router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { -// const { role_id, guild_id } = req.params; -// const body = req.body as RoleModifySchema; + if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); -// if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); + const role = new Role({ + ...body, + id: role_id, + guild_id, + permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")) + }); -// const role = new Role({ -// ...body, -// id: role_id, -// guild_id, -// permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")) -// }); + await Promise.all([ + role.save(), + emitEvent({ + event: "GUILD_ROLE_UPDATE", + guild_id, + data: { + guild_id, + role + } + } as GuildRoleUpdateEvent) + ]); -// await Promise.all([ -// role.save(), -// emitEvent({ -// event: "GUILD_ROLE_UPDATE", -// guild_id, -// data: { -// guild_id, -// role -// } -// } as GuildRoleUpdateEvent) -// ]); - -// res.json(role); -// }); - -// router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { -// const { guild_id } = req.params; -// const body = req.body as RolePositionUpdateSchema; - -// const perms = await getPermission(req.user_id, guild_id); -// perms.hasThrow("MANAGE_ROLES"); - -// await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); - -// const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) }); - -// await Promise.all( -// roles.map((x) => -// emitEvent({ -// event: "GUILD_ROLE_UPDATE", -// guild_id, -// data: { -// guild_id, -// role: x -// } -// } as GuildRoleUpdateEvent) -// ) -// ); - -// res.json(roles); -// }); + res.json(role); +}); export default router; diff --git a/api/src/routes/guilds/#guild_id/roles/index.ts b/api/src/routes/guilds/#guild_id/roles/index.ts index b6894e3f..53465105 100644 --- a/api/src/routes/guilds/#guild_id/roles/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/index.ts @@ -81,59 +81,6 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }) res.json(role); }); -router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const { role_id } = req.params; - if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); - - await Promise.all([ - Role.delete({ - id: role_id, - guild_id: guild_id - }), - emitEvent({ - event: "GUILD_ROLE_DELETE", - guild_id, - data: { - guild_id, - role_id - } - } as GuildRoleDeleteEvent) - ]); - - res.sendStatus(204); -}); - -// TODO: check role hierarchy - -router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const { role_id, guild_id } = req.params; - const body = req.body as RoleModifySchema; - - if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); - - const role = new Role({ - ...body, - id: role_id, - guild_id, - permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")) - }); - - await Promise.all([ - role.save(), - emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { - guild_id, - role - } - } as GuildRoleUpdateEvent) - ]); - - res.json(role); -}); - router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const body = req.body as RolePositionUpdateSchema; From c7fa2238537d96d4ad3db7269af941933335cd19 Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 01:03:35 +1000 Subject: [PATCH 5/8] Simplify role query as per code review --- api/src/routes/guilds/#guild_id/roles/#role_id/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index 88c1219b..2c1a4c7e 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -16,8 +16,7 @@ const router = Router(); router.get("/",route({}), async (req: Request, res: Response) => { const { guild_id, role_id } = req.params await Member.IsInGuildOrFail(req.user_id, guild_id); - const roles = await Role.find({ guild_id: guild_id }) - const role = roles.find((r: {id: string}) => r.id === role_id); + const role = await Role.find({ guild_id, id: role_id }) return res.json(role); }); From 99afcf0f3c937cfc691bd7bd26c425ed4d2747af Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 01:04:16 +1000 Subject: [PATCH 6/8] Add semicolon --- api/src/routes/guilds/#guild_id/roles/#role_id/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index 2c1a4c7e..79cf39cc 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -16,7 +16,7 @@ const router = Router(); router.get("/",route({}), async (req: Request, res: Response) => { const { guild_id, role_id } = req.params await Member.IsInGuildOrFail(req.user_id, guild_id); - const role = await Role.find({ guild_id, id: role_id }) + const role = await Role.find({ guild_id, id: role_id }); return res.json(role); }); From 582491cfe14879cd32496ea548dfbdfd034b3076 Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 01:08:12 +1000 Subject: [PATCH 7/8] Ensure query fails if role with specified id does not exist --- api/src/routes/guilds/#guild_id/roles/#role_id/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index 79cf39cc..c9a4f662 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -16,7 +16,7 @@ const router = Router(); router.get("/",route({}), async (req: Request, res: Response) => { const { guild_id, role_id } = req.params await Member.IsInGuildOrFail(req.user_id, guild_id); - const role = await Role.find({ guild_id, id: role_id }); + const role = await Role.findOneOrFail({ guild_id, id: role_id }); return res.json(role); }); From d75eefe7cd8249ed7f39928c916cf46ccab72f0f Mon Sep 17 00:00:00 2001 From: LachlanCourt Date: Tue, 5 Jul 2022 01:23:54 +1000 Subject: [PATCH 8/8] Format changed files --- .../guilds/#guild_id/roles/#role_id/index.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index c9a4f662..2ad01682 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -1,21 +1,14 @@ import { Router, Request, Response } from "express"; -import { - Role, - Member, - GuildRoleUpdateEvent, - GuildRoleDeleteEvent, - emitEvent, - handleFile -} from "@fosscord/util"; +import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, handleFile } from "@fosscord/util"; import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; -import {RoleModifySchema} from '../' +import { RoleModifySchema } from "../"; const router = Router(); -router.get("/",route({}), async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params - await Member.IsInGuildOrFail(req.user_id, guild_id); +router.get("/", route({}), async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); const role = await Role.findOneOrFail({ guild_id, id: role_id }); return res.json(role); }); @@ -48,7 +41,7 @@ router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" } const { role_id, guild_id } = req.params; const body = req.body as RoleModifySchema; - if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); + if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); const role = new Role({ ...body,