From 98e50a6d57ebb48fd0b5fec148aa14aeaf95a1dd Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 21 Jul 2026 15:43:51 +0200 Subject: Move authentication enforcement to route middleware, move route middleware file --- src/api/util/handlers/route.ts | 175 ----------------------------------------- src/api/util/index.ts | 1 - 2 files changed, 176 deletions(-) delete mode 100644 src/api/util/handlers/route.ts (limited to 'src/api/util') diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts deleted file mode 100644 index c00a959d..00000000 --- a/src/api/util/handlers/route.ts +++ /dev/null @@ -1,175 +0,0 @@ -/* - 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 { DiscordApiErrors, EVENT, FieldErrors, PermissionResolvable, Permissions, RightResolvable, Rights, SpacebarApiErrors, getPermission, getRights } from "@spacebar/util"; -import { AnyValidateFunction } from "ajv/dist/core"; -import { NextFunction, Request, Response } from "express"; -import { ajv } from "@spacebar/schemas"; -import { BigNumber } from "bignumber.js"; - -const ignoredRequestSchemas = [ - // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix? - "SettingsProtoUpdateJsonSchema", -]; - -declare global { - // TODO: fix this - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - permission?: Permissions; - } - } -} - -export type RouteResponse = { - status?: number; - body?: `${string}Response`; - headers?: Record; -}; -export type stripNulls = { [key: string]: true | stripNulls }; -export interface RouteOptions { - permission?: PermissionResolvable; - right?: RightResolvable; - requestBody?: `${string}Schema`; // typescript interface name - responses?: { - [status: number]: { - // body?: `${string}Response`; - body?: string; - }; - }; - stripNulls?: stripNulls | true; - event?: EVENT | EVENT[]; - summary?: string; - description?: string; - query?: { - [key: string]: { - type: string; - required?: boolean; - description?: string; - values?: string[]; - }; - }; - deprecated?: boolean; - spacebarOnly?: boolean; - // test?: { - // response?: RouteResponse; - // body?: unknown; - // path?: string; - // event?: EVENT | EVENT[]; - // headers?: Record; - // }; -} -export function stripNull(obj: object) { - for (const [key, value] of Object.entries(obj)) { - if (value instanceof Object || (value && !value.__proto__)) { - stripNull(value); - } else if (value === null) { - //@ts-expect-error this is fine - delete obj[key]; - } - } -} -// eslint-disable-next-line -export function followNullPath(obj1: any, nullObj: stripNulls) { - for (const [key, value] of Object.entries(nullObj)) { - if (key in obj1) - if (value instanceof Object) { - if (obj1[key] instanceof Object) - //@ts-expect-error this works lol - followNullPath(obj1[key], nullObj[key]); - else delete obj1[key]; - } else if (obj1[key] instanceof Object) { - stripNull(obj1[key]); - } - } -} -//It's pretty safe to assume numbers over the number limit aren't really meant to be numbers, so we turn them to strings. -export function bigNumberToString(obj1: unknown) { - if (obj1 && typeof obj1 === "object") { - for (const [key, value] of Object.entries(obj1)) { - if (typeof value === "object") { - if (value instanceof BigNumber) { - //@ts-expect-error this is fine lol - obj1[key] = value.toString(); - } - bigNumberToString(value); - } - } - } -} -export function route(opts: RouteOptions) { - let validate: AnyValidateFunction | undefined; - if (opts.requestBody) { - try { - validate = ajv.getSchema(opts.requestBody); - } catch (e) { - console.error("AJV getSchema failed!"); - throw e; - } - - if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`); - } - - return async (req: Request, res: Response, next: NextFunction) => { - if (opts.permission) { - const { guild_id, channel_id } = req.params as { [key: string]: string }; - req.permission = await getPermission(req.user_id, guild_id, channel_id); - - const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission]; - requiredPerms.forEach((perm) => { - // bitfield comparison: check if user lacks certain permission - if (!req.permission!.has(new Permissions(perm))) { - throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string); - } - }); - } - - if (opts.right) { - const required = new Rights(opts.right); - req.rights = await getRights(req.user_id); - - if (!req.rights || !req.rights.has(required)) { - throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string); - } - } - bigNumberToString(req.body); - - if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) { - if (opts.stripNulls) { - if (opts.stripNulls === true) stripNull(req.body); - else followNullPath(req.body, opts.stripNulls); - } - - const valid = validate(req.body); - if (!valid) { - const fields: Record = {}; - validate.errors?.forEach( - (x) => - (fields[x.instancePath.slice(1)] = { - code: x.keyword, - message: x.message || "", - }), - ); - if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors); - throw FieldErrors(fields, validate.errors!); - } - } - next(); - }; -} diff --git a/src/api/util/index.ts b/src/api/util/index.ts index b57d263a..5fb5a684 100644 --- a/src/api/util/index.ts +++ b/src/api/util/index.ts @@ -19,7 +19,6 @@ export * from "./utility/ipAddress"; export * from "./handlers/Message"; export * from "./utility/passwordStrength"; -export * from "./handlers/route"; export * from "./handlers/Voice"; export * from "./utility/captcha"; export * from "./utility/EmbedHandlers"; -- cgit 1.5.1