Something went wrong. Try again.
General purpose moderation bot for Stoat. automod.vale.rocks
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128import type { Request, Response } from "express";import type { Collection } from "mongodb";import { db } from ".";import { botReq } from "./routes/internal/ws";
let _sessionsCollection: Collection | null = null;
async function sessionsCollection() { if (!_sessionsCollection) _sessionsCollection = (await db).collection("sessions"); return _sessionsCollection;}
class Session { user!: string; token!: string; nonce!: string; expires!: number; invalid!: boolean;}
/** * @param req * @returns false if not authenticated, otherwise the (Stoat) user ID */async function isAuthenticated(req: Request, res?: Response, send401?: boolean): Promise<string | false> { const user = req.header("x-auth-user"); const token = req.header("x-auth-token"); if (!user || !token) return false; const info = await getSessionInfo(user, token); if (res && send401 && !info.valid) { res.status(401).send({ error: "Unauthorized" }); } return info.valid ? user : false;}
type SessionInfo = { exists: boolean; valid: boolean; nonce?: string };
async function getSessionInfo(user: string, token: string): Promise<SessionInfo> { const session = await (await sessionsCollection()).findOne<Session>({ user, token });
return { exists: !!session, valid: !!(session && !session.invalid && session.expires > Date.now()), nonce: session?.nonce, };}
function badRequest(res: Response, infoText?: string) { res.status(400).send(JSON.stringify({ error: "Invalid request body", info: infoText || undefined }, null, 4));}
function unauthorized(res: Response, infoText?: string) { res.status(401).send(JSON.stringify({ error: "Unauthorized", info: infoText || undefined }, null, 4));}
function forbidden(res: Response, infoText?: string) { res.status(403).send(JSON.stringify({ error: infoText || "Forbidden" }, null, 4));}
async function getPermissionLevel(user: string, server: string) { return await botReq("getPermissionLevel", { user, server });}
type RequireAuthConfig = { permission?: 0 | 1 | 2 | 3; requireLogin?: boolean; noAuthOnly?: boolean };function requireAuth(config: RequireAuthConfig): (req: Request, res: Response, next: () => void) => void { return async (req: Request, res: Response, next: () => void) => { const auth = await isAuthenticated(req);
if (config.noAuthOnly && typeof auth == "string") return res.status(403).send({ error: "Cannot access this route with authentication" }); if (config.requireLogin && !auth) return unauthorized(res, "Authentication required for this route");
if (config.permission != undefined) { if (!auth) return unauthorized(res, "Authentication required for this route"); const server_id = req.params["serverid"] || req.params["server"]; const levelRes = await getPermissionLevel(auth, server_id); if (!levelRes.success) return res.status(500).send({ error: "Unknown server or other error" }); if (levelRes["level"] < config.permission) return forbidden(res, "Your permission level is too low"); }
next(); };}
/** * Strips the input object of unwanted fields and throws if a value has the wrong type * @param obj * @param structure */function ensureObjectStructure(obj: any, structure: { [key: string]: "string" | "number" | "float" | "strarray" }, allowEmpty?: boolean): any { const returnObj: any = {};
for (const key of Object.keys(obj)) { const type = obj[key] == null ? "null" : typeof obj[key];
if (allowEmpty && (type == "undefined" || type == "null")) continue;
switch (structure[key]) { case "string": case "number": case "float": if (type != structure[key]) throw `Property '${key}' was expected to be of type '${structure[key]}', got '${type}' instead`;
if (structure[key] == "number" && `${Math.round(obj[key])}` != `${obj[key]}`) throw `Property '${key}' was expected to be of type '${structure[key]}', got 'float' instead`;
returnObj[key] = obj[key]; break; case "strarray": if (!(obj[key] instanceof Array)) { throw `Property '${key}' was expected to be of type 'string[]', got '${type}' instead`; }
for (const i in obj[key]) { const item = obj[key][i]; if (typeof item != "string") throw `Property '${key}' was expected to be of type 'string[]', ` + `found '${typeof item}' at index ${i}`; }
returnObj[key] = obj[key]; break; default: continue; } }
return returnObj;}
export { isAuthenticated, getSessionInfo, badRequest, unauthorized, forbidden, getPermissionLevel, requireAuth, ensureObjectStructure };