diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 1df7911b..50048b12 100644
--- a/src/api/middlewares/Authentication.ts
+++ b/src/api/middlewares/Authentication.ts
@@ -10,7 +10,7 @@ export const NO_AUTHORIZATION_ROUTES = [
"/auth/mfa/totp",
// Routes with a seperate auth system
"/webhooks/",
- // Public information endpoints
+ // Public information endpoints
"/ping",
"/gateway",
"/experiments",
@@ -26,7 +26,7 @@ export const NO_AUTHORIZATION_ROUTES = [
// Public policy pages
"/policies/instance",
// Asset delivery
- /\/guilds\/\d+\/widget\.(json|png)/
+ /\/guilds\/\d+\/widget\.(json|png)/,
];
export const API_PREFIX = /^\/api(\/v\d+)?/;
@@ -43,7 +43,11 @@ declare global {
}
}
-export async function Authentication(req: Request, res: Response, next: NextFunction) {
+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 (url.startsWith("/invites") && req.method === "GET") return next();
@@ -54,12 +58,16 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
})
)
return next();
- if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
+ if (!req.headers.authorization)
+ return next(new HTTPError("Missing Authorization Header", 401));
try {
const { jwtSecret } = Config.get().security;
- const { decoded, user }: any = await checkToken(req.headers.authorization, jwtSecret);
+ const { decoded, user }: any = await checkToken(
+ req.headers.authorization,
+ jwtSecret,
+ );
req.token = decoded;
req.user_id = decoded.id;
diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index 4cb376bc..7741f1fd 100644
--- a/src/api/middlewares/BodyParser.ts
+++ b/src/api/middlewares/BodyParser.ts
@@ -6,7 +6,8 @@ export function BodyParser(opts?: OptionsJson) {
const jsonParser = bodyParser.json(opts);
return (req: Request, res: Response, next: NextFunction) => {
- if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
+ if (!req.headers["content-type"])
+ req.headers["content-type"] = "application/json";
jsonParser(req, res, (err) => {
if (err) {
diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 20260cf9..2dce51c6 100644
--- a/src/api/middlewares/CORS.ts
+++ b/src/api/middlewares/CORS.ts
@@ -7,10 +7,16 @@ export function CORS(req: Request, res: Response, next: NextFunction) {
// TODO: use better CSP
res.set(
"Content-security-policy",
- "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';"
+ "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
+ );
+ res.set(
+ "Access-Control-Allow-Headers",
+ req.header("Access-Control-Request-Headers") || "*",
+ );
+ res.set(
+ "Access-Control-Allow-Methods",
+ req.header("Access-Control-Request-Methods") || "*",
);
- res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
- res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*");
next();
}
diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index 2012b91c..bf3b011a 100644
--- a/src/api/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -3,7 +3,12 @@ import { HTTPError } from "lambert-server";
import { ApiError, FieldError } from "@fosscord/util";
const EntityNotFoundErrorRegex = /"(\w+)"/;
-export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) {
+export function ErrorHandler(
+ error: Error,
+ req: Request,
+ res: Response,
+ next: NextFunction,
+) {
if (!error) return next();
try {
@@ -12,20 +17,28 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
let message = error?.toString();
let errors = undefined;
- if (error instanceof HTTPError && error.code) code = httpcode = error.code;
+ if (error instanceof HTTPError && error.code)
+ code = httpcode = error.code;
else if (error instanceof ApiError) {
code = error.code;
message = error.message;
httpcode = error.httpStatus;
} else if (error.name === "EntityNotFoundError") {
- message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
+ message = `${
+ error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"
+ } could not be found`;
code = httpcode = 404;
} else if (error instanceof FieldError) {
code = Number(error.code);
message = error.message;
errors = error.errors;
} else {
- console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
+ console.error(
+ `[Error] ${code} ${req.url}\n`,
+ errors || error,
+ "\nbody:",
+ req.body,
+ );
if (req.server?.options?.production) {
// don't expose internal errors to the user, instead human errors should be thrown as HTTPError
@@ -39,6 +52,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
res.status(httpcode).json({ code: code, message, errors });
} catch (error) {
console.error(`[Internal Server Error] 500`, error);
- return res.status(500).json({ code: 500, message: "Internal Server Error" });
+ return res
+ .status(500)
+ .json({ code: 500, message: "Internal Server Error" });
}
}
diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 57645c0b..b3976a16 100644
--- a/src/api/middlewares/RateLimit.ts
+++ b/src/api/middlewares/RateLimit.ts
@@ -40,21 +40,32 @@ export default function rateLimit(opts: {
success?: boolean;
onlyIp?: boolean;
}): any {
- return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
+ return async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+ ): Promise<any> => {
// exempt user? if so, immediately short circuit
if (req.user_id) {
const rights = await getRights(req.user_id);
if (rights.has("BYPASS_RATE_LIMITS")) return next();
}
- const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
+ const bucket_id =
+ opts.bucket ||
+ req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
let executor_id = getIpAdress(req);
if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
let max_hits = opts.count;
if (opts.bot && req.user_bot) max_hits = opts.bot;
- if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
- else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
+ if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method))
+ max_hits = opts.GET;
+ else if (
+ opts.MODIFY &&
+ ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)
+ )
+ max_hits = opts.MODIFY;
let offender = Cache.get(executor_id + bucket_id);
@@ -75,11 +86,15 @@ export default function rateLimit(opts: {
const global = bucket_id === "global";
// each block violation pushes the expiry one full window further
reset += opts.window * 1000;
- offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
+ offender.expires_at = new Date(
+ offender.expires_at.getTime() + opts.window * 1000,
+ );
resetAfterMs = reset - Date.now();
resetAfterSec = Math.ceil(resetAfterMs / 1000);
- console.log(`blocked bucket: ${bucket_id} ${executor_id}`, { resetAfterMs });
+ console.log(`blocked bucket: ${bucket_id} ${executor_id}`, {
+ resetAfterMs,
+ });
return (
res
.status(429)
@@ -91,20 +106,33 @@ export default function rateLimit(opts: {
.set("Retry-After", `${Math.ceil(resetAfterSec)}`)
.set("X-RateLimit-Bucket", `${bucket_id}`)
// TODO: error rate limit message translation
- .send({ message: "You are being rate limited.", retry_after: resetAfterSec, global })
+ .send({
+ message: "You are being rate limited.",
+ retry_after: resetAfterSec,
+ global,
+ })
);
}
}
next();
- const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window };
+ const hitRouteOpts = {
+ bucket_id,
+ executor_id,
+ max_hits,
+ window: opts.window,
+ };
if (opts.error || opts.success) {
res.once("finish", () => {
// check if error and increment error rate limit
if (res.statusCode >= 400 && opts.error) {
return hitRoute(hitRouteOpts);
- } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
+ } else if (
+ res.statusCode >= 200 &&
+ res.statusCode < 300 &&
+ opts.success
+ ) {
return hitRoute(hitRouteOpts);
}
});
@@ -141,8 +169,8 @@ export async function initRateLimits(app: Router) {
rateLimit({
bucket: "global",
onlyIp: true,
- ...ip
- })
+ ...ip,
+ }),
);
app.use(rateLimit({ bucket: "global", ...global }));
app.use(
@@ -150,17 +178,25 @@ export async function initRateLimits(app: Router) {
bucket: "error",
error: true,
onlyIp: true,
- ...error
- })
+ ...error,
+ }),
);
app.use("/guilds/:id", rateLimit(routes.guild));
app.use("/webhooks/:id", rateLimit(routes.webhook));
app.use("/channels/:id", rateLimit(routes.channel));
app.use("/auth/login", rateLimit(routes.auth.login));
- app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
+ app.use(
+ "/auth/register",
+ rateLimit({ onlyIp: true, success: true, ...routes.auth.register }),
+ );
}
-async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number; }) {
+async function hitRoute(opts: {
+ executor_id: string;
+ bucket_id: string;
+ max_hits: number;
+ window: number;
+}) {
const id = opts.executor_id + opts.bucket_id;
let limit = Cache.get(id);
if (!limit) {
@@ -169,7 +205,7 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
executor_id: opts.executor_id,
expires_at: new Date(Date.now() + opts.window * 1000),
hits: 0,
- blocked: false
+ blocked: false,
};
Cache.set(id, limit);
}
@@ -205,4 +241,4 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
}
await ratelimit.save();
*/
-}
\ No newline at end of file
+}
diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index c0b7a4b8..05038040 100644
--- a/src/api/middlewares/Translation.ts
+++ b/src/api/middlewares/Translation.ts
@@ -9,8 +9,12 @@ const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets");
export async function initTranslation(router: Router) {
const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
- const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
- const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
+ const namespaces = fs.readdirSync(
+ path.join(ASSET_FOLDER_PATH, "locales", "en"),
+ );
+ const ns = namespaces
+ .filter((x) => x.endsWith(".json"))
+ .map((x) => x.slice(0, x.length - 5));
await i18next
.use(i18nextBackend)
@@ -21,9 +25,11 @@ export async function initTranslation(router: Router) {
fallbackLng: "en",
ns,
backend: {
- loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
+ loadPath:
+ path.join(ASSET_FOLDER_PATH, "locales") +
+ "/{{lng}}/{{ns}}.json",
},
- load: "all"
+ load: "all",
});
router.use(i18nextMiddleware.handle(i18next, {}));
|