Skip to content

Commit

Permalink
Push routing attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
TheArcaneBrony committed Dec 26, 2024
1 parent cb3e7a4 commit f760824
Show file tree
Hide file tree
Showing 40 changed files with 3,507 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"ws": "^8.18.0"
},
"_moduleAliases": {
"@spacebar/admin-api": "dist/admin-api",
"@spacebar/api": "dist/api",
"@spacebar/cdn": "dist/cdn",
"@spacebar/gateway": "dist/gateway",
Expand Down
190 changes: 190 additions & 0 deletions src/admin-api/Server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
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 <https://www.gnu.org/licenses/>.
*/

import {
Config,
ConnectionLoader,
Email,
JSONReplacer,
Sentry,
initDatabase,
initEvent,
registerRoutes,
} from "@spacebar/util";
import { Request, Response, Router, IRoute, Application } from "express";
import { Server, ServerOptions } from "lambert-server";
import "missing-native-js-functions";
import morgan from "morgan";
import path from "path";
import { red } from "picocolors";
import { Authentication, CORS, ImageProxy } from "./middlewares/";
import { BodyParser } from "./middlewares/BodyParser";
import { ErrorHandler } from "./middlewares/ErrorHandler";
import { initRateLimits } from "./middlewares/RateLimit";
import { initTranslation } from "./middlewares/Translation";
import * as console from "node:console";
import fs from "fs/promises";
import { Dirent } from "node:fs";

const PUBLIC_ASSETS_FOLDER = path.join(
__dirname,
"..",
"..",
"assets",
"public",
);

export type SpacebarServerOptions = ServerOptions;

// declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
// namespace Express {
// interface Request {
// server: AdminApiServer;
// }
// }
// }

export class AdminApiServer extends Server {
public declare options: SpacebarServerOptions;

constructor(opts?: Partial<SpacebarServerOptions>) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
super({ ...opts, errorHandler: false, jsonBody: false });
}

async start() {
console.log("[AdminAPI] Starting...");
await initDatabase();
await Config.init();
await initEvent();
await Sentry.init(this.app);

const logRequests = process.env["LOG_REQUESTS"] != undefined;
if (logRequests) {
this.app.use(
morgan("combined", {
skip: (req, res) => {
let skip = !(
process.env["LOG_REQUESTS"]?.includes(
res.statusCode.toString(),
) ?? false
);
if (process.env["LOG_REQUESTS"]?.charAt(0) == "-")
skip = !skip;
return skip;
},
}),
);
}

this.app.set("json replacer", JSONReplacer);

const trustedProxies = Config.get().security.trustedProxies;
if (trustedProxies) this.app.set("trust proxy", trustedProxies);

this.app.use(CORS);
this.app.use(BodyParser({ inflate: true, limit: "10mb" }));

const app = this.app;
const api = Router();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.app = api;

// api.use(Authentication);
// await initRateLimits(api);
// await initTranslation(api);

// this.routes = await registerRoutes(
// this,
// path.join(__dirname, "routes", "/"),
// );

await this.registerControllers(app, path.join(__dirname, "routes", "/"));

// fall /v1/api back to /v0/api without redirect
app.use("/_spacebar/admin/:version/:path", (req, res) => {
console.log(req.params);
const versionNumber = req.params.version
.replace("v", "")
.toNumber();
const found = [];
for (let i = versionNumber; i >= 0; i--) {
// const oroutes = this.app._router.stack.filter(
// (x: IRoute) =>
// x.path == `/_spacebar/admin/v${i}/${req.params.path}`,
// );
const routes = this.routes.map(
(x: Router) =>
x.stack.filter(y =>
y.path == `/_spacebar/admin/v${i}/${req.params.path}`
),
).filter(x => x.length > 0);
console.log(i, routes);
found.push(...routes);
}
res.json({ versionNumber, routes: found });
});
// 404 is not an error in express, so this should not be an error middleware
// this is a fine place to put the 404 handler because its after we register the routes
// and since its not an error middleware, our error handler below still works.
api.use("*", (req: Request, res: Response) => {
res.status(404).json({
message: "404 endpoint not found",
code: 0,
});
});

this.app = app;

app.use("/_spacebar/admin/", api);

this.app.use(ErrorHandler);

Sentry.errorHandler(this.app);

ConnectionLoader.loadConnections();

if (logRequests)
console.log(
red(
`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`,
),
);

console.log("[AdminAPI] Listening...");
return super.start();
}

private async registerControllers(app: Application, root: string) {
// get files recursively
const fsEntries = (await fs.readdir(root, { withFileTypes: true }));
for (const file of fsEntries.filter(x=>x.isFile() && (x.name.endsWith(".js") || x.name.endsWith(".ts")))) {
const fullPath = path.join(file.parentPath, file.name);
const controller = require(fullPath);
console.log(fullPath, controller);

}

for (const dir of fsEntries.filter(x=>x.isDirectory())) {
await this.registerControllers(app, path.join(dir.parentPath, dir.name));
}
}
}
26 changes: 26 additions & 0 deletions src/admin-api/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
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 <https://www.gnu.org/licenses/>.
*/

// declare global {
// namespace Express {
// interface Request {
// user_id: any;
// token: any;
// }
// }
// }
21 changes: 21 additions & 0 deletions src/admin-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
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 <https://www.gnu.org/licenses/>.
*/

export * from "./Server";
export * from "./middlewares/";
export * from "./util/";
113 changes: 113 additions & 0 deletions src/admin-api/middlewares/Authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
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 <https://www.gnu.org/licenses/>.
*/

import * as Sentry from "@sentry/node";
import { checkToken, Rights } from "@spacebar/util";
import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server";

export const NO_AUTHORIZATION_ROUTES = [
// Authentication routes
"POST /auth/login",
"POST /auth/register",
"GET /auth/location-metadata",
"POST /auth/mfa/",
"POST /auth/verify",
"POST /auth/forgot",
"POST /auth/reset",
"GET /invites/",
// Routes with a seperate auth system
/^(POST|HEAD) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
// Public information endpoints
"GET /ping",
"GET /gateway",
"GET /experiments",
"GET /updates",
"GET /download",
"GET /scheduled-maintenances/upcoming.json",
// Public kubernetes integration
"GET /-/readyz",
"GET /-/healthz",
// Client analytics
"POST /science",
"POST /track",
// Public policy pages
"GET /policies/instance/",
// Oauth callback
"/oauth2/callback",
// Asset delivery
/^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/,
// Connections
/^(POST|HEAD) \/connections\/\w+\/callback/,
// Image proxy
/^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/,
];

export const API_PREFIX = /^\/api(\/v\d+)?/;
export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Express {
interface Request {
user_id: string;
user_bot: boolean;
token: { id: string; iat: number };
rights: Rights;
}
}
}

export async function Authentication(
req: Request,
res: Response,
next: NextFunction,
) {
if (req.method === "OPTIONS") return res.sendStatus(204);
const url = req.url.replace(API_PREFIX, "");
if (
NO_AUTHORIZATION_ROUTES.some((x) => {
if (req.method == "HEAD") {
if (typeof x === "string")
return url.startsWith(x.split(" ").slice(1).join(" "));
return x.test(req.method + " " + url);
}

if (typeof x === "string")
return (req.method + " " + url).startsWith(x);
return x.test(req.method + " " + url);
})
)
return next();
if (!req.headers.authorization)
return next(new HTTPError("Missing Authorization Header", 401));

Sentry.setUser({ id: req.user_id });

try {
const { decoded, user } = await checkToken(req.headers.authorization);

req.token = decoded;
req.user_id = decoded.id;
req.user_bot = user.bot;
req.rights = new Rights(Number(user.rights));
return next();
} catch (error) {
return next(new HTTPError(error!.toString(), 400));
}
}
Loading

0 comments on commit f760824

Please sign in to comment.