summary refs log tree commit diff
diff options
context:
space:
mode:
-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
-rw-r--r--bundle/database.dbbin0 -> 225280 bytes
-rw-r--r--bundle/package-lock.json60
-rw-r--r--bundle/package.json3
-rw-r--r--bundle/src/Database.ts44
-rw-r--r--bundle/src/Server.ts4
-rw-r--r--bundle/src/start.ts2
-rw-r--r--cdn/src/Server.ts5
-rw-r--r--util/src/entities/Guild.ts2
-rw-r--r--util/src/entities/Message.ts4
-rw-r--r--util/src/entities/RateLimit.ts8
-rw-r--r--util/src/entities/Role.ts1
-rw-r--r--util/src/entities/User.ts38
-rw-r--r--util/src/entities/index.ts1
-rw-r--r--util/src/interfaces/Event.ts2
-rw-r--r--util/src/util/Config.ts4
-rw-r--r--util/src/util/Database.ts12
-rw-r--r--util/src/util/checkToken.ts3
24 files changed, 178 insertions, 183 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[]; diff --git a/bundle/database.db b/bundle/database.db new file mode 100644
index 00000000..9572c45e --- /dev/null +++ b/bundle/database.db
Binary files differdiff --git a/bundle/package-lock.json b/bundle/package-lock.json
index 379b1c18..9fcb5490 100644 --- a/bundle/package-lock.json +++ b/bundle/package-lock.json
@@ -63,19 +63,21 @@ "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", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "typeorm": "^0.2.37" }, "devDependencies": { "@types/amqplib": "^0.8.1", "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", + "@types/jest": "^27.0.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -111,11 +113,13 @@ "file-type": "^16.5.0", "fs-extra": "^10.0.0", "image-size": "^1.0.0", + "jest": "^27.0.6", "lambert-db": "^1.2.3", "lambert-server": "^1.2.8", "missing-native-js-functions": "^1.2.10", "multer": "^1.4.2", "node-fetch": "^2.6.1", + "supertest": "^6.1.6", "typescript": "^4.1.2", "uuid": "^8.3.2" }, @@ -173,26 +177,30 @@ "hasInstallScript": true, "license": "GPLV3", "dependencies": { - "ajv": "^8.5.0", + "ajv": "^8.6.2", "amqplib": "^0.8.0", + "class-validator": "^0.13.1", "dot-prop": "^6.0.1", "env-paths": "^2.2.1", "jsonwebtoken": "^8.5.1", - "missing-native-js-functions": "^1.2.10", - "mongodb": "^3.6.9", - "mongoose": "^5.13.7", - "mongoose-autopopulate": "^0.12.3", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "node-fetch": "^2.6.1", - "typescript": "^4.1.3" + "patch-package": "^6.4.7", + "pg": "^8.7.1", + "reflect-metadata": "^0.1.13", + "sqlite3": "^5.0.2", + "typeorm": "^0.2.37", + "typescript": "^4.3.5", + "typescript-json-schema": "^0.50.1" }, "devDependencies": { "@types/amqplib": "^0.8.1", "@types/jsonwebtoken": "^8.5.0", - "@types/mongodb": "^3.6.9", "@types/mongoose-autopopulate": "^0.10.1", - "@types/mongoose-lean-virtuals": "^0.5.1", "@types/node": "^14.17.9", - "@types/node-fetch": "^2.5.12" + "@types/node-fetch": "^2.5.12", + "jest": "^27.0.6" } }, "node_modules/@babel/runtime": { @@ -1868,6 +1876,7 @@ "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", + "@types/jest": "^27.0.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -1899,8 +1908,8 @@ "image-size": "^1.0.0", "jest": "^26.6.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", @@ -1909,6 +1918,7 @@ "saslprep": "^1.0.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", + "typeorm": "^0.2.37", "typescript": "^4.1.2" } }, @@ -1940,11 +1950,13 @@ "file-type": "^16.5.0", "fs-extra": "^10.0.0", "image-size": "^1.0.0", + "jest": "^27.0.6", "lambert-db": "^1.2.3", "lambert-server": "^1.2.8", "missing-native-js-functions": "^1.2.10", "multer": "^1.4.2", "node-fetch": "^2.6.1", + "supertest": "^6.1.6", "typescript": "^4.1.2", "uuid": "^8.3.2" } @@ -1981,22 +1993,26 @@ "requires": { "@types/amqplib": "^0.8.1", "@types/jsonwebtoken": "^8.5.0", - "@types/mongodb": "^3.6.9", "@types/mongoose-autopopulate": "^0.10.1", - "@types/mongoose-lean-virtuals": "^0.5.1", "@types/node": "^14.17.9", "@types/node-fetch": "^2.5.12", - "ajv": "^8.5.0", + "ajv": "^8.6.2", "amqplib": "^0.8.0", + "class-validator": "^0.13.1", "dot-prop": "^6.0.1", "env-paths": "^2.2.1", + "jest": "^27.0.6", "jsonwebtoken": "^8.5.1", - "missing-native-js-functions": "^1.2.10", - "mongodb": "^3.6.9", - "mongoose": "^5.13.7", - "mongoose-autopopulate": "^0.12.3", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "node-fetch": "^2.6.1", - "typescript": "^4.1.3" + "patch-package": "^6.4.7", + "pg": "^8.7.1", + "reflect-metadata": "^0.1.13", + "sqlite3": "^5.0.2", + "typeorm": "^0.2.37", + "typescript": "^4.3.5", + "typescript-json-schema": "^0.50.1" } }, "@types/amqplib": { diff --git a/bundle/package.json b/bundle/package.json
index 38377d15..71d7efea 100644 --- a/bundle/package.json +++ b/bundle/package.json
@@ -5,8 +5,9 @@ "main": "src/start.js", "scripts": { "preinstall": "cd ../util && npm i && cd ../api && npm i && cd ../cdn && npm i && cd ../gateway && npm i", - "build": "npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle", + "build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle", "build:bundle": "npx tsc -b .", + "build:util": "cd ../util/ && npm run build", "build:api": "cd ../api/ && npm run build", "build:cdn": "cd ../cdn/ && npm run build", "build:gateway": "cd ../gateway/ && npm run build", diff --git a/bundle/src/Database.ts b/bundle/src/Database.ts deleted file mode 100644
index 12febc1c..00000000 --- a/bundle/src/Database.ts +++ /dev/null
@@ -1,44 +0,0 @@ -import fs from "fs"; -import { MongoMemoryServer } from "mongodb-memory-server"; -import path from "path"; -import exitHook from "async-exit-hook"; - -if (process.arch == "ia32") { - Object.defineProperty(process, "arch", { - value: "x64", - }); -} - -export async function setupDatabase() { - if (process.env.MONGO_URL) return; // exit because the user provides his own mongodb - const dbPath = path.join(__dirname, "..", "..", "db"); - const dbName = "fosscord"; - const storageEngine = "wiredTiger"; - const port = 27020; - const ip = "127.0.0.1"; - var mongod: MongoMemoryServer; - fs.mkdirSync(dbPath, { recursive: true }); - - exitHook((callback: any) => { - (async () => { - console.log(`Stopping MongoDB ...`); - await mongod.stop(); - console.log(`Stopped MongoDB`); - callback(); - })(); - }); - - console.log(`[Database] starting ...`); - mongod = new MongoMemoryServer({ - instance: { - port, - ip, - dbName, - dbPath, - storageEngine, - auth: false, // by default `mongod` is started with '--noauth', start `mongod` with '--auth' - }, - }); - await mongod.start(); - process.env.MONGO_URL = mongod.getUri(dbName); -} diff --git a/bundle/src/Server.ts b/bundle/src/Server.ts
index e0586601..56c82cd1 100644 --- a/bundle/src/Server.ts +++ b/bundle/src/Server.ts
@@ -6,7 +6,7 @@ import { FosscordServer as APIServer } from "@fosscord/api"; import { Server as GatewayServer } from "@fosscord/gateway"; import { CDNServer } from "@fosscord/cdn/"; import express from "express"; -import { Config } from "@fosscord/util"; +import { Config, initDatabase } from "@fosscord/util"; const app = express(); const server = http.createServer(); @@ -22,6 +22,8 @@ const cdn = new CDNServer({ server, port, production, app }); const gateway = new GatewayServer({ server, port, production }); async function main() { + await initDatabase(); + await Config.init(); await Config.set({ cdn: { endpointClient: "${location.host}", diff --git a/bundle/src/start.ts b/bundle/src/start.ts
index 323995ae..843e3812 100644 --- a/bundle/src/start.ts +++ b/bundle/src/start.ts
@@ -1,7 +1,6 @@ // process.env.MONGOMS_DEBUG = "true"; import cluster from "cluster"; import os from "os"; -import { setupDatabase } from "./Database"; import { initStats } from "./stats"; // TODO: add tcp socket event transmission @@ -12,7 +11,6 @@ if (cluster.isMaster && !process.env.masterStarted) { (async () => { initStats(); - await setupDatabase(); if (cores === 1) { require("./Server.js"); diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts
index 522e11c2..f4a6b576 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts
@@ -1,5 +1,5 @@ import { Server, ServerOptions } from "lambert-server"; -import { Config, db } from "@fosscord/util"; +import { Config, initDatabase } from "@fosscord/util"; import path from "path"; import avatarsRoute from "./routes/avatars"; @@ -13,8 +13,7 @@ export class CDNServer extends Server { } async start() { - // @ts-ignore - await (db as Promise<Connection>); + await initDatabase(); await Config.init(); this.app.use((req, res, next) => { res.set("Access-Control-Allow-Origin", "*"); diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 9ca4b1e4..e6a93824 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts
@@ -158,7 +158,7 @@ export class Guild extends BaseClass { vanity_url_code?: string; @JoinColumn({ name: "vanity_url_code" }) - @OneToOne(() => Invite, (invite: Invite) => invite.code) + @ManyToOne(() => Invite) vanity_url?: Invite; @Column({ nullable: true }) diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 0c41a2eb..43d0f9d0 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts
@@ -148,8 +148,8 @@ export class Message extends BaseClass { party_id: string; }; - @Column({ type: "bigint", nullable: true }) - flags?: bigint; + @Column({ nullable: true }) + flags?: string; @RelationId((message: Message) => message.stickers) sticker_ids: string[]; diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts
index 3ac35df3..49af0416 100644 --- a/util/src/entities/RateLimit.ts +++ b/util/src/entities/RateLimit.ts
@@ -7,12 +7,8 @@ export class RateLimit extends BaseClass { @Column() id: "global" | "error" | string; // channel_239842397 | guild_238927349823 | webhook_238923423498 - @RelationId((rate_limit: RateLimit) => rate_limit.user) - user_id: string; - - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, (user) => user.id) - user: User; + @Column() // no relation as it also + executor_id: string; @Column() hits: number; diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts
index 7c6ce64e..ddae7e40 100644 --- a/util/src/entities/Role.ts +++ b/util/src/entities/Role.ts
@@ -1,4 +1,5 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; + import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index c5f870fa..73afba67 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts
@@ -49,7 +49,7 @@ export class User extends BaseClass { avatar?: string; // hash of the user avatar @Column({ nullable: true }) - accent_color?: number = 0; // banner color of user + accent_color?: number; // banner color of user @Column({ nullable: true }) banner?: string; // hash of the user banner @@ -58,52 +58,52 @@ export class User extends BaseClass { phone?: string; // phone number of the user @Column() - desktop: boolean = false; // if the user has desktop app installed + desktop: boolean; // if the user has desktop app installed @Column() - mobile: boolean = false; // if the user has mobile app installed + mobile: boolean; // if the user has mobile app installed @Column() - premium: boolean = false; // if user bought nitro + premium: boolean; // if user bought nitro @Column() - premium_type: number = 0; // nitro level + premium_type: number; // nitro level @Column() - bot: boolean = false; // if user is bot + bot: boolean; // if user is bot @Column() - bio: string = ""; // short description of the user (max 190 chars -> should be configurable) + bio: string; // short description of the user (max 190 chars -> should be configurable) @Column() - system: boolean = false; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author + system: boolean; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author @Column() - nsfw_allowed: boolean = false; // if the user is older than 18 (resp. Config) + nsfw_allowed: boolean; // if the user is older than 18 (resp. Config) @Column() - mfa_enabled: boolean = false; // if multi factor authentication is enabled + mfa_enabled: boolean; // if multi factor authentication is enabled @Column() created_at: Date = new Date(); // registration date @Column() - verified: boolean = false; // if the user is offically verified + verified: boolean; // if the user is offically verified @Column() - disabled: boolean = false; // if the account is disabled + disabled: boolean; // if the account is disabled @Column() - deleted: boolean = false; // if the user was deleted + deleted: boolean; // if the user was deleted @Column({ nullable: true }) email?: string; // email of the user - @Column({ type: "bigint" }) - flags: bigint = BigInt(0); // UserFlags + @Column() + flags: string; // UserFlags - @Column({ type: "bigint" }) - public_flags: bigint = BigInt(0); + @Column() + public_flags: string; @RelationId((user: User) => user.relationships) relationship_ids: string[]; // array of guild ids the user is part of @@ -123,13 +123,13 @@ export class User extends BaseClass { data: { valid_tokens_since: Date; // all tokens with a previous issue date are invalid hash?: string; // hash of the password, salt is saved in password (bcrypt) - } = { valid_tokens_since: new Date() }; + }; @Column({ type: "simple-array" }) fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts @Column({ type: "simple-json" }) - settings: UserSettings = defaultSettings; + settings: UserSettings; static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) { const user = await User.findOne(user_id, { diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index b9e361c1..e0246a10 100644 --- a/util/src/entities/index.ts +++ b/util/src/entities/index.ts
@@ -14,6 +14,7 @@ export * from "./RateLimit"; export * from "./ReadState"; export * from "./Relationship"; export * from "./Role"; +export * from "./Sticker"; export * from "./Team"; export * from "./TeamMember"; export * from "./Template"; diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index c3516c4c..0de55f71 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts
@@ -515,4 +515,4 @@ export type EVENT = | "RELATIONSHIP_REMOVE" | CUSTOMEVENTS; -export type CUSTOMEVENTS = "INVALIDATED"; +export type CUSTOMEVENTS = "INVALIDATED" | "RATELIMIT"; diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
index f8574f38..f16921bd 100644 --- a/util/src/util/Config.ts +++ b/util/src/util/Config.ts
@@ -6,6 +6,7 @@ var config: ConfigEntity; export const Config = { init: async function init() { + if (config) return config; config = new ConfigEntity({}, { id: "0" }); return this.set((config.value || {}).merge(DefaultConfigOptions)); }, @@ -13,7 +14,8 @@ export const Config = { return config.value as ConfigValue; }, set: function set(val: any) { - config.value = val.merge(config.value); + if (!config) return; + config.value = val.merge(config?.value || {}); return config.save(); }, }; diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts
index f49fb04c..c22d8abd 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts
@@ -1,5 +1,5 @@ import "reflect-metadata"; -import { Connection, createConnection } from "typeorm"; +import { Connection, createConnection, ValueTransformer } from "typeorm"; import * as Models from "../entities"; // UUID extension option is only supported with postgres @@ -14,10 +14,10 @@ export function initDatabase() { console.log("[Database] connecting ..."); // @ts-ignore promise = createConnection({ - // type: "sqlite", - // database: "database.db", - type: "postgres", - url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord", + type: "sqlite", + database: "database.db", + // type: "postgres", + // url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord", // entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"), synchronize: true, @@ -25,6 +25,8 @@ export function initDatabase() { cache: { duration: 1000 * 3, // cache all find queries for 3 seconds }, + bigNumberStrings: false, + supportBigNumbers: true, }); promise.then((connection) => { diff --git a/util/src/util/checkToken.ts b/util/src/util/checkToken.ts
index 1e203006..8415e8c0 100644 --- a/util/src/util/checkToken.ts +++ b/util/src/util/checkToken.ts
@@ -12,7 +12,8 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> { const user = await User.findOne({ id: decoded.id }, { select: ["data", "bot", "disabled", "deleted"] }); if (!user) return rej("Invalid Token"); // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds - if (decoded.iat * 1000 < user.data.valid_tokens_since.setSeconds(0, 0)) return rej("Invalid Token"); + if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) + return rej("Invalid Token"); if (user.disabled) return rej("User disabled"); if (user.deleted) return rej("User not found");