Move authentication enforcement to route middleware, move route middleware file

This commit is contained in:
Rory&
2026-07-21 15:43:51 +02:00
parent d64455b729
commit 98e50a6d57
4 changed files with 24 additions and 33 deletions
+14 -32
View File
@@ -20,7 +20,7 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server/HTTPError";
import { Session, User } from "@spacebar/database";
import { Random } from "@spacebar/extensions";
import { checkToken, Rights, UserTokenData } from "@spacebar/util";
import { checkToken, DiscordApiErrors, Rights, UserTokenData } from "@spacebar/util";
export const NO_AUTHORIZATION_ROUTES = [
// Authentication routes
@@ -77,6 +77,7 @@ declare global {
session?: Session;
rights: Rights;
fingerprint?: string;
isAuthenticated: boolean;
}
}
}
@@ -96,33 +97,16 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
`__sb_sessid=${(req.fingerprint = Random.getString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 32))}; Secure; HttpOnly; SameSite=None; Path=/`,
);
if (
NO_AUTHORIZATION_ROUTES.some((x) => {
if (typeof x !== "string") {
return x.test(req.method + " " + url);
}
await handleAuthentication(req);
const fullRoute = req.method + " " + url;
return next();
}
if (req.method === "HEAD") {
const urlPart = x.split(" ").slice(1).join(" ");
if (urlPart.endsWith("/")) {
return url.startsWith(urlPart);
} else {
return url === urlPart;
}
}
if (x.endsWith("/")) {
return fullRoute.startsWith(x);
} else {
return fullRoute === x;
}
})
)
return next();
if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
export async function handleAuthentication(req: Request) {
if (!req.headers.authorization) {
req.isAuthenticated = false;
return;
}
try {
const { decoded, user, session } = (req.tokenData = await checkToken(req.headers.authorization, {
@@ -136,11 +120,9 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
req.user = user;
req.session = session;
req.rights = new Rights(Number(user.rights));
return next();
} catch (error) {
if (error instanceof HTTPError) {
return next(error);
}
return next(new HTTPError(error!.toString(), 400));
req.isAuthenticated = true;
} catch (e) {
req.isAuthenticated = false;
console.error("[Authentication] Token was provided, but was invalid:", e);
}
}
@@ -74,6 +74,11 @@ export interface RouteOptions {
// event?: EVENT | EVENT[];
// headers?: Record<string, string>;
// };
/**
* @defaultValue "required"
*/
authentication?: "never" | "optional" | "required";
}
export function stripNull(obj: object) {
for (const [key, value] of Object.entries(obj)) {
@@ -126,7 +131,11 @@ export function route(opts: RouteOptions) {
if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`);
}
opts.authentication ??= "required";
return async (req: Request, res: Response, next: NextFunction) => {
if (opts.authentication === "required" && !req.isAuthenticated) throw DiscordApiErrors.UNAUTHORIZED;
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);
+1
View File
@@ -22,4 +22,5 @@ export * from "./CORS";
export * from "./ErrorHandler";
export * from "./ImageProxy";
export * from "./RateLimit";
export * from "./Route";
export * from "./Translation";
-1
View File
@@ -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";