1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-25 03:33:33 +01:00

Actually run prettier

This commit is contained in:
Emma [it/its]@Rory& 2023-12-11 01:12:54 +01:00
parent daeb6c3587
commit 0a8ceb9e63
250 changed files with 2905 additions and 6286 deletions

View File

@ -34,12 +34,7 @@ const addToDir = (dir) => {
const commentStrings = languageCommentStrings[fileType]; const commentStrings = languageCommentStrings[fileType];
if (!commentStrings) continue; if (!commentStrings) continue;
const preamble = const preamble = commentStrings[0] + "\n" + SPACEBAR_LICENSE_PREAMBLE + "\n" + commentStrings[1];
commentStrings[0] +
"\n" +
SPACEBAR_LICENSE_PREAMBLE +
"\n" +
commentStrings[1];
if (file.startsWith(preamble)) { if (file.startsWith(preamble)) {
continue; continue;

View File

@ -20,9 +20,7 @@ require("module-alias/register");
const getRouteDescriptions = require("./util/getRouteDescriptions"); const getRouteDescriptions = require("./util/getRouteDescriptions");
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
const { const { NO_AUTHORIZATION_ROUTES } = require("../dist/api/middlewares/Authentication");
NO_AUTHORIZATION_ROUTES,
} = require("../dist/api/middlewares/Authentication");
require("missing-native-js-functions"); require("missing-native-js-functions");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
@ -35,8 +33,7 @@ let specification = {
openapi: "3.1.0", openapi: "3.1.0",
info: { info: {
title: "Spacebar Server", title: "Spacebar Server",
description: description: "Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
license: { license: {
name: "AGPLV3", name: "AGPLV3",
url: "https://www.gnu.org/licenses/agpl-3.0.en.html", url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
@ -90,8 +87,7 @@ function combineSchemas(schemas) {
continue; continue;
} }
specification.components = specification.components || {}; specification.components = specification.components || {};
specification.components.schemas = specification.components.schemas = specification.components.schemas || {};
specification.components.schemas || {};
specification.components.schemas[key] = definitions[key]; specification.components.schemas[key] = definitions[key];
delete definitions[key].additionalProperties; delete definitions[key].additionalProperties;
delete definitions[key].$schema; delete definitions[key].$schema;
@ -170,8 +166,7 @@ function apiRoutes() {
[k]: { [k]: {
...(v.body ...(v.body
? { ? {
description: description: obj?.responses?.[k]?.description || "",
obj?.responses?.[k]?.description || "",
content: { content: {
"application/json": { "application/json": {
schema: schema, schema: schema,
@ -218,12 +213,9 @@ function apiRoutes() {
obj.tags = [...(obj.tags || []), getTag(p)].unique(); obj.tags = [...(obj.tags || []), getTag(p)].unique();
specification.paths[path] = Object.assign( specification.paths[path] = Object.assign(specification.paths[path] || {}, {
specification.paths[path] || {}, [method]: obj,
{ });
[method]: obj,
},
);
}); });
} }
@ -236,7 +228,7 @@ function main() {
openapiPath, openapiPath,
JSON.stringify(specification, null, 4) JSON.stringify(specification, null, 4)
.replaceAll("#/definitions", "#/components/schemas") .replaceAll("#/definitions", "#/components/schemas")
.replaceAll("bigint", "number"), .replaceAll("bigint", "number")
); );
} }

View File

@ -62,18 +62,13 @@ const Excluded = [
function main() { function main() {
const program = TJS.programFromConfig( const program = TJS.programFromConfig(
path.join(__dirname, "..", "tsconfig.json"), path.join(__dirname, "..", "tsconfig.json"),
walk(path.join(__dirname, "..", "src", "util", "schemas")), walk(path.join(__dirname, "..", "src", "util", "schemas"))
); );
const generator = TJS.buildGenerator(program, settings); const generator = TJS.buildGenerator(program, settings);
if (!generator || !program) return; if (!generator || !program) return;
let schemas = generator.getUserSymbols().filter((x) => { let schemas = generator.getUserSymbols().filter((x) => {
return ( return (x.endsWith("Schema") || x.endsWith("Response") || x.startsWith("API")) && !Excluded.includes(x);
(x.endsWith("Schema") ||
x.endsWith("Response") ||
x.startsWith("API")) &&
!Excluded.includes(x)
);
}); });
var definitions = {}; var definitions = {};

View File

@ -22,15 +22,7 @@ const path = require("path");
(async () => { (async () => {
DataSourceOptions.setOptions({ DataSourceOptions.setOptions({
logging: true, logging: true,
migrations: [ migrations: [path.join(process.cwd(), "scripts", "stagingMigration", DatabaseType, "*.js")],
path.join(
process.cwd(),
"scripts",
"stagingMigration",
DatabaseType,
"*.js",
),
],
}); });
const dbConnection = await DataSourceOptions.initialize(); const dbConnection = await DataSourceOptions.initialize();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,148 +22,74 @@ module.exports = class staging1672815835837 {
name = "staging1672815835837"; name = "staging1672815835837";
async up(queryRunner) { async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`);
await queryRunner.query(`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`);
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`, `ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`
); );
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`, `CREATE TABLE "embed_cache" ("id" character varying NOT NULL, "url" character varying NOT NULL, "embed" text NOT NULL, CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY ("id"))`
); );
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`, `CREATE TABLE "security_settings" ("id" character varying NOT NULL, "guild_id" character varying, "channel_id" character varying, "encryption_permission_mask" integer NOT NULL, "allowed_algorithms" text NOT NULL, "current_algorithm" character varying NOT NULL, "used_since_message" character varying, CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY ("id"))`
);
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`);
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`);
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`);
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`);
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`);
await queryRunner.query(`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`);
await queryRunner.query(`ALTER TABLE "client_release" ADD "enabled" boolean NOT NULL`);
await queryRunner.query(`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "users" ADD "settingsIndex" integer`);
await queryRunner.query(
`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`
);
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`);
await queryRunner.query(`ALTER TABLE "client_release" ADD "pub_date" TIMESTAMP NOT NULL`);
await queryRunner.query(`UPDATE channels SET nsfw = false WHERE nsfw IS NULL`);
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`);
await queryRunner.query(`UPDATE channels SET flags = 0 WHERE flags IS NULL`);
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "flags" SET NOT NULL`);
await queryRunner.query(
`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`
); );
await queryRunner.query( await queryRunner.query(
`CREATE TABLE "embed_cache" ("id" character varying NOT NULL, "url" character varying NOT NULL, "embed" text NOT NULL, CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY ("id"))`, `ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`
); );
await queryRunner.query( await queryRunner.query(
`CREATE TABLE "security_settings" ("id" character varying NOT NULL, "guild_id" character varying, "channel_id" character varying, "encryption_permission_mask" integer NOT NULL, "allowed_algorithms" text NOT NULL, "current_algorithm" character varying NOT NULL, "used_since_message" character varying, CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY ("id"))`, `ALTER TABLE "user_settings" DROP CONSTRAINT IF EXISTS "PK_e81f8bb92802737337d35c00981"`
); );
await queryRunner.query(`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`);
await queryRunner.query(`ALTER TABLE "user_settings" ADD "index" SERIAL NOT NULL`);
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`, `ALTER TABLE "user_settings" ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY ("index")`
);
await queryRunner.query(
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`,
);
await queryRunner.query(
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`,
);
await queryRunner.query(
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`,
);
await queryRunner.query(
`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`,
);
await queryRunner.query(
`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "client_release" ADD "enabled" boolean NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`,
);
await queryRunner.query(
`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`,
);
await queryRunner.query(
`ALTER TABLE "users" ADD "settingsIndex" integer`,
);
await queryRunner.query(
`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`,
);
await queryRunner.query(
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`,
);
await queryRunner.query(
`ALTER TABLE "client_release" ADD "pub_date" TIMESTAMP NOT NULL`,
);
await queryRunner.query(
`UPDATE channels SET nsfw = false WHERE nsfw IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`,
);
await queryRunner.query(
`UPDATE channels SET flags = 0 WHERE flags IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "channels" ALTER COLUMN "flags" SET NOT NULL`,
);
await queryRunner.query(
`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "user_settings" DROP CONSTRAINT IF EXISTS "PK_e81f8bb92802737337d35c00981"`,
);
await queryRunner.query(
`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`,
);
await queryRunner.query(
`ALTER TABLE "user_settings" ADD "index" SERIAL NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "user_settings" ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY ("index")`,
);
await queryRunner.query(
`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`,
);
await queryRunner.query(
`ALTER TABLE "guilds" ADD "primary_category_id" character varying`,
);
await queryRunner.query(
`UPDATE guilds SET large = false WHERE large IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`,
);
await queryRunner.query(
`UPDATE guilds SET premium_tier = 0 WHERE premium_tier IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "guilds" ALTER COLUMN "premium_tier" SET NOT NULL`,
);
await queryRunner.query(
`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "guilds" ALTER COLUMN "unavailable" SET NOT NULL`,
);
await queryRunner.query(
`UPDATE guilds SET widget_enabled = false WHERE widget_enabled IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`,
);
await queryRunner.query(
`UPDATE guilds SET nsfw = false WHERE nsfw IS NULL`,
);
await queryRunner.query(
`ALTER TABLE "guilds" ALTER COLUMN "nsfw" SET NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`,
);
await queryRunner.query(
`ALTER TABLE "members" ADD "premium_since" bigint`,
); );
await queryRunner.query(`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`);
await queryRunner.query(`ALTER TABLE "guilds" ADD "primary_category_id" character varying`);
await queryRunner.query(`UPDATE guilds SET large = false WHERE large IS NULL`);
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`);
await queryRunner.query(`UPDATE guilds SET premium_tier = 0 WHERE premium_tier IS NULL`);
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "premium_tier" SET NOT NULL`);
await queryRunner.query(`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`);
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "unavailable" SET NOT NULL`);
await queryRunner.query(`UPDATE guilds SET widget_enabled = false WHERE widget_enabled IS NULL`);
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`);
await queryRunner.query(`UPDATE guilds SET nsfw = false WHERE nsfw IS NULL`);
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "nsfw" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`);
await queryRunner.query(`ALTER TABLE "members" ADD "premium_since" bigint`);
await queryRunner.query(`ALTER TABLE members ADD theme_colors text`); await queryRunner.query(`ALTER TABLE members ADD theme_colors text`);
await queryRunner.query(`ALTER TABLE members ADD pronouns varchar`); await queryRunner.query(`ALTER TABLE members ADD pronouns varchar`);
await queryRunner.query(`UPDATE users SET bio = '' WHERE bio IS NULL`); await queryRunner.query(`UPDATE users SET bio = '' WHERE bio IS NULL`);
await queryRunner.query( await queryRunner.query(`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`);
`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`,
);
await queryRunner.query(`ALTER TABLE users ADD theme_colors text`); await queryRunner.query(`ALTER TABLE users ADD theme_colors text`);
await queryRunner.query(`ALTER TABLE users ADD pronouns varchar`); await queryRunner.query(`ALTER TABLE users ADD pronouns varchar`);
await queryRunner.query(`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`);
await queryRunner.query(`ALTER TABLE users ALTER COLUMN mfa_enabled SET NOT NULL`);
await queryRunner.query( await queryRunner.query(
`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`, `ALTER TABLE "users" ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES "user_settings"("index") ON DELETE NO ACTION ON UPDATE NO ACTION`
);
await queryRunner.query(
`ALTER TABLE users ALTER COLUMN mfa_enabled SET NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "users" ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES "user_settings"("index") ON DELETE NO ACTION ON UPDATE NO ACTION`,
); );
} }

View File

@ -25,7 +25,7 @@ const doTimedIdentify = () =>
token: TOKEN, token: TOKEN,
properties: {}, properties: {},
}, },
}), })
); );
break; break;
case OPCODES.Dispatch: case OPCODES.Dispatch:

View File

@ -24,9 +24,7 @@
const { spawn } = require("child_process"); const { spawn } = require("child_process");
const path = require("path"); const path = require("path");
const server = spawn("node", [ const server = spawn("node", [path.join(__dirname, "..", "dist", "bundle", "start.js")]);
path.join(__dirname, "..", "dist", "bundle", "start.js"),
]);
server.stdout.on("data", (data) => { server.stdout.on("data", (data) => {
process.stdout.write(data); process.stdout.write(data);

View File

@ -15,10 +15,7 @@ let currentPath = "";
const proxy = (file, method, prefix, path, ...args) => { const proxy = (file, method, prefix, path, ...args) => {
const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true); const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
if (!opts) if (!opts) return console.error(`${file} has route without route() description middleware`);
return console.error(
`${file} has route without route() description middleware`,
);
console.log(prefix + path + " - " + method); console.log(prefix + path + " - " + method);
opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts"); opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
@ -26,12 +23,7 @@ const proxy = (file, method, prefix, path, ...args) => {
}; };
express.Router = () => { express.Router = () => {
return Object.fromEntries( return Object.fromEntries(methods.map((method) => [method, proxy.bind(null, currentFile, method, currentPath)]));
methods.map((method) => [
method,
proxy.bind(null, currentFile, method, currentPath),
]),
);
}; };
RouteUtility.route = (opts) => { RouteUtility.route = (opts) => {
@ -50,8 +42,7 @@ module.exports = function getRouteDescriptions() {
currentPath = file.replace(root.slice(0, -1), ""); currentPath = file.replace(root.slice(0, -1), "");
currentPath = currentPath.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path currentPath = currentPath.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
if (currentPath.endsWith("/index")) if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
try { try {
require(file); require(file);

View File

@ -41,13 +41,7 @@ import { initRateLimits } from "./middlewares/RateLimit";
import { initTranslation } from "./middlewares/Translation"; import { initTranslation } from "./middlewares/Translation";
import { initInstance } from "./util/handlers/Instance"; import { initInstance } from "./util/handlers/Instance";
const PUBLIC_ASSETS_FOLDER = path.join( const PUBLIC_ASSETS_FOLDER = path.join(__dirname, "..", "..", "assets", "public");
__dirname,
"..",
"..",
"assets",
"public",
);
export type SpacebarServerOptions = ServerOptions; export type SpacebarServerOptions = ServerOptions;
@ -84,16 +78,11 @@ export class SpacebarServer extends Server {
this.app.use( this.app.use(
morgan("combined", { morgan("combined", {
skip: (req, res) => { skip: (req, res) => {
let skip = !( let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
process.env["LOG_REQUESTS"]?.includes( if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
res.statusCode.toString(),
) ?? false
);
if (process.env["LOG_REQUESTS"]?.charAt(0) == "-")
skip = !skip;
return skip; return skip;
}, },
}), })
); );
} }
@ -112,10 +101,7 @@ export class SpacebarServer extends Server {
await initRateLimits(api); await initRateLimits(api);
await initTranslation(api); await initTranslation(api);
this.routes = await registerRoutes( this.routes = await registerRoutes(this, path.join(__dirname, "routes", "/"));
this,
path.join(__dirname, "routes", "/"),
);
// 404 is not an error in express, so this should not be an error middleware // 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 // this is a fine place to put the 404 handler because its after we register the routes
@ -137,9 +123,7 @@ export class SpacebarServer extends Server {
app.use("/api/v9", api); app.use("/api/v9", api);
app.use("/api", api); // allow unversioned requests app.use("/api", api); // allow unversioned requests
app.get("/", (req, res) => app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")),
);
this.app.use(ErrorHandler); this.app.use(ErrorHandler);
@ -150,8 +134,8 @@ export class SpacebarServer extends Server {
if (logRequests) if (logRequests)
console.log( console.log(
red( red(
`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`, `Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`
), )
); );
return super.start(); return super.start();

View File

@ -71,11 +71,7 @@ declare global {
} }
} }
export async function Authentication( export async function Authentication(req: Request, res: Response, next: NextFunction) {
req: Request,
res: Response,
next: NextFunction,
) {
if (req.method === "OPTIONS") return res.sendStatus(204); if (req.method === "OPTIONS") return res.sendStatus(204);
const url = req.url.replace(API_PREFIX, ""); const url = req.url.replace(API_PREFIX, "");
if (url.startsWith("/invites") && req.method === "GET") return next(); if (url.startsWith("/invites") && req.method === "GET") return next();
@ -86,8 +82,7 @@ export async function Authentication(
}) })
) )
return next(); return next();
if (!req.headers.authorization) if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
return next(new HTTPError("Missing Authorization Header", 401));
Sentry.setUser({ id: req.user_id }); Sentry.setUser({ id: req.user_id });

View File

@ -24,8 +24,7 @@ export function BodyParser(opts?: OptionsJson) {
const jsonParser = bodyParser.json(opts); const jsonParser = bodyParser.json(opts);
return (req: Request, res: Response, next: NextFunction) => { return (req: Request, res: Response, next: NextFunction) => {
if (!req.headers["content-type"]) if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
req.headers["content-type"] = "application/json";
jsonParser(req, res, (err) => { jsonParser(req, res, (err) => {
if (err) { if (err) {

View File

@ -25,16 +25,10 @@ export function CORS(req: Request, res: Response, next: NextFunction) {
// TODO: use better CSP // TODO: use better CSP
res.set( res.set(
"Content-security-policy", "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(); next();
} }

View File

@ -21,12 +21,7 @@ import { HTTPError } from "lambert-server";
import { ApiError, FieldError } from "@spacebar/util"; import { ApiError, FieldError } from "@spacebar/util";
const EntityNotFoundErrorRegex = /"(\w+)"/; const EntityNotFoundErrorRegex = /"(\w+)"/;
export function ErrorHandler( export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) {
error: Error & { type?: string },
req: Request,
res: Response,
next: NextFunction,
) {
if (!error) return next(); if (!error) return next();
try { try {
@ -35,16 +30,13 @@ export function ErrorHandler(
let message = error?.toString(); let message = error?.toString();
let errors = undefined; let errors = undefined;
if (error instanceof HTTPError && error.code) if (error instanceof HTTPError && error.code) code = httpcode = error.code;
code = httpcode = error.code;
else if (error instanceof ApiError) { else if (error instanceof ApiError) {
code = error.code; code = error.code;
message = error.message; message = error.message;
httpcode = error.httpStatus; httpcode = error.httpStatus;
} else if (error.name === "EntityNotFoundError") { } else if (error.name === "EntityNotFoundError") {
message = `${ message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"
} could not be found`;
code = httpcode = 404; code = httpcode = 404;
} else if (error instanceof FieldError) { } else if (error instanceof FieldError) {
code = Number(error.code); code = Number(error.code);
@ -56,12 +48,7 @@ export function ErrorHandler(
code = 50109; code = 50109;
message = "The request body contains invalid JSON."; message = "The request body contains invalid JSON.";
} else { } else {
console.error( console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
`[Error] ${code} ${req.url}\n`,
errors || error,
"\nbody:",
req.body,
);
if (req.server?.options?.production) { if (req.server?.options?.production) {
// don't expose internal errors to the user, instead human errors should be thrown as HTTPError // don't expose internal errors to the user, instead human errors should be thrown as HTTPError
@ -75,8 +62,6 @@ export function ErrorHandler(
res.status(httpcode).json({ code: code, message, errors }); res.status(httpcode).json({ code: code, message, errors });
} catch (error) { } catch (error) {
console.error(`[Internal Server Error] 500`, error); console.error(`[Internal Server Error] 500`, error);
return res return res.status(500).json({ code: 500, message: "Internal Server Error" });
.status(500)
.json({ code: 500, message: "Internal Server Error" });
} }
} }

View File

@ -65,21 +65,14 @@ export default function rateLimit(opts: {
if (rights.has("BYPASS_RATE_LIMITS")) return next(); if (rights.has("BYPASS_RATE_LIMITS")) return next();
} }
const bucket_id = const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
opts.bucket ||
req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
let executor_id = getIpAdress(req); let executor_id = getIpAdress(req);
if (!opts.onlyIp && req.user_id) executor_id = req.user_id; if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
let max_hits = opts.count; let max_hits = opts.count;
if (opts.bot && req.user_bot) max_hits = opts.bot; if (opts.bot && req.user_bot) max_hits = opts.bot;
if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
max_hits = opts.GET; else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
else if (
opts.MODIFY &&
["POST", "DELETE", "PATCH", "PUT"].includes(req.method)
)
max_hits = opts.MODIFY;
const offender = Cache.get(executor_id + bucket_id); const offender = Cache.get(executor_id + bucket_id);
@ -104,18 +97,13 @@ export default function rateLimit(opts: {
} }
res.set("X-RateLimit-Reset", `${reset}`); res.set("X-RateLimit-Reset", `${reset}`);
res.set( res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`);
"X-RateLimit-Reset-After",
`${Math.max(0, Math.ceil(resetAfterSec))}`,
);
if (offender.blocked) { if (offender.blocked) {
const global = bucket_id === "global"; const global = bucket_id === "global";
// each block violation pushes the expiry one full window further // each block violation pushes the expiry one full window further
reset += opts.window * 1000; reset += opts.window * 1000;
offender.expires_at = new Date( offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
offender.expires_at.getTime() + opts.window * 1000,
);
resetAfterMs = reset - Date.now(); resetAfterMs = reset - Date.now();
resetAfterSec = Math.ceil(resetAfterMs / 1000); resetAfterSec = Math.ceil(resetAfterMs / 1000);
@ -129,10 +117,7 @@ export default function rateLimit(opts: {
res res
.status(429) .status(429)
.set("X-RateLimit-Remaining", "0") .set("X-RateLimit-Remaining", "0")
.set( .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
"Retry-After",
`${Math.max(0, Math.ceil(resetAfterSec))}`,
)
// TODO: error rate limit message translation // TODO: error rate limit message translation
.send({ .send({
message: "You are being rate limited.", message: "You are being rate limited.",
@ -156,11 +141,7 @@ export default function rateLimit(opts: {
// check if error and increment error rate limit // check if error and increment error rate limit
if (res.statusCode >= 400 && opts.error) { if (res.statusCode >= 400 && opts.error) {
return hitRoute(hitRouteOpts); return hitRoute(hitRouteOpts);
} else if ( } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
res.statusCode >= 200 &&
res.statusCode < 300 &&
opts.success
) {
return hitRoute(hitRouteOpts); return hitRoute(hitRouteOpts);
} }
}); });
@ -198,7 +179,7 @@ export async function initRateLimits(app: Router) {
bucket: "global", bucket: "global",
onlyIp: true, onlyIp: true,
...ip, ...ip,
}), })
); );
app.use(rateLimit({ bucket: "global", ...global })); app.use(rateLimit({ bucket: "global", ...global }));
app.use( app.use(
@ -207,24 +188,16 @@ export async function initRateLimits(app: Router) {
error: true, error: true,
onlyIp: true, onlyIp: true,
...error, ...error,
}), })
); );
app.use("/guilds/:id", rateLimit(routes.guild)); app.use("/guilds/:id", rateLimit(routes.guild));
app.use("/webhooks/:id", rateLimit(routes.webhook)); app.use("/webhooks/:id", rateLimit(routes.webhook));
app.use("/channels/:id", rateLimit(routes.channel)); app.use("/channels/:id", rateLimit(routes.channel));
app.use("/auth/login", rateLimit(routes.auth.login)); app.use("/auth/login", rateLimit(routes.auth.login));
app.use( app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
"/auth/register",
rateLimit({ onlyIp: true, success: true, ...routes.auth.register }),
);
} }
async function hitRoute(opts: { async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) {
executor_id: string;
bucket_id: string;
max_hits: number;
window: number;
}) {
const id = opts.executor_id + opts.bucket_id; const id = opts.executor_id + opts.bucket_id;
let limit = Cache.get(id); let limit = Cache.get(id);
if (!limit) { if (!limit) {

View File

@ -27,12 +27,8 @@ const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets");
export async function initTranslation(router: Router) { export async function initTranslation(router: Router) {
const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales")); const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
const namespaces = fs.readdirSync( const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
path.join(ASSET_FOLDER_PATH, "locales", "en"), const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
);
const ns = namespaces
.filter((x) => x.endsWith(".json"))
.map((x) => x.slice(0, x.length - 5));
await i18next await i18next
.use(i18nextBackend) .use(i18nextBackend)
@ -43,9 +39,7 @@ export async function initTranslation(router: Router) {
fallbackLng: "en", fallbackLng: "en",
ns, ns,
backend: { backend: {
loadPath: loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
path.join(ASSET_FOLDER_PATH, "locales") +
"/{{lng}}/{{ns}}.json",
}, },
load: "all", load: "all",
}); });

View File

@ -50,15 +50,14 @@ router.post(
relations: ["owner"], relations: ["owner"],
}); });
if (app.owner.id != req.user_id) if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
const user = await createAppBotUser(app, req); const user = await createAppBotUser(app, req);
res.send({ res.send({
token: await generateToken(user.id), token: await generateToken(user.id),
}).status(204); }).status(204);
}, }
); );
router.post( router.post(
@ -77,13 +76,9 @@ router.post(
const bot = await User.findOneOrFail({ where: { id: req.params.id } }); const bot = await User.findOneOrFail({ where: { id: req.params.id } });
const owner = await User.findOneOrFail({ where: { id: req.user_id } }); const owner = await User.findOneOrFail({ where: { id: req.user_id } });
if (owner.id != req.user_id) if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
if ( if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code)))
owner.totp_secret &&
(!req.body.code || verifyToken(owner.totp_secret, req.body.code))
)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
bot.data = { hash: undefined, valid_tokens_since: new Date() }; bot.data = { hash: undefined, valid_tokens_since: new Date() };
@ -93,7 +88,7 @@ router.post(
const token = await generateToken(bot.id); const token = await generateToken(bot.id);
res.json({ token }).status(200); res.json({ token }).status(200);
}, }
); );
router.patch( router.patch(
@ -120,14 +115,9 @@ router.patch(
if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
if (app.owner.id != req.user_id) if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
if (body.avatar) if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string);
body.avatar = await handleFile(
`/avatars/${app.id}`,
body.avatar as string,
);
app.bot.assign(body); app.bot.assign(body);
@ -135,7 +125,7 @@ router.patch(
await app.save(); await app.save();
res.json(app).status(200); res.json(app).status(200);
}, }
); );
export default router; export default router;

View File

@ -34,7 +34,7 @@ router.get(
// TODO: // TODO:
//const { exclude_consumed } = req.query; //const { exclude_consumed } = req.query;
res.status(200).send([]); res.status(200).send([]);
}, }
); );
export default router; export default router;

View File

@ -17,11 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Application, ApplicationModifySchema, DiscordApiErrors } from "@spacebar/util";
Application,
ApplicationModifySchema,
DiscordApiErrors,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa"; import { verifyToken } from "node-2fa";
@ -45,11 +41,10 @@ router.get(
where: { id: req.params.id }, where: { id: req.params.id },
relations: ["owner", "bot"], relations: ["owner", "bot"],
}); });
if (app.owner.id != req.user_id) if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
return res.json(app); return res.json(app);
}, }
); );
router.patch( router.patch(
@ -73,14 +68,9 @@ router.patch(
relations: ["owner", "bot"], relations: ["owner", "bot"],
}); });
if (app.owner.id != req.user_id) if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
if ( if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code)))
app.owner.totp_secret &&
(!req.body.code ||
verifyToken(app.owner.totp_secret, req.body.code))
)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
if (app.bot) { if (app.bot) {
@ -93,7 +83,7 @@ router.patch(
await app.save(); await app.save();
return res.json(app); return res.json(app);
}, }
); );
router.post( router.post(
@ -111,20 +101,15 @@ router.post(
where: { id: req.params.id }, where: { id: req.params.id },
relations: ["bot", "owner"], relations: ["bot", "owner"],
}); });
if (app.owner.id != req.user_id) if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
if ( if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code)))
app.owner.totp_secret &&
(!req.body.code ||
verifyToken(app.owner.totp_secret, req.body.code))
)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
await Application.delete({ id: app.id }); await Application.delete({ id: app.id });
res.send().status(200); res.send().status(200);
}, }
); );
export default router; export default router;

View File

@ -32,7 +32,7 @@ router.get(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
res.json([]).status(200); res.json([]).status(200);
}, }
); );
export default router; export default router;

View File

@ -33,7 +33,7 @@ router.get(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
//TODO //TODO
res.send([]).status(200); res.send([]).status(200);
}, }
); );
export default router; export default router;

View File

@ -17,14 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Application, ApplicationCreateSchema, Config, User, createAppBotUser, trimSpecial } from "@spacebar/util";
Application,
ApplicationCreateSchema,
Config,
User,
createAppBotUser,
trimSpecial,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
@ -44,7 +37,7 @@ router.get(
relations: ["owner", "bot"], relations: ["owner", "bot"],
}); });
res.json(results).status(200); res.json(results).status(200);
}, }
); );
router.post( router.post(
@ -77,7 +70,7 @@ router.post(
} else await app.save(); } else await app.save();
res.json(app); res.json(app);
}, }
); );
export default router; export default router;

View File

@ -17,13 +17,7 @@
*/ */
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
import { import { Config, Email, FieldErrors, ForgotPasswordSchema, User } from "@spacebar/util";
Config,
Email,
FieldErrors,
ForgotPasswordSchema,
User,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
const router = Router(); const router = Router();
@ -47,10 +41,7 @@ router.post(
const config = Config.get(); const config = Config.get();
if ( if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
config.passwordReset.requireCaptcha &&
config.security.captcha.enabled
) {
const { sitekey, service } = config.security.captcha; const { sitekey, service } = config.security.captcha;
if (!captcha_key) { if (!captcha_key) {
return res.status(400).json({ return res.status(400).json({
@ -87,8 +78,7 @@ router.post(
if (!user.email) if (!user.email)
throw FieldErrors({ throw FieldErrors({
login: { login: {
message: message: "This account does not have an email address associated with it.",
"This account does not have an email address associated with it.",
code: "NO_EMAIL", code: "NO_EMAIL",
}, },
}); });
@ -110,12 +100,10 @@ router.post(
return res.sendStatus(204); return res.sendStatus(204);
}) })
.catch((e) => { .catch((e) => {
console.error( console.error(`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`);
`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`,
);
throw new HTTPError("Failed to send password reset email", 500); throw new HTTPError("Failed to send password reset email", 500);
}); });
}, }
); );
export default router; export default router;

View File

@ -29,13 +29,11 @@ router.get(
query: { query: {
count: { count: {
type: "number", type: "number",
description: description: "The number of registration tokens to generate. Defaults to 1.",
"The number of registration tokens to generate. Defaults to 1.",
}, },
length: { length: {
type: "number", type: "number",
description: description: "The length of each registration token. Defaults to 255.",
"The length of each registration token. Defaults to 255.",
}, },
}, },
right: "OPERATOR", right: "OPERATOR",
@ -43,18 +41,14 @@ router.get(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const count = req.query.count ? parseInt(req.query.count as string) : 1; const count = req.query.count ? parseInt(req.query.count as string) : 1;
const length = req.query.length const length = req.query.length ? parseInt(req.query.length as string) : 255;
? parseInt(req.query.length as string)
: 255;
const tokens: ValidRegistrationToken[] = []; const tokens: ValidRegistrationToken[] = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const token = ValidRegistrationToken.create({ const token = ValidRegistrationToken.create({
token: random(length), token: random(length),
expires_at: expires_at: Date.now() + Config.get().security.defaultRegistrationTokenExpiration,
Date.now() +
Config.get().security.defaultRegistrationTokenExpiration,
}); });
tokens.push(token); tokens.push(token);
} }
@ -67,16 +61,11 @@ router.get(
}); });
const ret = req.query.include_url const ret = req.query.include_url
? tokens.map( ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`)
(x) =>
`${Config.get().general.frontPage}/register?token=${
x.token
}`,
)
: tokens.map((x) => x.token); : tokens.map((x) => x.token);
if (req.query.plain) return res.send(ret.join("\n")); if (req.query.plain) return res.send(ret.join("\n"));
return res.json({ tokens: ret }); return res.json({ tokens: ret });
}, }
); );

View File

@ -38,7 +38,7 @@ router.get(
country_code: country_code, country_code: country_code,
promotional_email_opt_in: { required: true, pre_checked: false }, promotional_email_opt_in: { required: true, pre_checked: false },
}); });
}, }
); );
export default router; export default router;

View File

@ -47,8 +47,7 @@ router.post(
}, },
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { login, password, captcha_key, undelete } = const { login, password, captcha_key, undelete } = req.body as LoginSchema;
req.body as LoginSchema;
const config = Config.get(); const config = Config.get();
@ -101,10 +100,7 @@ router.post(
}); });
// the salt is saved in the password refer to bcrypt docs // the salt is saved in the password refer to bcrypt docs
const same_password = await bcrypt.compare( const same_password = await bcrypt.compare(password, user.data.hash || "");
password,
user.data.hash || "",
);
if (!same_password) { if (!same_password) {
throw FieldErrors({ throw FieldErrors({
login: { login: {
@ -123,8 +119,7 @@ router.post(
throw FieldErrors({ throw FieldErrors({
login: { login: {
code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
message: message: "Email verification is required, please check your email.",
"Email verification is required, please check your email.",
}, },
}); });
} }
@ -153,9 +148,7 @@ router.post(
const challenge = JSON.stringify({ const challenge = JSON.stringify({
publicKey: { publicKey: {
...options, ...options,
challenge: Buffer.from(options.challenge).toString( challenge: Buffer.from(options.challenge).toString("base64"),
"base64",
),
allowCredentials: user.security_keys.map((x) => ({ allowCredentials: user.security_keys.map((x) => ({
id: x.key_id, id: x.key_id,
type: "public-key", type: "public-key",
@ -179,10 +172,8 @@ router.post(
if (undelete) { if (undelete) {
// undelete refers to un'disable' here // undelete refers to un'disable' here
if (user.disabled) if (user.disabled) await User.update({ id: user.id }, { disabled: false });
await User.update({ id: user.id }, { disabled: false }); if (user.deleted) await User.update({ id: user.id }, { deleted: false });
if (user.deleted)
await User.update({ id: user.id }, { deleted: false });
} else { } else {
if (user.deleted) if (user.deleted)
return res.status(400).json({ return res.status(400).json({
@ -203,7 +194,7 @@ router.post(
// https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
res.json({ token, settings: { ...user.settings, index: undefined } }); res.json({ token, settings: { ...user.settings, index: undefined } });
}, }
); );
/** /**

View File

@ -31,16 +31,12 @@ router.post(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
if (req.body.provider != null || req.body.voip_provider != null) { if (req.body.provider != null || req.body.voip_provider != null) {
console.log( console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
`[LOGOUT]: provider or voip provider not null!`,
req.body,
);
} else { } else {
delete req.body.provider; delete req.body.provider;
delete req.body.voip_provider; delete req.body.voip_provider;
if (Object.keys(req.body).length != 0) if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
} }
res.status(204).send(); res.status(204).send();
}, }
); );

View File

@ -59,11 +59,7 @@ router.post(
if (!backup) { if (!backup) {
const ret = verifyToken(user.totp_secret || "", code); const ret = verifyToken(user.totp_secret || "", code);
if (!ret || ret.delta != 0) if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
throw new HTTPError(
req.t("auth:login.INVALID_TOTP_CODE"),
60008,
);
} else { } else {
backup.consumed = true; backup.consumed = true;
await backup.save(); await backup.save();
@ -75,7 +71,7 @@ router.post(
token: await generateToken(user.id), token: await generateToken(user.id),
settings: { ...user.settings, index: undefined }, settings: { ...user.settings, index: undefined },
}); });
}, }
); );
export default router; export default router;

View File

@ -17,14 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { generateToken, SecurityKey, User, verifyWebAuthnToken, WebAuthn, WebAuthnTotpSchema } from "@spacebar/util";
generateToken,
SecurityKey,
User,
verifyWebAuthnToken,
WebAuthn,
WebAuthnTotpSchema,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { ExpectedAssertionResult } from "fido2-lib"; import { ExpectedAssertionResult } from "fido2-lib";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
@ -65,46 +58,33 @@ router.post(
}); });
const ret = await verifyWebAuthnToken(ticket); const ret = await verifyWebAuthnToken(ticket);
if (!ret) if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
await User.update({ id: user.id }, { totp_last_ticket: "" }); await User.update({ id: user.id }, { totp_last_ticket: "" });
const clientAttestationResponse = JSON.parse(code); const clientAttestationResponse = JSON.parse(code);
if (!clientAttestationResponse.rawId) if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
throw new HTTPError("Missing rawId", 400);
clientAttestationResponse.rawId = toArrayBuffer( clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url"));
Buffer.from(clientAttestationResponse.rawId, "base64url"),
);
const securityKey = await SecurityKey.findOneOrFail({ const securityKey = await SecurityKey.findOneOrFail({
where: { where: {
key_id: Buffer.from( key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"),
clientAttestationResponse.rawId,
"base64url",
).toString("base64"),
}, },
}); });
const assertionExpectations: ExpectedAssertionResult = JSON.parse( const assertionExpectations: ExpectedAssertionResult = JSON.parse(
Buffer.from( Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()
clientAttestationResponse.response.clientDataJSON,
"base64",
).toString(),
); );
const authnResult = await WebAuthn.fido2.assertionResult( const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, {
clientAttestationResponse, ...assertionExpectations,
{ factor: "second",
...assertionExpectations, publicKey: securityKey.public_key,
factor: "second", prevCounter: securityKey.counter,
publicKey: securityKey.public_key, userHandle: securityKey.key_id,
prevCounter: securityKey.counter, });
userHandle: securityKey.key_id,
},
);
const counter = authnResult.authnrData.get("counter"); const counter = authnResult.authnrData.get("counter");
@ -116,7 +96,7 @@ router.post(
token: await generateToken(user.id), token: await generateToken(user.id),
user_settings: user.settings, user_settings: user.settings,
}); });
}, }
); );
export default router; export default router;

View File

@ -16,13 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { import { IPAnalysis, getIpAdress, isProxy, route, verifyCaptcha } from "@spacebar/api";
IPAnalysis,
getIpAdress,
isProxy,
route,
verifyCaptcha,
} from "@spacebar/api";
import { import {
Config, Config,
FieldErrors, FieldErrors,
@ -65,13 +59,9 @@ router.post(
}); });
await regToken.remove(); await regToken.remove();
regTokenUsed = true; regTokenUsed = true;
console.log( console.log(`[REGISTER] Registration token ${token} used for registration!`);
`[REGISTER] Registration token ${token} used for registration!`,
);
} else { } else {
console.log( console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`,
);
} }
} }
@ -104,11 +94,7 @@ router.post(
}); });
} }
if ( if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
!regTokenUsed &&
register.requireCaptcha &&
security.captcha.enabled
) {
const { sitekey, service } = security.captcha; const { sitekey, service } = security.captcha;
if (!body.captcha_key) { if (!body.captcha_key) {
return res?.status(400).json({ return res?.status(400).json({
@ -139,9 +125,7 @@ router.post(
throw FieldErrors({ throw FieldErrors({
email: { email: {
code: "EMAIL_ALREADY_REGISTERED", code: "EMAIL_ALREADY_REGISTERED",
message: req.t( message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
"auth:register.EMAIL_ALREADY_REGISTERED",
),
}, },
}); });
} }
@ -176,9 +160,7 @@ router.post(
throw FieldErrors({ throw FieldErrors({
email: { email: {
code: "EMAIL_ALREADY_REGISTERED", code: "EMAIL_ALREADY_REGISTERED",
message: req.t( message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
"auth:register.EMAIL_ALREADY_REGISTERED",
),
}, },
}); });
} }
@ -198,14 +180,9 @@ router.post(
message: req.t("common:field.BASE_TYPE_REQUIRED"), message: req.t("common:field.BASE_TYPE_REQUIRED"),
}, },
}); });
} else if ( } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
register.dateOfBirth.required &&
register.dateOfBirth.minimum
) {
const minimum = new Date(); const minimum = new Date();
minimum.setFullYear( minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
minimum.getFullYear() - register.dateOfBirth.minimum,
);
body.date_of_birth = new Date(body.date_of_birth as Date); body.date_of_birth = new Date(body.date_of_birth as Date);
// higher is younger // higher is younger
@ -228,10 +205,7 @@ router.post(
throw FieldErrors({ throw FieldErrors({
password: { password: {
code: "PASSWORD_REQUIREMENTS_MIN_LENGTH", code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
message: req.t( message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }),
"auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH",
{ min: min },
),
}, },
}); });
} }
@ -249,8 +223,7 @@ router.post(
if ( if (
!regTokenUsed && !regTokenUsed &&
!body.invite && !body.invite &&
(register.requireInvite || (register.requireInvite || (register.guestsRequireInvite && !register.email))
(register.guestsRequireInvite && !register.email))
) { ) {
// require invite to register -> e.g. for organizations to send invites to their employees // require invite to register -> e.g. for organizations to send invites to their employees
throw FieldErrors({ throw FieldErrors({
@ -266,18 +239,14 @@ router.post(
limits.absoluteRate.register.enabled && limits.absoluteRate.register.enabled &&
(await User.count({ (await User.count({
where: { where: {
created_at: MoreThan( created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)),
new Date(
Date.now() - limits.absoluteRate.register.window,
),
),
}, },
})) >= limits.absoluteRate.register.limit })) >= limits.absoluteRate.register.limit
) { ) {
console.log( console.log(
`Global register ratelimit exceeded for ${getIpAdress(req)}, ${ `Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${
req.body.username req.body.invite || "No invite given"
}, ${req.body.invite || "No invite given"}`, }`
); );
throw FieldErrors({ throw FieldErrors({
email: { email: {
@ -295,7 +264,7 @@ router.post(
} }
return res.json({ token: await generateToken(user.id) }); return res.json({ token: await generateToken(user.id) });
}, }
); );
export default router; export default router;

View File

@ -17,14 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { checkToken, Email, FieldErrors, generateToken, PasswordResetSchema, User } from "@spacebar/util";
checkToken,
Email,
FieldErrors,
generateToken,
PasswordResetSchema,
User,
} from "@spacebar/util";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
@ -76,7 +69,7 @@ router.post(
await Email.sendPasswordChanged(user, user.email!); await Email.sendPasswordChanged(user, user.email!);
res.json({ token: await generateToken(user.id) }); res.json({ token: await generateToken(user.id) });
}, }
); );
export default router; export default router;

View File

@ -17,13 +17,7 @@
*/ */
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
import { import { checkToken, Config, FieldErrors, generateToken, User } from "@spacebar/util";
checkToken,
Config,
FieldErrors,
generateToken,
User,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
@ -97,7 +91,7 @@ router.post(
await User.update({ id: user.id }, { verified: true }); await User.update({ id: user.id }, { verified: true });
return res.json(await getToken(user)); return res.json(await getToken(user));
}, }
); );
export default router; export default router;

View File

@ -52,12 +52,10 @@ router.post(
return res.sendStatus(204); return res.sendStatus(204);
}) })
.catch((e) => { .catch((e) => {
console.error( console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`);
`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`,
);
throw new HTTPError("Failed to send verification email", 500); throw new HTTPError("Failed to send verification email", 500);
}); });
}, }
); );
export default router; export default router;

View File

@ -52,7 +52,7 @@ router.post(
nonce: "NoncePlaceholder", nonce: "NoncePlaceholder",
regenerate_nonce: "RegenNoncePlaceholder", regenerate_nonce: "RegenNoncePlaceholder",
}); });
}, }
); );
export default router; export default router;

View File

@ -52,7 +52,7 @@ router.get(
}); });
return res.send(channel); return res.send(channel);
}, }
); );
router.delete( router.delete(
@ -101,7 +101,7 @@ router.delete(
} }
res.send(channel); res.send(channel);
}, }
); );
router.patch( router.patch(
@ -122,11 +122,7 @@ router.patch(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const payload = req.body as ChannelModifySchema; const payload = req.body as ChannelModifySchema;
const { channel_id } = req.params; const { channel_id } = req.params;
if (payload.icon) if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
payload.icon = await handleFile(
`/channel-icons/${channel_id}`,
payload.icon,
);
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
where: { id: channel_id }, where: { id: channel_id },
@ -143,7 +139,7 @@ router.patch(
]); ]);
res.send(channel); res.send(channel);
}, }
); );
export default router; export default router;

View File

@ -65,9 +65,7 @@ router.post(
const { guild_id } = channel; const { guild_id } = channel;
const expires_at = const expires_at =
body.max_age == 0 || body.max_age == undefined body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now());
? undefined
: new Date(body.max_age * 1000 + Date.now());
const invite = await Invite.create({ const invite = await Invite.create({
code: random(), code: random(),
@ -95,7 +93,7 @@ router.post(
} as InviteCreateEvent); } as InviteCreateEvent);
res.status(201).send(data); res.status(201).send(data);
}, }
); );
router.get( router.get(
@ -126,7 +124,7 @@ router.get(
}); });
res.status(200).send(invites); res.status(200).send(invites);
}, }
); );
export default router; export default router;

View File

@ -17,12 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@spacebar/util";
emitEvent,
getPermission,
MessageAckEvent,
ReadState,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
@ -43,18 +38,13 @@ router.post(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params; const { channel_id, message_id } = req.params;
const permission = await getPermission( const permission = await getPermission(req.user_id, undefined, channel_id);
req.user_id,
undefined,
channel_id,
);
permission.hasThrow("VIEW_CHANNEL"); permission.hasThrow("VIEW_CHANNEL");
let read_state = await ReadState.findOne({ let read_state = await ReadState.findOne({
where: { user_id: req.user_id, channel_id }, where: { user_id: req.user_id, channel_id },
}); });
if (!read_state) if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
read_state = ReadState.create({ user_id: req.user_id, channel_id });
read_state.last_message_id = message_id; read_state.last_message_id = message_id;
await read_state.save(); await read_state.save();
@ -70,7 +60,7 @@ router.post(
} as MessageAckEvent); } as MessageAckEvent);
res.json({ token: null }); res.json({ token: null });
}, }
); );
export default router; export default router;

View File

@ -57,7 +57,7 @@ router.post(
flags: 1, flags: 1,
components: [], components: [],
}).status(200); }).status(200);
}, }
); );
export default router; export default router;

View File

@ -75,11 +75,7 @@ router.patch(
relations: ["attachments"], relations: ["attachments"],
}); });
const permissions = await getPermission( const permissions = await getPermission(req.user_id, undefined, channel_id);
req.user_id,
undefined,
channel_id,
);
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
@ -139,7 +135,7 @@ router.patch(
// these are not in the Discord.com response // these are not in the Discord.com response
mention_channels: new_message.mention_channels, mention_channels: new_message.mention_channels,
}); });
}, }
); );
// Backfill message with specific timestamp // Backfill message with specific timestamp
@ -196,13 +192,8 @@ router.put(
if (req.file) { if (req.file) {
try { try {
const file = await uploadFile( const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
`/attachments/${req.params.channel_id}`, attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
req.file,
);
attachments.push(
Attachment.create({ ...file, proxy_url: file.url }),
);
} catch (error) { } catch (error) {
return res.status(400).json(error); return res.status(400).json(error);
} }
@ -241,12 +232,10 @@ router.put(
]); ]);
// no await as it shouldnt block the message send function and silently catch error // no await as it shouldnt block the message send function and silently catch error
postHandleMessage(message).catch((e) => postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
console.error("[Message] post-message handler failed", e),
);
return res.json(message); return res.json(message);
}, }
); );
router.get( router.get(
@ -272,17 +261,12 @@ router.get(
relations: ["attachments"], relations: ["attachments"],
}); });
const permissions = await getPermission( const permissions = await getPermission(req.user_id, undefined, channel_id);
req.user_id,
undefined,
channel_id,
);
if (message.author_id !== req.user_id) if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
permissions.hasThrow("READ_MESSAGE_HISTORY");
return res.json(message); return res.json(message);
}, }
); );
router.delete( router.delete(
@ -310,11 +294,7 @@ router.delete(
if (message.author_id !== req.user_id) { if (message.author_id !== req.user_id) {
if (!rights.has("MANAGE_MESSAGES")) { if (!rights.has("MANAGE_MESSAGES")) {
const permission = await getPermission( const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
req.user_id,
channel.guild_id,
channel_id,
);
permission.hasThrow("MANAGE_MESSAGES"); permission.hasThrow("MANAGE_MESSAGES");
} }
} else rights.hasThrow("SELF_DELETE_MESSAGES"); } else rights.hasThrow("SELF_DELETE_MESSAGES");
@ -332,7 +312,7 @@ router.delete(
} as MessageDeleteEvent); } as MessageDeleteEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -88,7 +88,7 @@ router.delete(
} as MessageReactionRemoveAllEvent); } as MessageReactionRemoveAllEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
router.delete( router.delete(
@ -113,9 +113,7 @@ router.delete(
}); });
const already_added = message.reactions.find( const already_added = message.reactions.find(
(x) => (x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
(x.emoji.id === emoji.id && emoji.id) ||
x.emoji.name === emoji.name,
); );
if (!already_added) throw new HTTPError("Reaction not found", 404); if (!already_added) throw new HTTPError("Reaction not found", 404);
message.reactions.remove(already_added); message.reactions.remove(already_added);
@ -135,7 +133,7 @@ router.delete(
]); ]);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
router.get( router.get(
@ -161,9 +159,7 @@ router.get(
where: { id: message_id, channel_id }, where: { id: message_id, channel_id },
}); });
const reaction = message.reactions.find( const reaction = message.reactions.find(
(x) => (x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
(x.emoji.id === emoji.id && emoji.id) ||
x.emoji.name === emoji.name,
); );
if (!reaction) throw new HTTPError("Reaction not found", 404); if (!reaction) throw new HTTPError("Reaction not found", 404);
@ -175,7 +171,7 @@ router.get(
}); });
res.json(users); res.json(users);
}, }
); );
router.put( router.put(
@ -204,9 +200,7 @@ router.put(
where: { id: message_id, channel_id }, where: { id: message_id, channel_id },
}); });
const already_added = message.reactions.find( const already_added = message.reactions.find(
(x) => (x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
(x.emoji.id === emoji.id && emoji.id) ||
x.emoji.name === emoji.name,
); );
if (!already_added) req.permission?.hasThrow("ADD_REACTIONS"); if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
@ -222,8 +216,7 @@ router.put(
} }
if (already_added) { if (already_added) {
if (already_added.user_ids.includes(req.user_id)) if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
already_added.count++; already_added.count++;
already_added.user_ids.push(req.user_id); already_added.user_ids.push(req.user_id);
} else } else
@ -258,7 +251,7 @@ router.put(
} as MessageReactionAddEvent); } as MessageReactionAddEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
router.delete( router.delete(
@ -288,30 +281,19 @@ router.delete(
if (user_id === "@me") user_id = req.user_id; if (user_id === "@me") user_id = req.user_id;
else { else {
const permissions = await getPermission( const permissions = await getPermission(req.user_id, undefined, channel_id);
req.user_id,
undefined,
channel_id,
);
permissions.hasThrow("MANAGE_MESSAGES"); permissions.hasThrow("MANAGE_MESSAGES");
} }
const already_added = message.reactions.find( const already_added = message.reactions.find(
(x) => (x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name
(x.emoji.id === emoji.id && emoji.id) ||
x.emoji.name === emoji.name,
); );
if (!already_added || !already_added.user_ids.includes(user_id)) if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
throw new HTTPError("Reaction not found", 404);
already_added.count--; already_added.count--;
if (already_added.count <= 0) message.reactions.remove(already_added); if (already_added.count <= 0) message.reactions.remove(already_added);
else else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
already_added.user_ids.splice(
already_added.user_ids.indexOf(user_id),
1,
);
await message.save(); await message.save();
@ -328,7 +310,7 @@ router.delete(
} as MessageReactionRemoveEvent); } as MessageReactionRemoveEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -17,15 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Channel, Config, emitEvent, getPermission, getRights, Message, MessageDeleteBulkEvent } from "@spacebar/util";
Channel,
Config,
emitEvent,
getPermission,
getRights,
Message,
MessageDeleteBulkEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
@ -54,30 +46,22 @@ router.post(
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
where: { id: channel_id }, where: { id: channel_id },
}); });
if (!channel.guild_id) if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
throw new HTTPError("Can't bulk delete dm channel messages", 400);
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
rights.hasThrow("SELF_DELETE_MESSAGES"); rights.hasThrow("SELF_DELETE_MESSAGES");
const superuser = rights.has("MANAGE_MESSAGES"); const superuser = rights.has("MANAGE_MESSAGES");
const permission = await getPermission( const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
req.user_id,
channel?.guild_id,
channel_id,
);
const { maxBulkDelete } = Config.get().limits.message; const { maxBulkDelete } = Config.get().limits.message;
const { messages } = req.body as { messages: string[] }; const { messages } = req.body as { messages: string[] };
if (messages.length === 0) if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
throw new HTTPError("You must specify messages to bulk delete");
if (!superuser) { if (!superuser) {
permission.hasThrow("MANAGE_MESSAGES"); permission.hasThrow("MANAGE_MESSAGES");
if (messages.length > maxBulkDelete) if (messages.length > maxBulkDelete)
throw new HTTPError( throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
`You cannot delete more than ${maxBulkDelete} messages`,
);
} }
await Message.delete(messages); await Message.delete(messages);
@ -89,5 +73,5 @@ router.post(
} as MessageDeleteBulkEvent); } as MessageDeleteBulkEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );

View File

@ -40,13 +40,7 @@ import {
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import multer from "multer"; import multer from "multer";
import { import { FindManyOptions, FindOperator, LessThan, MoreThan, MoreThanOrEqual } from "typeorm";
FindManyOptions,
FindOperator,
LessThan,
MoreThan,
MoreThanOrEqual,
} from "typeorm";
import { URL } from "url"; import { URL } from "url";
const router: Router = Router(); const router: Router = Router();
@ -68,8 +62,7 @@ router.get(
}, },
limit: { limit: {
type: "number", type: "number",
description: description: "max number of messages to return (1-100). defaults to 50",
"max number of messages to return (1-100). defaults to 50",
}, },
}, },
responses: { responses: {
@ -95,14 +88,9 @@ router.get(
const before = req.query.before ? `${req.query.before}` : undefined; const before = req.query.before ? `${req.query.before}` : undefined;
const after = req.query.after ? `${req.query.after}` : undefined; const after = req.query.after ? `${req.query.after}` : undefined;
const limit = Number(req.query.limit) || 50; const limit = Number(req.query.limit) || 50;
if (limit < 1 || limit > 100) if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
throw new HTTPError("limit must be between 1 and 100", 422);
const permissions = await getPermission( const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
req.user_id,
channel.guild_id,
channel_id,
);
permissions.hasThrow("VIEW_CHANNEL"); permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
@ -148,12 +136,10 @@ router.get(
} }
} else { } else {
if (after) { if (after) {
if (BigInt(after) > BigInt(Snowflake.generate())) if (BigInt(after) > BigInt(Snowflake.generate())) return res.status(422);
return res.status(422);
query.where.id = MoreThan(after); query.where.id = MoreThan(after);
} else if (before) { } else if (before) {
if (BigInt(before) > BigInt(Snowflake.generate())) if (BigInt(before) > BigInt(Snowflake.generate())) return res.status(422);
return res.status(422);
query.where.id = LessThan(before); query.where.id = LessThan(before);
} }
@ -180,12 +166,8 @@ router.get(
}); });
x.attachments?.forEach((y: Attachment) => { x.attachments?.forEach((y: Attachment) => {
// dynamically set attachment proxy_url in case the endpoint changed // dynamically set attachment proxy_url in case the endpoint changed
const uri = y.proxy_url.startsWith("http") const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
? y.proxy_url y.proxy_url = `${endpoint == null ? "" : endpoint}${new URL(uri).pathname}`;
: `https://example.org${y.proxy_url}`;
y.proxy_url = `${endpoint == null ? "" : endpoint}${
new URL(uri).pathname
}`;
}); });
/** /**
@ -202,7 +184,7 @@ router.get(
}); });
return res.json(ret); return res.json(ret);
}, }
); );
// TODO: config max upload size // TODO: config max upload size
@ -258,10 +240,7 @@ router.post(
relations: ["recipients", "recipients.user"], relations: ["recipients", "recipients.user"],
}); });
if (!channel.isWritable()) { if (!channel.isWritable()) {
throw new HTTPError( throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
`Cannot send messages to channel of type ${channel.type}`,
400,
);
} }
if (body.nonce) { if (body.nonce) {
@ -283,12 +262,7 @@ router.post(
const count = await Message.count({ const count = await Message.count({
where: { where: {
channel_id, channel_id,
timestamp: MoreThan( timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
new Date(
Date.now() -
limits.absoluteRate.sendMessage.window,
),
),
}, },
}); });
@ -305,13 +279,8 @@ router.post(
const files = (req.files as Express.Multer.File[]) ?? []; const files = (req.files as Express.Multer.File[]) ?? [];
for (const currFile of files) { for (const currFile of files) {
try { try {
const file = await uploadFile( const file = await uploadFile(`/attachments/${channel.id}`, currFile);
`/attachments/${channel.id}`, attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
currFile,
);
attachments.push(
Attachment.create({ ...file, proxy_url: file.url }),
);
} catch (error) { } catch (error) {
return res.status(400).json({ message: error?.toString() }); return res.status(400).json({ message: error?.toString() });
} }
@ -347,14 +316,12 @@ router.post(
recipient.save(), recipient.save(),
emitEvent({ emitEvent({
event: "CHANNEL_CREATE", event: "CHANNEL_CREATE",
data: channel_dto.excludedRecipients([ data: channel_dto.excludedRecipients([recipient.user_id]),
recipient.user_id,
]),
user_id: recipient.user_id, user_id: recipient.user_id,
}), }),
]); ]);
} }
}) || [], }) || []
); );
} }
@ -370,16 +337,13 @@ router.post(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
message.member.roles = message.member.roles message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id);
.filter((x) => x.id != x.guild_id)
.map((x) => x.id);
} }
let read_state = await ReadState.findOne({ let read_state = await ReadState.findOne({
where: { user_id: req.user_id, channel_id }, where: { user_id: req.user_id, channel_id },
}); });
if (!read_state) if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
read_state = ReadState.create({ user_id: req.user_id, channel_id });
read_state.last_message_id = message.id; read_state.last_message_id = message.id;
await Promise.all([ await Promise.all([
@ -391,21 +355,16 @@ router.post(
data: message, data: message,
} as MessageCreateEvent), } as MessageCreateEvent),
message.guild_id message.guild_id
? Member.update( ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id })
{ id: req.user_id, guild_id: message.guild_id },
{ last_message_id: message.id },
)
: null, : null,
channel.save(), channel.save(),
]); ]);
// no await as it shouldnt block the message send function and silently catch error // no await as it shouldnt block the message send function and silently catch error
postHandleMessage(message).catch((e) => postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
console.error("[Message] post-message handler failed", e),
);
return res.json(message); return res.json(message);
}, }
); );
export default router; export default router;

View File

@ -55,15 +55,14 @@ router.put(
if (!channel.guild_id) throw new HTTPError("Channel not found", 404); if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
if (body.type === 0) { if (body.type === 0) {
if (!(await Role.count({ where: { id: overwrite_id } }))) if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
throw new HTTPError("role not found", 404);
} else if (body.type === 1) { } else if (body.type === 1) {
if (!(await Member.count({ where: { id: overwrite_id } }))) if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
throw new HTTPError("user not found", 404);
} else throw new HTTPError("type not supported", 501); } else throw new HTTPError("type not supported", 501);
let overwrite: ChannelPermissionOverwrite | undefined = let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find(
channel.permission_overwrites?.find((x) => x.id === overwrite_id); (x) => x.id === overwrite_id
);
if (!overwrite) { if (!overwrite) {
overwrite = { overwrite = {
id: overwrite_id, id: overwrite_id,
@ -73,14 +72,8 @@ router.put(
}; };
channel.permission_overwrites?.push(overwrite); channel.permission_overwrites?.push(overwrite);
} }
overwrite.allow = String( overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0")));
(req.permission?.bitfield || 0n) & overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0")));
(BigInt(body.allow) || BigInt("0")),
);
overwrite.deny = String(
(req.permission?.bitfield || 0n) &
(BigInt(body.deny) || BigInt("0")),
);
await Promise.all([ await Promise.all([
channel.save(), channel.save(),
@ -92,7 +85,7 @@ router.put(
]); ]);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
// TODO: check permission hierarchy // TODO: check permission hierarchy
@ -107,9 +100,7 @@ router.delete(
}); });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404); if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
channel.permission_overwrites = channel.permission_overwrites?.filter( channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id === overwrite_id);
(x) => x.id === overwrite_id,
);
await Promise.all([ await Promise.all([
channel.save(), channel.save(),
@ -121,7 +112,7 @@ router.delete(
]); ]);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -57,8 +57,7 @@ router.put(
where: { channel: { id: channel_id }, pinned: true }, where: { channel: { id: channel_id }, pinned: true },
}); });
const { maxPins } = Config.get().limits.channel; const { maxPins } = Config.get().limits.channel;
if (pinned_count >= maxPins) if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
await Promise.all([ await Promise.all([
Message.update({ id: message_id }, { pinned: true }), Message.update({ id: message_id }, { pinned: true }),
@ -79,7 +78,7 @@ router.put(
]); ]);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
router.delete( router.delete(
@ -129,7 +128,7 @@ router.delete(
]); ]);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
router.get( router.get(
@ -153,7 +152,7 @@ router.get(
}); });
res.send(pins); res.send(pins);
}, }
); );
export default router; export default router;

View File

@ -57,17 +57,12 @@ router.post(
where: { id: channel_id }, where: { id: channel_id },
}); });
if (!channel.guild_id) if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400);
throw new HTTPError("Can't purge dm channels", 400);
isTextChannel(channel.type); isTextChannel(channel.type);
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
if (!rights.has("MANAGE_MESSAGES")) { if (!rights.has("MANAGE_MESSAGES")) {
const permissions = await getPermission( const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
req.user_id,
channel.guild_id,
channel_id,
);
permissions.hasThrow("MANAGE_MESSAGES"); permissions.hasThrow("MANAGE_MESSAGES");
permissions.hasThrow("MANAGE_CHANNELS"); permissions.hasThrow("MANAGE_CHANNELS");
} }
@ -84,9 +79,7 @@ router.post(
where: { where: {
channel_id, channel_id,
id: Between(after, before), // the right way around id: Between(after, before), // the right way around
author_id: rights.has("SELF_DELETE_MESSAGES") author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id),
? undefined
: Not(req.user_id),
// if you lack the right of self-deletion, you can't delete your own messages, even in purges // if you lack the right of self-deletion, you can't delete your own messages, even in purges
}, },
relations: [ relations: [
@ -121,5 +114,5 @@ router.post(
} as MessageDeleteBulkEvent); } as MessageDeleteBulkEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );

View File

@ -48,24 +48,16 @@ router.put(
}); });
if (channel.type !== ChannelType.GROUP_DM) { if (channel.type !== ChannelType.GROUP_DM) {
const recipients = [ const recipients = [...(channel.recipients?.map((r) => r.user_id) || []), user_id].unique();
...(channel.recipients?.map((r) => r.user_id) || []),
user_id,
].unique();
const new_channel = await Channel.createDMChannel( const new_channel = await Channel.createDMChannel(recipients, req.user_id);
recipients,
req.user_id,
);
return res.status(201).json(new_channel); return res.status(201).json(new_channel);
} else { } else {
if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
} }
channel.recipients?.push( channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
Recipient.create({ channel_id: channel_id, user_id: user_id }),
);
await channel.save(); await channel.save();
await emitEvent({ await emitEvent({
@ -87,7 +79,7 @@ router.put(
} as ChannelRecipientAddEvent); } as ChannelRecipientAddEvent);
return res.sendStatus(204); return res.sendStatus(204);
} }
}, }
); );
router.delete( router.delete(
@ -104,12 +96,7 @@ router.delete(
where: { id: channel_id }, where: { id: channel_id },
relations: ["recipients"], relations: ["recipients"],
}); });
if ( if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id)))
!(
channel.type === ChannelType.GROUP_DM &&
(channel.owner_id === req.user_id || user_id === req.user_id)
)
)
throw DiscordApiErrors.MISSING_PERMISSIONS; throw DiscordApiErrors.MISSING_PERMISSIONS;
if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) { if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
@ -119,7 +106,7 @@ router.delete(
await Channel.removeRecipientFromChannel(channel, user_id); await Channel.removeRecipientFromChannel(channel, user_id);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -64,7 +64,7 @@ router.post(
} as TypingStartEvent); } as TypingStartEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -47,7 +47,7 @@ router.get(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
res.json([]); res.json([]);
}, }
); );
// TODO: use Image Data Type for avatar instead of String // TODO: use Image Data Type for avatar instead of String
@ -77,8 +77,7 @@ router.post(
const webhook_count = await Webhook.count({ where: { channel_id } }); const webhook_count = await Webhook.count({ where: { channel_id } });
const { maxWebhooks } = Config.get().limits.channel; const { maxWebhooks } = Config.get().limits.channel;
if (maxWebhooks && webhook_count > maxWebhooks) if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
let { avatar, name } = req.body as WebhookCreateSchema; let { avatar, name } = req.body as WebhookCreateSchema;
name = trimSpecial(name); name = trimSpecial(name);
@ -105,7 +104,7 @@ router.post(
...hook, ...hook,
user: user, user: user,
}); });
}, }
); );
export default router; export default router;

View File

@ -30,9 +30,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
provider_id: { provider_id: {
code: "BASE_TYPE_CHOICES", code: "BASE_TYPE_CHOICES",
message: req.t("common:field.BASE_TYPE_CHOICES", { message: req.t("common:field.BASE_TYPE_CHOICES", {
types: Array.from(ConnectionStore.connections.keys()).join( types: Array.from(ConnectionStore.connections.keys()).join(", "),
", ",
),
}), }),
}, },
}); });

View File

@ -17,55 +17,44 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { ConnectionCallbackSchema, ConnectionStore, emitEvent, FieldErrors } from "@spacebar/util";
ConnectionCallbackSchema,
ConnectionStore,
emitEvent,
FieldErrors,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
router.post( router.post("/", route({ requestBody: "ConnectionCallbackSchema" }), async (req: Request, res: Response) => {
"/", const { connection_name } = req.params;
route({ requestBody: "ConnectionCallbackSchema" }), const connection = ConnectionStore.connections.get(connection_name);
async (req: Request, res: Response) => { if (!connection)
const { connection_name } = req.params; throw FieldErrors({
const connection = ConnectionStore.connections.get(connection_name); provider_id: {
if (!connection) code: "BASE_TYPE_CHOICES",
throw FieldErrors({ message: req.t("common:field.BASE_TYPE_CHOICES", {
provider_id: { types: Array.from(ConnectionStore.connections.keys()).join(", "),
code: "BASE_TYPE_CHOICES", }),
message: req.t("common:field.BASE_TYPE_CHOICES", { },
types: Array.from( });
ConnectionStore.connections.keys(),
).join(", "),
}),
},
});
if (!connection.settings.enabled) if (!connection.settings.enabled)
throw FieldErrors({ throw FieldErrors({
provider_id: { provider_id: {
message: "This connection has been disabled server-side.", message: "This connection has been disabled server-side.",
}, },
}); });
const body = req.body as ConnectionCallbackSchema; const body = req.body as ConnectionCallbackSchema;
const userId = connection.getUserId(body.state); const userId = connection.getUserId(body.state);
const connectedAccnt = await connection.handleCallback(body); const connectedAccnt = await connection.handleCallback(body);
// whether we should emit a connections update event, only used when a connection doesnt already exist // whether we should emit a connections update event, only used when a connection doesnt already exist
if (connectedAccnt) if (connectedAccnt)
emitEvent({ emitEvent({
event: "USER_CONNECTIONS_UPDATE", event: "USER_CONNECTIONS_UPDATE",
data: { ...connectedAccnt, token_data: undefined }, data: { ...connectedAccnt, token_data: undefined },
user_id: userId, user_id: userId,
}); });
res.sendStatus(204); res.sendStatus(204);
}, });
);
export default router; export default router;

View File

@ -70,7 +70,7 @@ router.get(
offset: Number(offset || Config.get().guild.discovery.offset), offset: Number(offset || Config.get().guild.discovery.offset),
limit: Number(limit || configLimit), limit: Number(limit || configLimit),
}); });
}, }
); );
export default router; export default router;

View File

@ -38,12 +38,10 @@ router.get(
// const { locale, primary_only } = req.query; // const { locale, primary_only } = req.query;
const { primary_only } = req.query; const { primary_only } = req.query;
const out = primary_only const out = primary_only ? await Categories.find() : await Categories.find({ where: { is_primary: true } });
? await Categories.find()
: await Categories.find({ where: { is_primary: true } });
res.send(out); res.send(out);
}, }
); );
export default router; export default router;

View File

@ -52,7 +52,7 @@ router.get(
}); });
res.redirect(release.url); res.redirect(release.url);
}, }
); );
export default router; export default router;

View File

@ -43,7 +43,7 @@ router.get(
max_concurrency: 1, max_concurrency: 1,
}, },
}); });
}, }
); );
export default router; export default router;

View File

@ -36,7 +36,7 @@ router.get(
res.json({ res.json({
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001", url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
}); });
}, }
); );
export default router; export default router;

View File

@ -36,9 +36,7 @@ router.get(
media_format: { media_format: {
type: "string", type: "string",
description: "Media format", description: "Media format",
values: Object.keys(TenorMediaTypes).filter((key) => values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
isNaN(Number(key)),
),
}, },
locale: { locale: {
type: "string", type: "string",
@ -65,13 +63,13 @@ router.get(
agent, agent,
method: "get", method: "get",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}, }
); );
const { results } = await response.json(); const { results } = await response.json();
res.json(results.map(parseGifResult)).status(200); res.json(results.map(parseGifResult)).status(200);
}, }
); );
export default router; export default router;

View File

@ -31,9 +31,7 @@ router.get(
media_format: { media_format: {
type: "string", type: "string",
description: "Media format", description: "Media format",
values: Object.keys(TenorMediaTypes).filter((key) => values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
isNaN(Number(key)),
),
}, },
locale: { locale: {
type: "string", type: "string",
@ -60,13 +58,13 @@ router.get(
agent, agent,
method: "get", method: "get",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}, }
); );
const { results } = await response.json(); const { results } = await response.json();
res.json(results.map(parseGifResult)).status(200); res.json(results.map(parseGifResult)).status(200);
}, }
); );
export default router; export default router;

View File

@ -17,12 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { TenorCategoriesResults, TenorTrendingResults, getGifApiKey, parseGifResult } from "@spacebar/util";
TenorCategoriesResults,
TenorTrendingResults,
getGifApiKey,
parseGifResult,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import fetch from "node-fetch"; import fetch from "node-fetch";
import { ProxyAgent } from "proxy-agent"; import { ProxyAgent } from "proxy-agent";
@ -55,28 +50,20 @@ router.get(
const agent = new ProxyAgent(); const agent = new ProxyAgent();
const [responseSource, trendGifSource] = await Promise.all([ const [responseSource, trendGifSource] = await Promise.all([
fetch( fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, agent,
{ method: "get",
agent, headers: { "Content-Type": "application/json" },
method: "get", }),
headers: { "Content-Type": "application/json" }, fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
}, agent,
), method: "get",
fetch( headers: { "Content-Type": "application/json" },
`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, }),
{
agent,
method: "get",
headers: { "Content-Type": "application/json" },
},
),
]); ]);
const { tags } = const { tags } = (await responseSource.json()) as TenorCategoriesResults;
(await responseSource.json()) as TenorCategoriesResults; const { results } = (await trendGifSource.json()) as TenorTrendingResults;
const { results } =
(await trendGifSource.json()) as TenorTrendingResults;
res.json({ res.json({
categories: tags.map((x) => ({ categories: tags.map((x) => ({
@ -85,7 +72,7 @@ router.get(
})), })),
gifs: [parseGifResult(results[0])], gifs: [parseGifResult(results[0])],
}).status(200); }).status(200);
}, }
); );
export default router; export default router;

View File

@ -39,9 +39,7 @@ router.get(
const showAllGuilds = Config.get().guild.discovery.showAllGuilds; const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
const genLoadId = (size: number) => const genLoadId = (size: number) =>
[...Array(size)] [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
.map(() => Math.floor(Math.random() * 16).toString(16))
.join("");
const guilds = showAllGuilds const guilds = showAllGuilds
? await Guild.find({ take: Math.abs(Number(limit || 24)) }) ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
@ -53,7 +51,7 @@ router.get(
recommended_guilds: guilds, recommended_guilds: guilds,
load_id: `server_recs/${genLoadId(32)}`, load_id: `server_recs/${genLoadId(32)}`,
}).status(200); }).status(200);
}, }
); );
export default router; export default router;

View File

@ -78,7 +78,7 @@ router.get(
}); });
return res.json(bansObj); return res.json(bansObj);
}, }
); );
router.get( router.get(
@ -115,7 +115,7 @@ router.get(
delete ban.ip; delete ban.ip;
return res.json(ban); return res.json(ban);
}, }
); );
router.put( router.put(
@ -139,14 +139,8 @@ router.put(
const { guild_id } = req.params; const { guild_id } = req.params;
const banned_user_id = req.params.user_id; const banned_user_id = req.params.user_id;
if ( if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id)
req.user_id === banned_user_id && throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
banned_user_id === req.permission?.cache.guild?.owner_id
)
throw new HTTPError(
"You are the guild owner, hence can't ban yourself",
403,
);
if (req.permission?.cache.guild?.owner_id === banned_user_id) if (req.permission?.cache.guild?.owner_id === banned_user_id)
throw new HTTPError("You can't ban the owner", 400); throw new HTTPError("You can't ban the owner", 400);
@ -175,7 +169,7 @@ router.put(
]); ]);
return res.json(ban); return res.json(ban);
}, }
); );
router.put( router.put(
@ -200,10 +194,7 @@ router.put(
const banned_user = await User.getPublicUser(req.params.user_id); const banned_user = await User.getPublicUser(req.params.user_id);
if (req.permission?.cache.guild?.owner_id === req.params.user_id) if (req.permission?.cache.guild?.owner_id === req.params.user_id)
throw new HTTPError( throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
"You are the guild owner, hence can't ban yourself",
403,
);
const ban = Ban.create({ const ban = Ban.create({
user_id: req.params.user_id, user_id: req.params.user_id,
@ -227,7 +218,7 @@ router.put(
]); ]);
return res.json(ban); return res.json(ban);
}, }
); );
router.delete( router.delete(
@ -273,7 +264,7 @@ router.delete(
]); ]);
return res.status(204).send(); return res.status(204).send();
}, }
); );
export default router; export default router;

View File

@ -42,7 +42,7 @@ router.get(
const channels = await Channel.find({ where: { guild_id } }); const channels = await Channel.find({ where: { guild_id } });
res.json(channels); res.json(channels);
}, }
); );
router.post( router.post(
@ -67,13 +67,10 @@ router.post(
const { guild_id } = req.params; const { guild_id } = req.params;
const body = req.body as ChannelModifySchema; const body = req.body as ChannelModifySchema;
const channel = await Channel.createChannel( const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
{ ...body, guild_id },
req.user_id,
);
res.status(201).json(channel); res.status(201).json(channel);
}, }
); );
router.patch( router.patch(
@ -102,9 +99,7 @@ router.patch(
}); });
// The channels not listed for this query // The channels not listed for this query
const notMentioned = guild.channel_ordering.filter( const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x));
(x) => !body.find((c) => c.id == x),
);
const withParents = body.filter((x) => x.parent_id != undefined); const withParents = body.filter((x) => x.parent_id != undefined);
const withPositions = body.filter((x) => x.position != undefined); const withPositions = body.filter((x) => x.position != undefined);
@ -124,7 +119,7 @@ router.patch(
channel_id: channel.id, channel_id: channel.id,
guild_id, guild_id,
} as ChannelUpdateEvent); } as ChannelUpdateEvent);
}), })
); );
// have to do the parents after the positions // have to do the parents after the positions
@ -141,10 +136,7 @@ router.patch(
]); ]);
if (opt.lock_permissions) if (opt.lock_permissions)
await Channel.update( await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
{ id: channel.id },
{ permission_overwrites: parent.permission_overwrites },
);
const parentPos = notMentioned.indexOf(parent.id); const parentPos = notMentioned.indexOf(parent.id);
notMentioned.splice(parentPos + 1, 0, channel.id); notMentioned.splice(parentPos + 1, 0, channel.id);
@ -156,16 +148,13 @@ router.patch(
channel_id: channel.id, channel_id: channel.id,
guild_id, guild_id,
} as ChannelUpdateEvent); } as ChannelUpdateEvent);
}), })
); );
await Guild.update( await Guild.update({ id: guild_id }, { channel_ordering: notMentioned });
{ id: guild_id },
{ channel_ordering: notMentioned },
);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -45,8 +45,7 @@ router.post(
where: { id: guild_id }, where: { id: guild_id },
select: ["owner_id"], select: ["owner_id"],
}); });
if (guild.owner_id !== req.user_id) if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401);
throw new HTTPError("You are not the owner of this guild", 401);
await Promise.all([ await Promise.all([
Guild.delete({ id: guild_id }), // this will also delete all guild related data Guild.delete({ id: guild_id }), // this will also delete all guild related data
@ -60,7 +59,7 @@ router.post(
]); ]);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -59,7 +59,7 @@ router.get(
}, },
minimum_size: 0, minimum_size: 0,
}); });
}, }
); );
export default router; export default router;

View File

@ -57,7 +57,7 @@ router.get(
}); });
return res.json(emojis); return res.json(emojis);
}, }
); );
router.get( router.get(
@ -86,7 +86,7 @@ router.get(
}); });
return res.json(emoji); return res.json(emoji);
}, }
); );
router.post( router.post(
@ -116,10 +116,7 @@ router.post(
}); });
const { maxEmojis } = Config.get().limits.guild; const { maxEmojis } = Config.get().limits.guild;
if (emoji_count >= maxEmojis) if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(
maxEmojis,
);
if (body.require_colons == null) body.require_colons = true; if (body.require_colons == null) body.require_colons = true;
const user = await User.findOneOrFail({ where: { id: req.user_id } }); const user = await User.findOneOrFail({ where: { id: req.user_id } });
@ -147,7 +144,7 @@ router.post(
} as GuildEmojisUpdateEvent); } as GuildEmojisUpdateEvent);
return res.status(201).json(emoji); return res.status(201).json(emoji);
}, }
); );
router.patch( router.patch(
@ -184,7 +181,7 @@ router.patch(
} as GuildEmojisUpdateEvent); } as GuildEmojisUpdateEvent);
return res.json(emoji); return res.json(emoji);
}, }
); );
router.delete( router.delete(
@ -216,7 +213,7 @@ router.delete(
} as GuildEmojisUpdateEvent); } as GuildEmojisUpdateEvent);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -58,17 +58,13 @@ router.get(
Guild.findOneOrFail({ where: { id: guild_id } }), Guild.findOneOrFail({ where: { id: guild_id } }),
Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
]); ]);
if (!member) if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
throw new HTTPError(
"You are not a member of the guild you are trying to access",
401,
);
return res.send({ return res.send({
...guild, ...guild,
joined_at: member?.joined_at, joined_at: member?.joined_at,
}); });
}, }
); );
router.patch( router.patch(
@ -99,9 +95,7 @@ router.patch(
const permission = await getPermission(req.user_id, guild_id); const permission = await getPermission(req.user_id, guild_id);
if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD"))
throw DiscordApiErrors.MISSING_PERMISSIONS.withParams( throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
"MANAGE_GUILDS",
);
const guild = await Guild.findOneOrFail({ const guild = await Guild.findOneOrFail({
where: { id: guild_id }, where: { id: guild_id },
@ -110,47 +104,29 @@ router.patch(
// TODO: guild update check image // TODO: guild update check image
if (body.icon && body.icon != guild.icon) if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
if (body.banner && body.banner !== guild.banner) if (body.banner && body.banner !== guild.banner)
body.banner = await handleFile(`/banners/${guild_id}`, body.banner); body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
if (body.splash && body.splash !== guild.splash) if (body.splash && body.splash !== guild.splash)
body.splash = await handleFile( body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
`/splashes/${guild_id}`,
body.splash,
);
if ( if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
body.discovery_splash && body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash);
body.discovery_splash !== guild.discovery_splash
)
body.discovery_splash = await handleFile(
`/discovery-splashes/${guild_id}`,
body.discovery_splash,
);
if (body.features) { if (body.features) {
const diff = guild.features const diff = guild.features
.filter((x) => !body.features?.includes(x)) .filter((x) => !body.features?.includes(x))
.concat( .concat(body.features.filter((x) => !guild.features.includes(x)));
body.features.filter((x) => !guild.features.includes(x)),
);
// TODO move these // TODO move these
const MUTABLE_FEATURES = [ const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
"COMMUNITY",
"INVITES_DISABLED",
"DISCOVERABLE",
];
for (const feature of diff) { for (const feature of diff) {
if (MUTABLE_FEATURES.includes(feature)) continue; if (MUTABLE_FEATURES.includes(feature)) continue;
throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams( throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
feature,
);
} }
// for some reason, they don't update in the assign. // for some reason, they don't update in the assign.
@ -179,7 +155,7 @@ router.patch(
], ],
}, },
undefined, undefined,
{ skipPermissionCheck: true }, { skipPermissionCheck: true }
); );
await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
@ -212,7 +188,7 @@ router.patch(
], ],
}, },
undefined, undefined,
{ skipPermissionCheck: true }, { skipPermissionCheck: true }
); );
await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
@ -242,7 +218,7 @@ router.patch(
]); ]);
return res.json(data); return res.json(data);
}, }
); );
export default router; export default router;

View File

@ -41,7 +41,7 @@ router.get(
}); });
return res.json(invites); return res.json(invites);
}, }
); );
export default router; export default router;

View File

@ -36,7 +36,7 @@ router.get(
message: "Unknown Guild Member Verification Form", message: "Unknown Guild Member Verification Form",
code: 10068, code: 10068,
}); });
}, }
); );
export default router; export default router;

View File

@ -62,13 +62,9 @@ router.get(
select: { select: {
index: true, index: true,
// only grab public member props // only grab public member props
...Object.fromEntries( ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])),
PublicMemberProjection.map((x) => [x, true]),
),
// and public user props // and public user props
user: Object.fromEntries( user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
PublicUserProjection.map((x) => [x, true]),
),
roles: { roles: {
id: true, id: true,
}, },
@ -80,7 +76,7 @@ router.get(
user: member.user.toPublicUser(), user: member.user.toPublicUser(),
roles: member.roles.map((x) => x.id), roles: member.roles.map((x) => x.id),
}); });
}, }
); );
router.patch( router.patch(
@ -104,8 +100,7 @@ router.patch(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { guild_id } = req.params; const { guild_id } = req.params;
const member_id = const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id;
req.params.member_id === "@me" ? req.user_id : req.params.member_id;
const body = req.body as MemberChangeSchema; const body = req.body as MemberChangeSchema;
const member = await Member.findOneOrFail({ const member = await Member.findOneOrFail({
@ -128,19 +123,13 @@ router.patch(
} }
} }
if ( if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") {
("bio" in body || "avatar" in body) &&
req.params.member_id != "@me"
) {
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
rights.hasThrow("MANAGE_USERS"); rights.hasThrow("MANAGE_USERS");
} }
if (body.avatar) if (body.avatar)
body.avatar = await handleFile( body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
`/guilds/${guild_id}/users/${member_id}/avatars`,
body.avatar as string,
);
member.assign(body); member.assign(body);
@ -152,8 +141,7 @@ router.patch(
body.roles = body.roles || []; body.roles = body.roles || [];
body.roles.filter((x) => !!x); body.roles.filter((x) => !!x);
if (body.roles.indexOf(everyone.id) === -1) if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
body.roles.push(everyone.id);
// foreign key constraint will fail if role doesn't exist // foreign key constraint will fail if role doesn't exist
member.roles = body.roles.map((x) => Role.create({ id: x })); member.roles = body.roles.map((x) => Role.create({ id: x }));
} }
@ -170,7 +158,7 @@ router.patch(
} as GuildMemberUpdateEvent); } as GuildMemberUpdateEvent);
res.json(member); res.json(member);
}, }
); );
router.put( router.put(
@ -222,7 +210,7 @@ router.put(
await Member.addToGuild(member_id, guild_id); await Member.addToGuild(member_id, guild_id);
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers }); res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
}, }
); );
router.delete( router.delete(
@ -249,7 +237,7 @@ router.delete(
await Member.removeFromGuild(member_id, guild_id); await Member.removeFromGuild(member_id, guild_id);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -49,7 +49,7 @@ router.patch(
await Member.changeNickname(member_id, guild_id, req.body.nick); await Member.changeNickname(member_id, guild_id, req.body.nick);
res.status(200).send(); res.status(200).send();
}, }
); );
export default router; export default router;

View File

@ -38,7 +38,7 @@ router.delete(
await Member.removeRole(member_id, guild_id, role_id); await Member.removeRole(member_id, guild_id, role_id);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
router.put( router.put(
@ -55,7 +55,7 @@ router.put(
await Member.addRole(member_id, guild_id, role_id); await Member.addRole(member_id, guild_id, role_id);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -33,8 +33,7 @@ router.get(
query: { query: {
limit: { limit: {
type: "number", type: "number",
description: description: "max number of members to return (1-1000). default 1",
"max number of members to return (1-1000). default 1",
}, },
after: { after: {
type: "string", type: "string",
@ -52,8 +51,7 @@ router.get(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { guild_id } = req.params; const { guild_id } = req.params;
const limit = Number(req.query.limit) || 1; const limit = Number(req.query.limit) || 1;
if (limit > 1000 || limit < 1) if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000");
throw new HTTPError("Limit must be between 1 and 1000");
const after = `${req.query.after}`; const after = `${req.query.after}`;
const query = after ? { id: MoreThan(after) } : {}; const query = after ? { id: MoreThan(after) } : {};
@ -67,7 +65,7 @@ router.get(
}); });
return res.json(members); return res.json(members);
}, }
); );
export default router; export default router;

View File

@ -54,14 +54,10 @@ router.get(
} = req.query; } = req.query;
const parsedLimit = Number(limit) || 50; const parsedLimit = Number(limit) || 50;
if (parsedLimit < 1 || parsedLimit > 100) if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
throw new HTTPError("limit must be between 1 and 100", 422);
if (sort_order) { if (sort_order) {
if ( if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1)
typeof sort_order != "string" ||
["desc", "asc"].indexOf(sort_order) == -1
)
throw FieldErrors({ throw FieldErrors({
sort_order: { sort_order: {
message: "Value must be one of ('desc', 'asc').", message: "Value must be one of ('desc', 'asc').",
@ -70,20 +66,13 @@ router.get(
}); // todo this is wrong }); // todo this is wrong
} }
const permissions = await getPermission( const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined);
req.user_id,
req.params.guild_id,
channel_id as string | undefined,
);
permissions.hasThrow("VIEW_CHANNEL"); permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY")) if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
return res.json({ messages: [], total_results: 0 });
const query: FindManyOptions<Message> = { const query: FindManyOptions<Message> = {
order: { order: {
timestamp: sort_order timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
? (sort_order.toUpperCase() as "ASC" | "DESC")
: "DESC",
}, },
take: parsedLimit || 0, take: parsedLimit || 0,
where: { where: {
@ -114,16 +103,8 @@ router.get(
const ids = []; const ids = [];
for (const channel of channels) { for (const channel of channels) {
const perm = await getPermission( const perm = await getPermission(req.user_id, req.params.guild_id, channel.id);
req.user_id, if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue;
req.params.guild_id,
channel.id,
);
if (
!perm.has("VIEW_CHANNEL") ||
!perm.has("READ_MESSAGE_HISTORY")
)
continue;
ids.push(channel.id); ids.push(channel.id);
} }
@ -170,7 +151,7 @@ router.get(
messages: messagesDto, messages: messagesDto,
total_results: messages.length, total_results: messages.length,
}); });
}, }
); );
export default router; export default router;

View File

@ -57,10 +57,7 @@ router.patch(
}); });
if (body.banner) if (body.banner)
body.banner = await handleFile( body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
`/guilds/${guild_id}/users/${req.user_id}/avatars`,
body.banner as string,
);
member = await OrmUtils.mergeDeep(member, body); member = await OrmUtils.mergeDeep(member, body);
@ -74,7 +71,7 @@ router.patch(
} as GuildMemberUpdateEvent); } as GuildMemberUpdateEvent);
res.json(member); res.json(member);
}, }
); );
export default router; export default router;

View File

@ -23,12 +23,7 @@ import { IsNull, LessThan } from "typeorm";
const router = Router(); const router = Router();
//Returns all inactive members, respecting role hierarchy //Returns all inactive members, respecting role hierarchy
const inactiveMembers = async ( const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
guild_id: string,
user_id: string,
days: number,
roles: string[] = [],
) => {
const date = new Date(); const date = new Date();
date.setDate(date.getDate() - days); date.setDate(date.getDate() - days);
//Snowflake should have `generateFromTime` method? Or similar? //Snowflake should have `generateFromTime` method? Or similar?
@ -55,9 +50,7 @@ const inactiveMembers = async (
//I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
if (roles.length && members.length) if (roles.length && members.length)
members = members.filter((user) => members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
user.roles?.some((role) => roles.includes(role.id)),
);
const me = await Member.findOneOrFail({ const me = await Member.findOneOrFail({
where: { id: user_id, guild_id }, where: { id: user_id, guild_id },
@ -73,8 +66,8 @@ const inactiveMembers = async (
member.roles?.some( member.roles?.some(
(role) => (role) =>
role.position < myHighestRole || //roles higher than me can't be kicked role.position < myHighestRole || //roles higher than me can't be kicked
me.id === guild.owner_id, //owner can kick anyone me.id === guild.owner_id //owner can kick anyone
), )
); );
return members; return members;
@ -95,15 +88,10 @@ router.get(
let roles = req.query.include_roles; let roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles]; //express will return array otherwise if (typeof roles === "string") roles = [roles]; //express will return array otherwise
const members = await inactiveMembers( const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]);
req.params.guild_id,
req.user_id,
days,
roles as string[],
);
res.send({ pruned: members.length }); res.send({ pruned: members.length });
}, }
); );
router.post( router.post(
@ -127,19 +115,12 @@ router.post(
if (typeof roles === "string") roles = [roles]; if (typeof roles === "string") roles = [roles];
const { guild_id } = req.params; const { guild_id } = req.params;
const members = await inactiveMembers( const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
guild_id,
req.user_id,
days,
roles as string[],
);
await Promise.all( await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
members.map((x) => Member.removeFromGuild(x.id, guild_id)),
);
res.send({ purged: members.length }); res.send({ purged: members.length });
}, }
); );
export default router; export default router;

View File

@ -38,13 +38,8 @@ router.get(
const { guild_id } = req.params; const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
//TODO we should use an enum for guild's features and not hardcoded strings //TODO we should use an enum for guild's features and not hardcoded strings
return res.json( return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS")));
await getVoiceRegions( }
getIpAdress(req),
guild.features.includes("VIP_REGIONS"),
),
);
},
); );
export default router; export default router;

View File

@ -53,7 +53,7 @@ router.get(
where: { guild_id, id: role_id }, where: { guild_id, id: role_id },
}); });
return res.json(role); return res.json(role);
}, }
); );
router.delete( router.delete(
@ -75,8 +75,7 @@ router.delete(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { guild_id, role_id } = req.params; const { guild_id, role_id } = req.params;
if (role_id === guild_id) if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
throw new HTTPError("You can't delete the @everyone role");
await Promise.all([ await Promise.all([
Role.delete({ Role.delete({
@ -94,7 +93,7 @@ router.delete(
]); ]);
res.sendStatus(204); res.sendStatus(204);
}, }
); );
// TODO: check role hierarchy // TODO: check role hierarchy
@ -123,11 +122,7 @@ router.patch(
const { role_id, guild_id } = req.params; const { role_id, guild_id } = req.params;
const body = req.body as RoleModifySchema; const body = req.body as RoleModifySchema;
if (body.icon && body.icon.length) if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
body.icon = await handleFile(
`/role-icons/${role_id}`,
body.icon as string,
);
else body.icon = undefined; else body.icon = undefined;
const role = await Role.findOneOrFail({ const role = await Role.findOneOrFail({
@ -135,10 +130,7 @@ router.patch(
}); });
role.assign({ role.assign({
...body, ...body,
permissions: String( permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
(req.permission?.bitfield || 0n) &
BigInt(body.permissions || "0"),
),
}); });
await Promise.all([ await Promise.all([
@ -154,7 +146,7 @@ router.patch(
]); ]);
res.json(role); res.json(role);
}, }
); );
export default router; export default router;

View File

@ -22,41 +22,31 @@ import { route } from "@spacebar/api";
const router = Router(); const router = Router();
router.patch( router.patch("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
"/", // Payload is JSON containing a list of member_ids, the new list of members to have the role
route({ permission: "MANAGE_ROLES" }), const { guild_id, role_id } = req.params;
async (req: Request, res: Response) => { const { member_ids } = req.body;
// Payload is JSON containing a list of member_ids, the new list of members to have the role
const { guild_id, role_id } = req.params;
const { member_ids } = req.body;
// don't mess with @everyone // don't mess with @everyone
if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE;
const members = await Member.find({ const members = await Member.find({
where: { guild_id }, where: { guild_id },
relations: ["roles"], relations: ["roles"],
}); });
const [add, remove] = partition( const [add, remove] = partition(
members, members,
(member) => (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id)
member_ids.includes(member.id) && );
!member.roles.map((role) => role.id).includes(role_id),
);
// TODO (erkin): have a bulk add/remove function that adds the roles in a single txn // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
await Promise.all([ await Promise.all([
...add.map((member) => ...add.map((member) => Member.addRole(member.id, guild_id, role_id)),
Member.addRole(member.id, guild_id, role_id), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id)),
), ]);
...remove.map((member) =>
Member.removeRole(member.id, guild_id, role_id),
),
]);
res.sendStatus(204); res.sendStatus(204);
}, });
);
export default router; export default router;

View File

@ -68,8 +68,7 @@ router.post(
const role_count = await Role.count({ where: { guild_id } }); const role_count = await Role.count({ where: { guild_id } });
const { maxRoles } = Config.get().limits.guild; const { maxRoles } = Config.get().limits.guild;
if (role_count > maxRoles) if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
const role = Role.create({ const role = Role.create({
// values before ...body are default and can be overriden // values before ...body are default and can be overriden
@ -80,10 +79,7 @@ router.post(
...body, ...body,
guild_id: guild_id, guild_id: guild_id,
managed: false, managed: false,
permissions: String( permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
(req.permission?.bitfield || 0n) &
BigInt(body.permissions || "0"),
),
tags: undefined, tags: undefined,
icon: undefined, icon: undefined,
unicode_emoji: undefined, unicode_emoji: undefined,
@ -112,7 +108,7 @@ router.post(
]); ]);
res.json(role); res.json(role);
}, }
); );
router.patch( router.patch(
@ -136,11 +132,7 @@ router.patch(
const { guild_id } = req.params; const { guild_id } = req.params;
const body = req.body as RolePositionUpdateSchema; const body = req.body as RolePositionUpdateSchema;
await Promise.all( await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
body.map(async (x) =>
Role.update({ guild_id, id: x.id }, { position: x.position }),
),
);
const roles = await Role.find({ const roles = await Role.find({
where: body.map((x) => ({ id: x.id, guild_id })), where: body.map((x) => ({ id: x.id, guild_id })),
@ -155,12 +147,12 @@ router.patch(
guild_id, guild_id,
role: x, role: x,
}, },
} as GuildRoleUpdateEvent), } as GuildRoleUpdateEvent)
), )
); );
res.json(roles); res.json(roles);
}, }
); );
export default router; export default router;

View File

@ -50,7 +50,7 @@ router.get(
await Member.IsInGuildOrFail(req.user_id, guild_id); await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(await Sticker.find({ where: { guild_id } })); res.json(await Sticker.find({ where: { guild_id } }));
}, }
); );
const bodyParser = multer({ const bodyParser = multer({
@ -102,7 +102,7 @@ router.post(
await sendStickerUpdateEvent(guild_id); await sendStickerUpdateEvent(guild_id);
res.json(sticker); res.json(sticker);
}, }
); );
function getStickerFormat(mime_type: string) { function getStickerFormat(mime_type: string) {
@ -116,9 +116,7 @@ function getStickerFormat(mime_type: string) {
case "image/gif": case "image/gif":
return StickerFormatType.GIF; return StickerFormatType.GIF;
default: default:
throw new HTTPError( throw new HTTPError("invalid sticker format: must be png, apng or lottie");
"invalid sticker format: must be png, apng or lottie",
);
} }
} }
@ -141,9 +139,9 @@ router.get(
res.json( res.json(
await Sticker.findOneOrFail({ await Sticker.findOneOrFail({
where: { guild_id, id: sticker_id }, where: { guild_id, id: sticker_id },
}), })
); );
}, }
); );
router.patch( router.patch(
@ -175,7 +173,7 @@ router.patch(
await sendStickerUpdateEvent(guild_id); await sendStickerUpdateEvent(guild_id);
return res.json(sticker); return res.json(sticker);
}, }
); );
async function sendStickerUpdateEvent(guild_id: string) { async function sendStickerUpdateEvent(guild_id: string) {
@ -207,7 +205,7 @@ router.delete(
await sendStickerUpdateEvent(guild_id); await sendStickerUpdateEvent(guild_id);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -57,7 +57,7 @@ router.get(
}); });
return res.json(templates); return res.json(templates);
}, }
); );
router.post( router.post(
@ -102,7 +102,7 @@ router.post(
}).save(); }).save();
res.json(template); res.json(template);
}, }
); );
router.delete( router.delete(
@ -123,7 +123,7 @@ router.delete(
}); });
res.json(template); res.json(template);
}, }
); );
router.put( router.put(
@ -148,7 +148,7 @@ router.put(
}).save(); }).save();
res.json(template); res.json(template);
}, }
); );
router.patch( router.patch(
@ -173,7 +173,7 @@ router.patch(
}).save(); }).save();
res.json(template); res.json(template);
}, }
); );
export default router; export default router;

View File

@ -17,13 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Channel, ChannelType, Guild, Invite, VanityUrlSchema } from "@spacebar/util";
Channel,
ChannelType,
Guild,
Invite,
VanityUrlSchema,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
@ -64,11 +58,9 @@ router.get(
}); });
if (!invite || invite.length == 0) return res.json({ code: null }); if (!invite || invite.length == 0) return res.json({ code: null });
return res.json( return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
invite.map((x) => ({ code: x.code, uses: x.uses })),
);
} }
}, }
); );
router.patch( router.patch(
@ -94,11 +86,9 @@ router.patch(
const code = body.code?.replace(InviteRegex, ""); const code = body.code?.replace(InviteRegex, "");
const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (!guild.features.includes("VANITY_URL")) if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
throw new HTTPError("Your guild doesn't support vanity urls");
if (!code || code.length === 0) if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
throw new HTTPError("Code cannot be null or empty");
const invite = await Invite.findOne({ where: { code } }); const invite = await Invite.findOne({ where: { code } });
if (invite) throw new HTTPError("Invite already exists"); if (invite) throw new HTTPError("Invite already exists");
@ -112,7 +102,7 @@ router.patch(
{ guild_id }, { guild_id },
{ {
code: code, code: code,
}, }
); );
return res.json({ code }); return res.json({ code });
@ -132,7 +122,7 @@ router.patch(
}).save(); }).save();
return res.json({ code: code }); return res.json({ code: code });
}, }
); );
export default router; export default router;

View File

@ -52,14 +52,9 @@ router.patch(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema; const body = req.body as VoiceStateUpdateSchema;
const { guild_id } = req.params; const { guild_id } = req.params;
const user_id = const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id;
req.params.user_id === "@me" ? req.user_id : req.params.user_id;
const perms = await getPermission( const perms = await getPermission(req.user_id, guild_id, body.channel_id);
req.user_id,
guild_id,
body.channel_id,
);
/* /*
From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
@ -98,7 +93,7 @@ router.patch(
} as VoiceStateUpdateEvent), } as VoiceStateUpdateEvent),
]); ]);
return res.sendStatus(204); return res.sendStatus(204);
}, }
); );
export default router; export default router;

View File

@ -17,12 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Channel, Guild, GuildUpdateWelcomeScreenSchema, Member } from "@spacebar/util";
Channel,
Guild,
GuildUpdateWelcomeScreenSchema,
Member,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
@ -46,7 +41,7 @@ router.get(
await Member.IsInGuildOrFail(req.user_id, guild_id); await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(guild.welcome_screen); res.json(guild.welcome_screen);
}, }
); );
router.patch( router.patch(
@ -70,11 +65,9 @@ router.patch(
const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
if (body.enabled != undefined) if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled;
guild.welcome_screen.enabled = body.enabled;
if (body.description != undefined) if (body.description != undefined) guild.welcome_screen.description = body.description;
guild.welcome_screen.description = body.description;
if (body.welcome_channels != undefined) { if (body.welcome_channels != undefined) {
// Ensure channels exist within the guild // Ensure channels exist within the guild
@ -83,8 +76,8 @@ router.patch(
Channel.findOneOrFail({ Channel.findOneOrFail({
where: { id: channel_id, guild_id }, where: { id: channel_id, guild_id },
select: { id: true }, select: { id: true },
}), })
) || [], ) || []
); );
guild.welcome_screen.welcome_channels = body.welcome_channels; guild.welcome_screen.welcome_channels = body.welcome_channels;
} }
@ -92,7 +85,7 @@ router.patch(
await guild.save(); await guild.save();
res.status(200).json(guild.welcome_screen); res.status(200).json(guild.welcome_screen);
}, }
); );
export default router; export default router;

View File

@ -81,10 +81,8 @@ router.get(
// Only return channels where @everyone has the CONNECT permission // Only return channels where @everyone has the CONNECT permission
if ( if (
doc.permission_overwrites === undefined || doc.permission_overwrites === undefined ||
Permissions.channelPermission( Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) ===
doc.permission_overwrites, Permissions.FLAGS.CONNECT
Permissions.FLAGS.CONNECT,
) === Permissions.FLAGS.CONNECT
) { ) {
channels.push({ channels.push({
id: doc.id, id: doc.id,
@ -110,7 +108,7 @@ router.get(
res.set("Cache-Control", "public, max-age=300"); res.set("Cache-Control", "public, max-age=300");
return res.json(data); return res.json(data);
}, }
); );
export default router; export default router;

View File

@ -57,15 +57,8 @@ router.get(
// Fetch parameter // Fetch parameter
const style = req.query.style?.toString() || "shield"; const style = req.query.style?.toString() || "shield";
if ( if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
!["shield", "banner1", "banner2", "banner3", "banner4"].includes( throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
style,
)
) {
throw new HTTPError(
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
400,
);
} }
// Setup canvas // Setup canvas
@ -74,17 +67,7 @@ router.get(
const sizeOf = require("image-size"); const sizeOf = require("image-size");
// TODO: Widget style templates need Spacebar branding // TODO: Widget style templates need Spacebar branding
const source = path.join( const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`);
__dirname,
"..",
"..",
"..",
"..",
"..",
"assets",
"widget",
`${style}.png`,
);
if (!fs.existsSync(source)) { if (!fs.existsSync(source)) {
throw new HTTPError("Widget template does not exist.", 400); throw new HTTPError("Widget template does not exist.", 400);
} }
@ -100,99 +83,32 @@ router.get(
switch (style) { switch (style) {
case "shield": case "shield":
ctx.textAlign = "center"; ctx.textAlign = "center";
await drawText( await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence);
ctx,
73,
13,
"#FFFFFF",
"thin 10px Verdana",
presence,
);
break; break;
case "banner1": case "banner1":
if (icon) await drawIcon(ctx, 20, 27, 50, icon); if (icon) await drawIcon(ctx, 20, 27, 50, icon);
await drawText( await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
ctx, await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence);
83,
51,
"#FFFFFF",
"12px Verdana",
name,
22,
);
await drawText(
ctx,
83,
66,
"#C9D2F0FF",
"thin 11px Verdana",
presence,
);
break; break;
case "banner2": case "banner2":
if (icon) await drawIcon(ctx, 13, 19, 36, icon); if (icon) await drawIcon(ctx, 13, 19, 36, icon);
await drawText( await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
ctx, await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence);
62,
34,
"#FFFFFF",
"12px Verdana",
name,
15,
);
await drawText(
ctx,
62,
49,
"#C9D2F0FF",
"thin 11px Verdana",
presence,
);
break; break;
case "banner3": case "banner3":
if (icon) await drawIcon(ctx, 20, 20, 50, icon); if (icon) await drawIcon(ctx, 20, 20, 50, icon);
await drawText( await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
ctx, await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence);
83,
44,
"#FFFFFF",
"12px Verdana",
name,
27,
);
await drawText(
ctx,
83,
58,
"#C9D2F0FF",
"thin 11px Verdana",
presence,
);
break; break;
case "banner4": case "banner4":
if (icon) await drawIcon(ctx, 21, 136, 50, icon); if (icon) await drawIcon(ctx, 21, 136, 50, icon);
await drawText( await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
ctx, await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence);
84,
156,
"#FFFFFF",
"13px Verdana",
name,
27,
);
await drawText(
ctx,
84,
171,
"#C9D2F0FF",
"thin 12px Verdana",
presence,
);
break; break;
default: default:
throw new HTTPError( throw new HTTPError(
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
400, 400
); );
} }
@ -201,16 +117,10 @@ router.get(
res.set("Content-Type", "image/png"); res.set("Content-Type", "image/png");
res.set("Cache-Control", "public, max-age=3600"); res.set("Cache-Control", "public, max-age=3600");
return res.send(buffer); return res.send(buffer);
}, }
); );
async function drawIcon( async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) {
canvas: any,
x: number,
y: number,
scale: number,
icon: string,
) {
const img = new (require("canvas").Image)(); const img = new (require("canvas").Image)();
img.src = icon; img.src = icon;
@ -234,12 +144,11 @@ async function drawText(
color: string, color: string,
font: string, font: string,
text: string, text: string,
maxcharacters?: number, maxcharacters?: number
) { ) {
canvas.fillStyle = color; canvas.fillStyle = color;
canvas.font = font; canvas.font = font;
if (text.length > (maxcharacters || 0) && maxcharacters) if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
text = text.slice(0, maxcharacters) + "...";
canvas.fillText(text, x, y); canvas.fillText(text, x, y);
} }

View File

@ -44,7 +44,7 @@ router.get(
enabled: guild.widget_enabled || false, enabled: guild.widget_enabled || false,
channel_id: guild.widget_channel_id || null, channel_id: guild.widget_channel_id || null,
}); });
}, }
); );
// https://discord.com/developers/docs/resources/guild#modify-guild-widget // https://discord.com/developers/docs/resources/guild#modify-guild-widget
@ -74,12 +74,12 @@ router.patch(
{ {
widget_enabled: body.enabled, widget_enabled: body.enabled,
widget_channel_id: body.channel_id, widget_channel_id: body.channel_id,
}, }
); );
// Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
return res.json(body); return res.json(body);
}, }
); );
export default router; export default router;

View File

@ -17,14 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Config, DiscordApiErrors, Guild, GuildCreateSchema, Member, getRights } from "@spacebar/util";
Config,
DiscordApiErrors,
Guild,
GuildCreateSchema,
Member,
getRights,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
@ -73,7 +66,7 @@ router.post(
await Member.addToGuild(req.user_id, guild.id); await Member.addToGuild(req.user_id, guild.id);
res.status(201).json(guild); res.status(201).json(guild);
}, }
); );
export default router; export default router;

View File

@ -47,13 +47,11 @@ router.get(
}, },
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { allowDiscordTemplates, allowRaws, enabled } = const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
Config.get().templates;
if (!enabled) if (!enabled)
res.json({ res.json({
code: 403, code: 403,
message: message: "Template creation & usage is disabled on this instance.",
"Template creation & usage is disabled on this instance.",
}).sendStatus(403); }).sendStatus(403);
const { code } = req.params; const { code } = req.params;
@ -63,8 +61,7 @@ router.get(
return res return res
.json({ .json({
code: 403, code: 403,
message: message: "Discord templates cannot be used on this instance.",
"Discord templates cannot be used on this instance.",
}) })
.sendStatus(403); .sendStatus(403);
const discordTemplateID = code.split("discord:", 2)[1]; const discordTemplateID = code.split("discord:", 2)[1];
@ -74,7 +71,7 @@ router.get(
{ {
method: "get", method: "get",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}, }
); );
return res.json(await discordTemplateData.json()); return res.json(await discordTemplateData.json());
} }
@ -95,75 +92,70 @@ router.get(
where: { code: code }, where: { code: code },
}); });
res.json(template); res.json(template);
}, }
); );
router.post( router.post("/:code", route({ requestBody: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
"/:code", const {
route({ requestBody: "GuildTemplateCreateSchema" }), enabled,
async (req: Request, res: Response) => { allowTemplateCreation,
const { // allowDiscordTemplates,
enabled, // allowRaws,
allowTemplateCreation, } = Config.get().templates;
// allowDiscordTemplates, if (!enabled)
// allowRaws, return res
} = Config.get().templates; .json({
if (!enabled) code: 403,
return res message: "Template creation & usage is disabled on this instance.",
.json({ })
code: 403, .sendStatus(403);
message: if (!allowTemplateCreation)
"Template creation & usage is disabled on this instance.", return res
}) .json({
.sendStatus(403); code: 403,
if (!allowTemplateCreation) message: "Template creation is disabled on this instance.",
return res })
.json({ .sendStatus(403);
code: 403,
message: "Template creation is disabled on this instance.",
})
.sendStatus(403);
const { code } = req.params; const { code } = req.params;
const body = req.body as GuildTemplateCreateSchema; const body = req.body as GuildTemplateCreateSchema;
const { maxGuilds } = Config.get().limits.user; const { maxGuilds } = Config.get().limits.user;
const guild_count = await Member.count({ where: { id: req.user_id } }); const guild_count = await Member.count({ where: { id: req.user_id } });
if (guild_count >= maxGuilds) { if (guild_count >= maxGuilds) {
throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
} }
const template = await Template.findOneOrFail({ const template = await Template.findOneOrFail({
where: { code: code }, where: { code: code },
}); });
const guild_id = Snowflake.generate(); const guild_id = Snowflake.generate();
const [guild] = await Promise.all([ const [guild] = await Promise.all([
Guild.create({ Guild.create({
...body, ...body,
...template.serialized_source_guild, ...template.serialized_source_guild,
id: guild_id, id: guild_id,
owner_id: req.user_id, owner_id: req.user_id,
}).save(), }).save(),
Role.create({ Role.create({
id: guild_id, id: guild_id,
guild_id: guild_id, guild_id: guild_id,
color: 0, color: 0,
hoist: false, hoist: false,
managed: true, managed: true,
mentionable: true, mentionable: true,
name: "@everyone", name: "@everyone",
permissions: BigInt("2251804225").toString(), // TODO: where did this come from? permissions: BigInt("2251804225").toString(), // TODO: where did this come from?
position: 0, position: 0,
}).save(), }).save(),
]); ]);
await Member.addToGuild(req.user_id, guild_id); await Member.addToGuild(req.user_id, guild_id);
res.status(201).json({ id: guild.id }); res.status(201).json({ id: guild.id });
}, });
);
export default router; export default router;

View File

@ -53,7 +53,7 @@ router.get(
}); });
res.status(200).send(invite); res.status(200).send(invite);
}, }
); );
router.post( router.post(
@ -89,21 +89,14 @@ router.post(
where: { id: req.user_id }, where: { id: req.user_id },
}); });
if ( if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1)
features.includes("INTERNAL_EMPLOYEE_ONLY") && throw new HTTPError("Only intended for the staff of this server.", 401);
(public_flags & 1) !== 1 if (features.includes("INVITES_DISABLED")) throw new HTTPError("Sorry, this guild has joins closed.", 403);
)
throw new HTTPError(
"Only intended for the staff of this server.",
401,
);
if (features.includes("INVITES_DISABLED"))
throw new HTTPError("Sorry, this guild has joins closed.", 403);
const invite = await Invite.joinGuild(req.user_id, code); const invite = await Invite.joinGuild(req.user_id, code);
res.json(invite); res.json(invite);
}, }
); );
// * cant use permission of route() function because path doesn't have guild_id/channel_id // * cant use permission of route() function because path doesn't have guild_id/channel_id
@ -127,20 +120,10 @@ router.delete(
const invite = await Invite.findOneOrFail({ where: { code } }); const invite = await Invite.findOneOrFail({ where: { code } });
const { guild_id, channel_id } = invite; const { guild_id, channel_id } = invite;
const permission = await getPermission( const permission = await getPermission(req.user_id, guild_id, channel_id);
req.user_id,
guild_id,
channel_id,
);
if ( if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS"))
!permission.has("MANAGE_GUILD") && throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401);
!permission.has("MANAGE_CHANNELS")
)
throw new HTTPError(
"You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
401,
);
await Promise.all([ await Promise.all([
Invite.delete({ code }), Invite.delete({ code }),
@ -156,7 +139,7 @@ router.delete(
]); ]);
res.json({ invite: invite }); res.json({ invite: invite });
}, }
); );
export default router; export default router;

View File

@ -17,11 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Application, DiscordApiErrors, PublicUserProjection } from "@spacebar/util";
Application,
DiscordApiErrors,
PublicUserProjection,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
@ -40,9 +36,7 @@ router.get(
where: { id: req.params.id }, where: { id: req.params.id },
relations: ["bot", "owner"], relations: ["bot", "owner"],
select: { select: {
owner: Object.fromEntries( owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
PublicUserProjection.map((x) => [x, true]),
),
}, },
}); });
@ -51,9 +45,8 @@ router.get(
res.json({ res.json({
...app, ...app,
owner: app.owner.toPublicUser(), owner: app.owner.toPublicUser(),
install_params: install_params: app.install_params !== null ? app.install_params : undefined,
app.install_params !== null ? app.install_params : undefined,
}); });
}, }
); );
export default router; export default router;

View File

@ -84,13 +84,7 @@ router.get(
id: req.user_id, id: req.user_id,
bot: false, bot: false,
}, },
select: [ select: ["id", "username", "avatar", "discriminator", "public_flags"],
"id",
"username",
"avatar",
"discriminator",
"public_flags",
],
}); });
const guilds = await Member.find({ const guilds = await Member.find({
@ -165,7 +159,7 @@ router.get(
}, },
authorized: false, authorized: false,
}); });
}, }
); );
router.post( router.post(
@ -210,17 +204,9 @@ router.post(
// TODO: captcha verification // TODO: captcha verification
// TODO: MFA verification // TODO: MFA verification
const perms = await getPermission( const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
req.user_id,
body.guild_id,
undefined,
{ member_relations: ["user"] },
);
// getPermission cache won't exist if we're owner // getPermission cache won't exist if we're owner
if ( if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot)
Object.keys(perms.cache || {}).length > 0 &&
perms.cache.member?.user.bot
)
throw DiscordApiErrors.UNAUTHORIZED; throw DiscordApiErrors.UNAUTHORIZED;
perms.hasThrow("MANAGE_GUILD"); perms.hasThrow("MANAGE_GUILD");
@ -234,19 +220,14 @@ router.post(
// TODO: use DiscordApiErrors // TODO: use DiscordApiErrors
// findOneOrFail throws code 404 // findOneOrFail throws code 404
if (!app) throw new ApiError("Unknown Application", 10002, 404); if (!app) throw new ApiError("Unknown Application", 10002, 404);
if (!app.bot) if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
throw new ApiError(
"OAuth2 application does not have a bot",
50010,
400,
);
await Member.addToGuild(app.id, body.guild_id); await Member.addToGuild(app.id, body.guild_id);
return res.json({ return res.json({
location: "/oauth2/authorized", // redirect URL location: "/oauth2/authorized", // redirect URL
}); });
}, }
); );
export default router; export default router;

View File

@ -48,7 +48,7 @@ router.get(
tosPage: general.tosPage, tosPage: general.tosPage,
}, },
}); });
}, }
); );
export default router; export default router;

View File

@ -34,20 +34,14 @@ router.get(
const { cdn, gateway, api } = Config.get(); const { cdn, gateway, api } = Config.get();
const IdentityForm = { const IdentityForm = {
cdn: cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
cdn.endpointPublic || gateway: gateway.endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
process.env.CDN ||
"http://localhost:3001",
gateway:
gateway.endpointPublic ||
process.env.GATEWAY ||
"ws://localhost:3001",
defaultApiVersion: api.defaultVersion ?? 9, defaultApiVersion: api.defaultVersion ?? 9,
apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/", apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/",
}; };
res.json(IdentityForm); res.json(IdentityForm);
}, }
); );
export default router; export default router;

View File

@ -33,7 +33,7 @@ router.get(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { general } = Config.get(); const { general } = Config.get();
res.json(general); res.json(general);
}, }
); );
export default router; export default router;

View File

@ -33,7 +33,7 @@ router.get(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { limits } = Config.get(); const { limits } = Config.get();
res.json(limits); res.json(limits);
}, }
); );
export default router; export default router;

View File

@ -17,14 +17,7 @@
*/ */
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import { Config, getRights, Guild, Member, Message, User } from "@spacebar/util";
Config,
getRights,
Guild,
Member,
Message,
User,
} from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
@ -54,7 +47,7 @@ router.get(
members: await Member.count(), members: await Member.count(),
}, },
}); });
}, }
); );
export default router; export default router;

View File

@ -61,7 +61,7 @@ router.post(
]); ]);
return res.status(204); return res.status(204);
}, }
); );
export default router; export default router;

View File

@ -20,15 +20,11 @@ import { Router, Request, Response } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
const router = Router(); const router = Router();
router.get( router.get("/scheduled-maintenances/upcoming.json", route({}), async (req: Request, res: Response) => {
"/scheduled-maintenances/upcoming.json", res.json({
route({}), page: {},
async (req: Request, res: Response) => { scheduled_maintenances: {},
res.json({ });
page: {}, });
scheduled_maintenances: {},
});
},
);
export default router; export default router;

View File

@ -31,7 +31,7 @@ router.post(
(req: Request, res: Response) => { (req: Request, res: Response) => {
// TODO: // TODO:
res.sendStatus(204); res.sendStatus(204);
}, }
); );
export default router; export default router;

Some files were not shown because too many files have changed in this diff Show More