summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--locales/de/auth.json (renamed from src/locales/de/auth.json)0
-rw-r--r--locales/de/common.json (renamed from src/locales/de/common.json)0
-rw-r--r--locales/en/auth.json (renamed from src/locales/en/auth.json)0
-rw-r--r--locales/en/common.json (renamed from src/locales/en/common.json)0
-rw-r--r--package-lock.json73
-rw-r--r--package.json7
-rw-r--r--src/Server.ts36
-rw-r--r--src/index.ts3
-rw-r--r--src/middlewares/Authentication.ts2
-rw-r--r--src/middlewares/BodyParser.ts17
-rw-r--r--src/middlewares/GlobalRateLimit.ts8
-rw-r--r--src/test/mongo_test.ts7
-rw-r--r--src/util/String.ts20
-rw-r--r--src/util/instanceOf.ts6
14 files changed, 143 insertions, 36 deletions
diff --git a/src/locales/de/auth.json b/locales/de/auth.json

index c60fc00d..c60fc00d 100644 --- a/src/locales/de/auth.json +++ b/locales/de/auth.json
diff --git a/src/locales/de/common.json b/locales/de/common.json
index 36c24671..36c24671 100644 --- a/src/locales/de/common.json +++ b/locales/de/common.json
diff --git a/src/locales/en/auth.json b/locales/en/auth.json
index ea5633df..ea5633df 100644 --- a/src/locales/en/auth.json +++ b/locales/en/auth.json
diff --git a/src/locales/en/common.json b/locales/en/common.json
index 7a0254fc..7a0254fc 100644 --- a/src/locales/en/common.json +++ b/locales/en/common.json
diff --git a/package-lock.json b/package-lock.json
index b365af4b..3d022326 100644 --- a/package-lock.json +++ b/package-lock.json
@@ -70,6 +70,15 @@ "integrity": "sha512-2uEQFb7bsx68rqD4F8q95wZq6LTLOyexjv6BnvJogCO4jStkyc6IDEkODPQcWfovI6g6M3uPQ2/uD/oedJKkNw==", "dev": true }, + "@types/i18next-node-fs-backend": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.0.tgz", + "integrity": "sha512-bOOeT89UO/bYLJoQHdN5S3pggj7mMmFfQMBpDdUQOQIQkENGpnTwhNsIM/kjl1NE2HEihjlRZUNVV60Ze86UZA==", + "dev": true, + "requires": { + "i18next": ">=17.0.11" + } + }, "@types/jsonwebtoken": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", @@ -202,6 +211,14 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -468,6 +485,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -663,6 +685,11 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "helmet": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.4.1.tgz", + "integrity": "sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw==" + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -712,6 +739,15 @@ "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.0.tgz", "integrity": "sha512-65rP8bi5b7znBzfgIUy0KE00SWg1X6mL5XEkassgTrjAeLSfSb4vQ2bs9cN3qwHCynKIpmHjmNDu5c8NylTVmw==" }, + "i18next-node-fs-backend": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.3.tgz", + "integrity": "sha512-CreMFiVl3ChlMc5ys/e0QfuLFOZyFcL40Jj6jaKD6DxZ/GCUMxPI9BpU43QMWUgC7r+PClpxg2cGXAl0CjG04g==", + "requires": { + "js-yaml": "3.13.1", + "json5": "2.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -775,6 +811,23 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json5": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.0.0.tgz", + "integrity": "sha512-0EdQvHuLm7yJ7lyG5dp7Q3X2ku++BG5ZHaJ5FTnaXpKqDrw4pMxel5Bt3oAYMthnrthFBdnZ1FcsXTPyrQlV0w==", + "requires": { + "minimist": "^1.2.0" + } + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -855,14 +908,15 @@ } }, "lambert-server": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.0.9.tgz", - "integrity": "sha512-5Msp/wBxUUK/PeM7YwyZSPMS/MfbfNNiphUB4uEHiYUDMxE/b4jr3ek0SSRuWNBm3+3FwlUO6qkNYMixEoeqvA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.0.10.tgz", + "integrity": "sha512-hhikhfjbU4w8D8J7Jif5DO3caqFCjtrJ4+coyOkQooz39cHM7ghHMIGSNvvQ7aee1pL71vBjuBRS/0HKO3wagA==", "requires": { "body-parser": "^1.19.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", - "missing-native-js-functions": "^1.1.6" + "helmet": "^4.4.1", + "missing-native-js-functions": "^1.1.8" } }, "locate-path": { @@ -1032,9 +1086,9 @@ } }, "missing-native-js-functions": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.1.9.tgz", - "integrity": "sha512-lMhbzzN1pKHQ4HzqooBkO6Yf4/krT0YaYwJLuAaHExF1l49qVw8xmYSxOOgWCRk/i8veyHin0pCI9rwBXWh+qA==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.0.tgz", + "integrity": "sha512-PpmpGJA3n3t3qwetmCXUwmLPi9Hd2d7mHbHw6+BwsxXNOVyQTBv397x+FwbY3mJBVWPhSEGzR51egmx4ZZm2Xg==" }, "mkdirp": { "version": "1.0.4", @@ -1640,6 +1694,11 @@ "memory-pager": "^1.0.2" } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", diff --git a/package.json b/package.json
index 47f764f6..6f808d29 100644 --- a/package.json +++ b/package.json
@@ -21,18 +21,20 @@ "@types/express": "^4.17.9", "@types/node-fetch": "^2.5.7", "bcrypt": "^5.0.0", + "body-parser": "^1.19.0", "express": "^4.17.1", "express-cache-middleware": "^1.0.1", "express-validator": "^6.9.2", "faker": "^5.1.0", "i18next": "^19.8.5", "i18next-http-middleware": "^3.1.0", + "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", "jwa": "^2.0.0", "jws": "^4.0.0", "lambert-db": "^1.1.3", - "lambert-server": "^1.0.9", - "missing-native-js-functions": "^1.1.9", + "lambert-server": "^1.0.10", + "missing-native-js-functions": "^1.2.0", "mongoose": "^5.11.14", "node-fetch": "^2.6.1", "rethinkdb-ts": "^2.4.5" @@ -40,6 +42,7 @@ "devDependencies": { "@types/bcrypt": "^3.0.0", "@types/faker": "^5.1.5", + "@types/i18next-node-fs-backend": "^2.1.0", "@types/jsonwebtoken": "^8.5.0", "@types/jws": "^3.2.3", "@types/node": "^14.14.22", diff --git a/src/Server.ts b/src/Server.ts
index 15e70b7f..729531ef 100644 --- a/src/Server.ts +++ b/src/Server.ts
@@ -4,14 +4,17 @@ import { Authentication, GlobalRateLimit } from "./middlewares/"; import Config from "./util/Config"; import db from "./util/Database"; import i18next from "i18next"; -import i18nextMiddleware from "i18next-http-middleware"; -import { Request } from "express"; +import i18nextMiddleware, { I18next } from "i18next-http-middleware"; +import i18nextBackend from "i18next-node-fs-backend"; +import { ErrorHandler } from "./middlewares/ErrorHandler"; +import { BodyParser } from "./middlewares/BodyParser"; export interface DiscordServerOptions extends ServerOptions {} declare global { namespace Express { interface Request { + // @ts-ignore server: DiscordServer; } } @@ -21,7 +24,8 @@ export class DiscordServer extends Server { public options: DiscordServerOptions; constructor(opts?: Partial<DiscordServerOptions>) { - super({ ...opts, errorHandler: false }); + // @ts-ignore + super({ ...opts, errorHandler: false, jsonBody: false }); } async start() { @@ -31,20 +35,28 @@ export class DiscordServer extends Server { this.app.use(GlobalRateLimit); this.app.use(Authentication); - const namespaces = await fs.readdir(__dirname + "/locales/de/"); + this.app.use(BodyParser({ inflate: true })); + const languages = await fs.readdir(__dirname + "/../locales/"); + const namespaces = await fs.readdir(__dirname + "/../locales/en/"); const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); - i18next.use(i18nextMiddleware.LanguageDetector).init({ - preload: ["en", "de"], - fallbackLng: "en", - ns, - backend: { - loadPath: "locales/{{lng}}/{{ns}}.json", - }, - }); + await i18next + .use(i18nextBackend) + .use(i18nextMiddleware.LanguageDetector) + .init({ + preload: languages, + // debug: true, + fallbackLng: "en", + ns, + backend: { + loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json", + }, + load: "all", + }); this.app.use(i18nextMiddleware.handle(i18next, {})); this.routes = await this.registerRoutes(__dirname + "/routes/"); + this.app.use(ErrorHandler); const indexHTML = await fs.readFile(__dirname + "/../client_test/index.html"); this.app.get("*", (req, res) => { diff --git a/src/index.ts b/src/index.ts
index 9299221c..f4be661d 100644 --- a/src/index.ts +++ b/src/index.ts
@@ -6,3 +6,6 @@ import { DiscordServer } from "./Server"; const server = new DiscordServer({ port: 3000 }); server.start().catch(console.error); + +// @ts-ignore +global.server = server; diff --git a/src/middlewares/Authentication.ts b/src/middlewares/Authentication.ts
index 5a1241f3..8fbae122 100644 --- a/src/middlewares/Authentication.ts +++ b/src/middlewares/Authentication.ts
@@ -19,7 +19,7 @@ export function Authentication(req: Request, res: Response, next: NextFunction) if (NO_AUTHORIZATION_ROUTES.includes(req.url)) return next(); if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401)); - return jwt.verify(req.headers.authorization, Config.get().server.jwtSecret, JWTOptions, (err, decoded: any) => { + return jwt.verify(req.headers.authorization, Config.get().security.jwtSecret, JWTOptions, (err, decoded: any) => { if (err || !decoded) return next(new HTTPError("Invalid Token", 401)); req.token = decoded; diff --git a/src/middlewares/BodyParser.ts b/src/middlewares/BodyParser.ts new file mode 100644
index 00000000..b0ff699d --- /dev/null +++ b/src/middlewares/BodyParser.ts
@@ -0,0 +1,17 @@ +import bodyParser, { OptionsJson } from "body-parser"; +import { NextFunction, Request, Response } from "express"; +import { HTTPError } from "lambert-server"; + +export function BodyParser(opts?: OptionsJson) { + const jsonParser = bodyParser.json(opts); + + return (req: Request, res: Response, next: NextFunction) => { + jsonParser(req, res, (err) => { + if (err) { + // TODO: different errors for body parser (request size limit, wrong body type, invalid body, ...) + return next(new HTTPError("Invalid Body", 400)); + } + next(); + }); + }; +} diff --git a/src/middlewares/GlobalRateLimit.ts b/src/middlewares/GlobalRateLimit.ts
index 5c5f690a..8fbfbd5c 100644 --- a/src/middlewares/GlobalRateLimit.ts +++ b/src/middlewares/GlobalRateLimit.ts
@@ -3,16 +3,16 @@ import Config from "../util/Config"; import db from "../util/Database"; export async function GlobalRateLimit(req: Request, res: Response, next: NextFunction) { - if (!Config.get().server.ipRateLimit.enabled) return next(); + if (!Config.get().limits.rate.ip.enabled) return next(); const ip = getIpAdress(req); let limit = (await db.data.ratelimit.global[ip].get()) || { start: Date.now(), count: 0 }; - if (limit.start < Date.now() - Config.get().server.ipRateLimit.timespan) { + if (limit.start < Date.now() - Config.get().limits.rate.ip.timespan) { limit.start = Date.now(); limit.count = 0; } - if (limit.count > Config.get().server.ipRateLimit.count) { + if (limit.count > Config.get().limits.rate.ip.count) { const timespan = Date.now() - limit.start; return res @@ -37,7 +37,7 @@ export async function GlobalRateLimit(req: Request, res: Response, next: NextFun } export function getIpAdress(req: Request): string { - const { forwadedFor } = Config.get().server; + const { forwadedFor } = Config.get().security; const ip = forwadedFor ? <string>req.headers[forwadedFor] : req.ip; return ip.replaceAll(".", "_").replaceAll(":", "_"); } diff --git a/src/test/mongo_test.ts b/src/test/mongo_test.ts
index ad290198..d6906402 100644 --- a/src/test/mongo_test.ts +++ b/src/test/mongo_test.ts
@@ -1,4 +1,6 @@ import mongoose from "mongoose"; +import { Long } from "mongodb"; +import { Snowflake } from "../util/Snowflake"; async function main() { const conn = await mongoose.createConnection( @@ -9,10 +11,7 @@ async function main() { } ); console.log("connected"); - const result = await conn - .collection("users") - .find({ $or: [{ email: "samuel.scheit@gmail.com" }, { phone: "samuel.scheit@gmail.com" }] }) - .toArray(); + const result = await conn.collection("users").insertOne({ test: Long.fromString(Snowflake.generate().toString()) }); // .project(undefined) console.log(result); diff --git a/src/util/String.ts b/src/util/String.ts
index e7f014eb..fa93f1b7 100644 --- a/src/util/String.ts +++ b/src/util/String.ts
@@ -1,6 +1,20 @@ -export const WHITE_SPACE = /\s\s+/g; +import { Request } from "express"; +import { FieldError, FieldErrors } from "./instanceOf"; + +export const DOUBLE_WHITE_SPACE = /\s\s+/g; export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; -export function trim(str: string) { - return str.replace(SPECIAL_CHAR, "").replace(WHITE_SPACE, " ").trim(); +export function trimSpecial(str: string) { + return str.replace(SPECIAL_CHAR, "").replace(DOUBLE_WHITE_SPACE, " ").trim(); +} + +export function checkLength(str: string, min: number, max: number, key: string, req: Request) { + if (str.length < min || str.length > max) { + throw FieldErrors({ + [key]: { + code: "BASE_TYPE_BAD_LENGTH", + message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` }), + }, + }); + } } diff --git a/src/util/instanceOf.ts b/src/util/instanceOf.ts
index 341374b7..5035e4c9 100644 --- a/src/util/instanceOf.ts +++ b/src/util/instanceOf.ts
@@ -95,7 +95,7 @@ export function instanceOf( return true; throw new FieldError("BASE_TYPE_CHOICES", t("common:field.BASE_TYPE_CHOICES", { types: type.types })); case Email: - if ((<Email>type).check()) return true; + if (new Email(value).check()) return true; throw new FieldError("EMAIL_TYPE_INVALID_EMAIL", t("common:field.EMAIL_TYPE_INVALID_EMAIL")); case Date: value = new Date(value); @@ -143,13 +143,13 @@ export function instanceOf( let newKey = key; const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); - errors[key] = {}; + errors[newKey] = {}; return ( instanceOf(type[key], value[newKey], { path: `${path}.${newKey}`, optional: OPTIONAL, - errors: errors[key], + errors: errors[newKey], t, ref: { key: newKey, obj: value }, }) === true