-
-
Notifications
You must be signed in to change notification settings - Fork 250
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cb3e7a4
commit f760824
Showing
40 changed files
with
3,507 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
// } | ||
// } | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
Oops, something went wrong.