summary refs log tree commit diff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/package-lock.json40
-rw-r--r--api/package.json4
-rw-r--r--api/src/middlewares/ErrorHandler.ts5
-rw-r--r--api/src/middlewares/RateLimit.ts95
-rw-r--r--api/src/routes/auth/login.ts2
-rw-r--r--api/src/routes/auth/register.ts18
-rw-r--r--api/src/schema/Message.ts4
7 files changed, 94 insertions, 74 deletions
diff --git a/api/package-lock.json b/api/package-lock.json

index bff4e940..1b673d4a 100644 --- a/api/package-lock.json +++ b/api/package-lock.json
@@ -28,8 +28,8 @@ "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", - "lambert-server": "^1.2.8", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", @@ -74,9 +74,11 @@ "dot-prop": "^6.0.1", "env-paths": "^2.2.1", "jsonwebtoken": "^8.5.1", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", + "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", @@ -6927,16 +6929,16 @@ } }, "node_modules/lambert-server": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.8.tgz", - "integrity": "sha512-vi/Ku/QudY+WIdGO9bc0qLfVhfuJFWXk1+etesPW1vW29sPbmevLL6IwfvCtw+/MyzRAJLOyCBfQ310a68+2QQ==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.10.tgz", + "integrity": "sha512-BHGPmpUrRklFJHPu0vAA8NBewtEd4IX80FRpV4nX9z8kHTUYHqnYHoBeUEWoUmxAeFQvQae1Axk5RQXRQk4VNw==", "dependencies": { "body-parser": "^1.19.0", "chalk": "^4.1.1", "express": "^4.17.1", "express-async-errors": "^3.1.1", "helmet": "^4.4.1", - "missing-native-js-functions": "^1.1.8" + "missing-native-js-functions": "^1.2.11" } }, "node_modules/lazystream": { @@ -7356,9 +7358,9 @@ } }, "node_modules/missing-native-js-functions": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.10.tgz", - "integrity": "sha512-sq+oAw/C3OtUyKopLNOf/+U85YNx7db6fy5nVfGVKlGdcV8tX24GjOSkcZeCAnAIjMEnlQBWTr17JXa3OJj22g==" + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.11.tgz", + "integrity": "sha512-U97IscNBL4Wg9adYjEBT46Hb0Ld5dPT8vbdwFX+TNzGrFQCc4WqoGAZouaLNFwUqxzzHZ9DVg59unwnQyeIIQg==" }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -12476,9 +12478,11 @@ "env-paths": "^2.2.1", "jest": "^27.0.6", "jsonwebtoken": "^8.5.1", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", + "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", @@ -17639,16 +17643,16 @@ } }, "lambert-server": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.8.tgz", - "integrity": "sha512-vi/Ku/QudY+WIdGO9bc0qLfVhfuJFWXk1+etesPW1vW29sPbmevLL6IwfvCtw+/MyzRAJLOyCBfQ310a68+2QQ==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.10.tgz", + "integrity": "sha512-BHGPmpUrRklFJHPu0vAA8NBewtEd4IX80FRpV4nX9z8kHTUYHqnYHoBeUEWoUmxAeFQvQae1Axk5RQXRQk4VNw==", "requires": { "body-parser": "^1.19.0", "chalk": "^4.1.1", "express": "^4.17.1", "express-async-errors": "^3.1.1", "helmet": "^4.4.1", - "missing-native-js-functions": "^1.1.8" + "missing-native-js-functions": "^1.2.11" } }, "lazystream": { @@ -18009,9 +18013,9 @@ } }, "missing-native-js-functions": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.10.tgz", - "integrity": "sha512-sq+oAw/C3OtUyKopLNOf/+U85YNx7db6fy5nVfGVKlGdcV8tX24GjOSkcZeCAnAIjMEnlQBWTr17JXa3OJj22g==" + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.11.tgz", + "integrity": "sha512-U97IscNBL4Wg9adYjEBT46Hb0Ld5dPT8vbdwFX+TNzGrFQCc4WqoGAZouaLNFwUqxzzHZ9DVg59unwnQyeIIQg==" }, "mixin-deep": { "version": "1.3.2", diff --git a/api/package.json b/api/package.json
index 1310d577..eef2d069 100644 --- a/api/package.json +++ b/api/package.json
@@ -74,8 +74,8 @@ "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", - "lambert-server": "^1.2.8", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts
index 8e2cd923..0ed37bb4 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts
@@ -4,7 +4,7 @@ import { FieldError } from "../util/instanceOf"; // TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { - if (!error) next(); + if (!error) return next(); try { let code = 400; @@ -18,7 +18,6 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne message = error.message; errors = error.errors; } else { - console.error(error); if (req.server?.options?.production) { message = "Internal Server Error"; } @@ -27,7 +26,7 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne if (httpcode > 511) httpcode = 400; - console.error(`[Error] ${code} ${req.url} ${message}`, errors || error); + console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); res.status(httpcode).json({ code: code, message, errors }); } catch (error) { diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts
index 9601dad3..e0cf103a 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts
@@ -1,6 +1,6 @@ -// @ts-nocheck -import { db, Bucket, Config, listenEvent, emitEvent } from "@fosscord/util"; +import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; +import { LessThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -18,10 +18,10 @@ TODO: different for methods (GET/POST) */ -var Cache = new Map<string, Bucket>(); -const EventRateLimit = "ratelimit"; +var Cache = new Map<string, RateLimit>(); +const EventRateLimit = "RATELIMIT"; -export default function RateLimit(opts: { +export default function rateLimit(opts: { bucket?: string; window: number; count: number; @@ -36,15 +36,15 @@ export default function RateLimit(opts: { }): any { return async (req: Request, res: Response, next: NextFunction): Promise<any> => { const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - var user_id = getIpAdress(req); - if (!opts.onlyIp && req.user_id) user_id = req.user_id; + var executor_id = getIpAdress(req); + if (!opts.onlyIp && req.user_id) executor_id = req.user_id; var max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; - const offender = Cache.get(user_id + bucket_id) as Bucket | null; + const offender = Cache.get(executor_id + bucket_id); if (offender && offender.blocked) { const reset = offender.expires_at.getTime(); @@ -72,12 +72,12 @@ export default function RateLimit(opts: { offender.expires_at = new Date(Date.now() + opts.window * 1000); offender.blocked = false; // mongodb ttl didn't update yet -> manually update/delete - db.collection("ratelimits").update({ id: bucket_id, user_id }, { $set: offender }); - Cache.delete(user_id + bucket_id); + RateLimit.delete({ id: bucket_id, executor_id }); + Cache.delete(executor_id + bucket_id); } } next(); - const hitRouteOpts = { bucket_id, user_id, max_hits, window: opts.window }; + const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window }; if (opts.error || opts.success) { res.once("finish", () => { @@ -97,69 +97,74 @@ export default function RateLimit(opts: { export async function initRateLimits(app: Router) { const { routes, global, ip, error } = Config.get().limits.rate; await listenEvent(EventRateLimit, (event) => { - Cache.set(event.channel_id, event.data); + Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); + await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted + const limits = await RateLimit.find({ blocked: true }); + limits.forEach((limit) => { + Cache.set(limit.executor_id, limit); + }); setInterval(() => { Cache.forEach((x, key) => { - if (Date.now() > x.expires_at) Cache.delete(key); + if (new Date() > x.expires_at) { + Cache.delete(key); + RateLimit.delete({ executor_id: key }); + } }); }, 1000 * 60 * 10); app.use( - RateLimit({ + rateLimit({ bucket: "global", onlyIp: true, ...ip }) ); - app.use(RateLimit({ bucket: "global", ...global })); + app.use(rateLimit({ bucket: "global", ...global })); app.use( - RateLimit({ + rateLimit({ bucket: "error", error: true, onlyIp: true, ...error }) ); - app.use("/guilds/:id", RateLimit(routes.guild)); - app.use("/webhooks/:id", RateLimit(routes.webhook)); - app.use("/channels/:id", RateLimit(routes.channel)); - app.use("/auth/login", RateLimit(routes.auth.login)); - app.use("/auth/register", RateLimit({ onlyIp: true, success: true, ...routes.auth.register })); + app.use("/guilds/:id", rateLimit(routes.guild)); + app.use("/webhooks/:id", rateLimit(routes.webhook)); + app.use("/channels/:id", rateLimit(routes.channel)); + app.use("/auth/login", rateLimit(routes.auth.login)); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: number; window: number }) { - const filter = { id: opts.bucket_id, user_id: opts.user_id }; - const { value } = await db.collection("ratelimits").findOneOrFailAndUpdate( - filter, - { - $setOnInsert: { - id: opts.bucket_id, - user_id: opts.user_id, - expires_at: new Date(Date.now() + opts.window * 1000) - }, - $inc: { - hits: 1 - } - // Conditionally update blocked doesn't work - }, - { upsert: true, returnDocument: "before" } - ); - if (!value) return; - const updateBlock = !value.blocked && value.hits >= opts.max_hits; +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { + var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); + if (!ratelimit) { + ratelimit = new RateLimit({ + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false + }); + } + + ratelimit.hits++; + + const updateBlock = !ratelimit.blocked && ratelimit.hits >= opts.max_hits; if (updateBlock) { - value.blocked = true; - Cache.set(opts.user_id + opts.bucket_id, value); + ratelimit.blocked = true; + Cache.set(opts.executor_id + opts.bucket_id, ratelimit); await emitEvent({ channel_id: EventRateLimit, event: EventRateLimit, - data: value + data: ratelimit }); - await db.collection("ratelimits").update(filter, { $set: { blocked: true } }); } else { - Cache.delete(opts.user_id); + Cache.delete(opts.executor_id); } + + await ratelimit.save(); } diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts
index c0acad4e..7fd0f870 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts
@@ -21,7 +21,7 @@ router.post( async (req: Request, res: Response) => { const { login, password, captcha_key, undelete } = req.body; const email = adjustEmail(login); - console.log(req.body, email); + console.log("login", email); const config = Config.get(); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts
index 5ad6d6a4..b0d8c9bd 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts
@@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, Config } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import "missing-native-js-functions"; @@ -182,17 +182,29 @@ router.post( // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false const user = await new User({ + created_at: new Date(), username: adjusted_username, discriminator, + bot: false, + system: false, + desktop: false, + mobile: false, premium: true, premium_type: 2, + bio: "", + mfa_enabled: false, + verified: false, + disabled: false, + deleted: false, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age - guilds: [], + public_flags: "0", + flags: "0", // TODO: generate data: { hash: adjusted_password, valid_tokens_since: new Date() - } + }, + settings: defaultSettings }).save(); return res.json({ token: await generateToken(user.id) }); diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts
index f9bfcc67..bf10c037 100644 --- a/api/src/schema/Message.ts +++ b/api/src/schema/Message.ts
@@ -11,7 +11,7 @@ export const MessageCreateSchema = { $content: new Length(String, 0, 2000), $nonce: String, $tts: Boolean, - $flags: BigInt, + $flags: String, $embed: { $title: new Length(String, 0, 256), //title of embed $type: String, // type of embed (always "rich" for webhook embeds) @@ -69,7 +69,7 @@ export interface MessageCreateSchema { content?: string; nonce?: string; tts?: boolean; - flags?: bigint; + flags?: string; embed?: Embed & { timestamp?: string }; allowed_mentions?: { parse?: string[];