From 249afd11d5dbae24a182368e6251b0b024e216b0 Mon Sep 17 00:00:00 2001 From: Rory& Date: Mon, 2 Nov 2026 20:01:59 +0100 Subject: [PATCH] Application tester routes --- assets/openapi.json | Bin 1145525 -> 1152195 bytes assets/schemas.json | Bin 575458 -> 577253 bytes .../oauth2/applications/#id/allowlist.ts | 114 ++++++++++++++++++ src/api/routes/oauth2/applications/@me.ts | 2 +- .../routes/oauth2/applications/allowlist.ts | 62 ++++++++++ src/schemas/api/developers/Application.ts | 17 +++ .../limits/ApplicationLimits.ts | 1 + 7 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 src/api/routes/oauth2/applications/#id/allowlist.ts create mode 100644 src/api/routes/oauth2/applications/allowlist.ts diff --git a/assets/openapi.json b/assets/openapi.json index 3463550251a29da388b18e855c11e720f0ee3126..509181b407297a6dee2ebc28c672ff9dce7853cd 100644 GIT binary patch delta 665 zcmaixOK1~O6o$F;$~P1DXtT*|7I-~B|;LR_(F+{;jqmT%=rGc@zfEMV+|vJs3l zz6FM>zK^bTv&|-{+*hX1-?7JG<*xo3%mfxmPd9$aw92^WxlSQpaz@V;&2h)NWUfyl zXfgT(K{?drwR6^wE32g8#`Yl?N~t@@^ec4|wXTSx&aT6 zZ*DAyAah0D^WRl|E^E6H^bbeRHeC#LUDb9HeV29(!6OrkzM96AC`F}c6rHH)*zJSA E01r;~?*IS* delta 136 zcmX@S(rxQk*M=6x7N#xCQcK$3E@uW}79eH?Vm2UV-~M(v#})2rTRHfq_ig16nQq|2 z%{g5`gH?X|^er4SrVER*>u+z^%JDgJ`hhG)NlghYPA!^l_>nQ3 z8!QhLb4*FuoO@E9aryxn#;{3`Sf+pIWMZ9Oz{MKI3s;hyoLVq>;z8Nz3-&XnO%BKm zo4&t^DSGn#9Pa6p&6xwH?q_yukH}yKViq7~1!A`C5gF`PQ`mA7D?(C>rysb%?#^gB zU2qYr!t?-PHrDC)&$6>mw?4~$WV-Klb|E&Pnv&F_$&S_G+e6N=hXn9LoDX;M5Yq+C8uB5#OOC& h$&gKCdV)A3>-0N9?4cm{qN`iBudNlw5 delta 58 zcmaDlSNT!DazhJa3)2?nL+R~1vYCOH1&CRJm~H!xZ1(Kb>HQbkFHLr?4&8p?5_?F% K_SLE!Rx1Gn0U7)N diff --git a/src/api/routes/oauth2/applications/#id/allowlist.ts b/src/api/routes/oauth2/applications/#id/allowlist.ts new file mode 100644 index 000000000..10698b74b --- /dev/null +++ b/src/api/routes/oauth2/applications/#id/allowlist.ts @@ -0,0 +1,114 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import { Request, Response, Router } from "express"; +import { route } from "@spacebar/api/middlewares"; +import { Application, User } from "@spacebar/database"; +import { Config, DiscordApiErrors } from "@spacebar/util"; +import { AllowlistAddRequestSchema, AllowlistedUser, ApplicationAllowlistResponse, ApplicationMembershipState, PublicUserProjection } from "@spacebar/schemas"; + +const router: Router = Router({ mergeParams: true }); + +router.get( + "/", + route({ + responses: { + 200: { + body: "ApplicationAllowlistResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: stub + const app = await Application.findOneOrFail({ + where: { id: req.params.id as string }, + relations: { bot: true, owner: true }, + select: { + owner: { id: true }, + }, + }); + + if (req.user_id != app.owner.id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + + return res.json([] satisfies ApplicationAllowlistResponse); + }, +); + +router.post( + "/", + route({ + requestBody: "AllowlistAddRequestSchema", + responses: { + 200: { + body: "AllowlistedUser", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: stub + const app = await Application.findOneOrFail({ + where: { id: req.params.id as string }, + relations: { bot: true, owner: true }, + select: { + owner: { id: true }, + }, + }); + + if (req.user_id != app.owner.id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (Number(1) >= Config.get().limits.application.maxTesters) throw DiscordApiErrors.MAXIMUM_OAUTH2_APPLICATIONS; // TODO: correct error + + const body = req.body as AllowlistAddRequestSchema; + + const targetUser = await User.findOneOrFail({ + where: { + username: body.username, + discriminator: body.discriminator ?? "0", + }, + }); + + return res.json({ + user: targetUser.toPartialUser(), + state: ApplicationMembershipState.INVITED, + } satisfies AllowlistedUser); + }, +); + +router.delete( + "/:user_id", + route({ + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + // TODO: stub + const app = await Application.findOneOrFail({ + where: { id: req.params.id as string }, + relations: { bot: true, owner: true }, + select: { + owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + }, + }); + + if (req.user_id != app.owner.id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + + return res.status(204).json({}); + }, +); + +export default router; diff --git a/src/api/routes/oauth2/applications/@me.ts b/src/api/routes/oauth2/applications/@me.ts index 872cb5b27..732b74ef0 100644 --- a/src/api/routes/oauth2/applications/@me.ts +++ b/src/api/routes/oauth2/applications/@me.ts @@ -35,7 +35,7 @@ router.get( }), async (req: Request, res: Response) => { const app = await Application.findOneOrFail({ - where: { id: req.params.id as string }, // ...huh? there's no ID in the path... + where: { id: req.user_id as string }, relations: { bot: true, owner: true }, select: { owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), diff --git a/src/api/routes/oauth2/applications/allowlist.ts b/src/api/routes/oauth2/applications/allowlist.ts new file mode 100644 index 000000000..43d07d903 --- /dev/null +++ b/src/api/routes/oauth2/applications/allowlist.ts @@ -0,0 +1,62 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import { Request, Response, Router } from "express"; +import { route } from "@spacebar/api/middlewares"; +import { Application, User } from "@spacebar/database"; +import { Config, DiscordApiErrors } from "@spacebar/util"; +import { + AllowlistAcceptSchema, + AllowlistAddRequestSchema, + AllowlistedUser, + ApplicationAllowlistResponse, + ApplicationMembershipState, + PublicUserProjection, +} from "@spacebar/schemas"; + +const router: Router = Router({ mergeParams: true }); + +router.post( + "/accept", + route({ + requestBody: "AllowlistAcceptSchema", + responses: { + 200: { + body: "AllowlistedUser", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: stub + const app = await Application.findOneOrFail({ + where: { id: req.params.id as string }, + relations: { bot: true, owner: true }, + select: { + owner: { id: true }, + }, + }); + + if (req.user_id != app.owner.id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + + const body = req.body as AllowlistAcceptSchema; + + return res.status(204).json({}); + }, +); + +export default router; diff --git a/src/schemas/api/developers/Application.ts b/src/schemas/api/developers/Application.ts index b2ccd1e46..62fc818b8 100644 --- a/src/schemas/api/developers/Application.ts +++ b/src/schemas/api/developers/Application.ts @@ -344,3 +344,20 @@ export enum EmbeddedActivityReleasePhase { SOFT_LAUNCH_MULTI_GEO = "soft_launch_multi_geo", GLOBAL_LAUNCH = "global_launch", } + +export type ApplicationAllowlistResponse = AllowlistedUser[]; +export interface AllowlistedUser { + user: PartialUser; + state: ApplicationMembershipState; +} +export enum ApplicationMembershipState { + INVITED = 1, + ACCEPTED = 2, +} +export interface AllowlistAddRequestSchema { + username: string; + discriminator?: string | null; +} +export interface AllowlistAcceptSchema { + token: string; +} diff --git a/src/util/config/types/subconfigurations/limits/ApplicationLimits.ts b/src/util/config/types/subconfigurations/limits/ApplicationLimits.ts index 52d492607..40d692ba9 100644 --- a/src/util/config/types/subconfigurations/limits/ApplicationLimits.ts +++ b/src/util/config/types/subconfigurations/limits/ApplicationLimits.ts @@ -18,6 +18,7 @@ export class ApplicationLimits { maxEmojis: number = 1000; + maxTesters: number = 50; commandLimits: CommandLimits = new CommandLimits(); }