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/locales/de/auth.json b/src/locales/de/auth.json
deleted file mode 100644
index c60fc00d..00000000
--- a/src/locales/de/auth.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "login": {
- "INVALID_LOGIN": "Ungültige E-Mail oder Telefonnummer",
- "INVALID_PASSWORD": "Ungültiges Passwort"
- },
- "register": {
- "REGISTRATION_DISABLED": "Neue Nutzer können sich nicht mehr registrieren",
- "INVITE_ONLY": "Du musst eingeladen werden, um dich zu registrieren",
- "EMAIL_INVALID": "Ungültige E-Mail Adresse",
- "EMAIL_ALREADY_REGISTERED": "Es existiert bereits ein Account mit dieser E-Mail Adresse",
- "DATE_OF_BIRTH_UNDERAGE": "Du musst mindestens {{years}} Jahre alt sein",
- "CONSENT_REQUIRED": "Du musst den AGB's und Datenschutzbestimmungen zustimmen",
- "USERNAME_TOO_MANY_USERS": "Es haben bereits zu viele Nutzer den gleichen Nutzernamen"
- }
-}
\ No newline at end of file
diff --git a/src/locales/de/common.json b/src/locales/de/common.json
deleted file mode 100644
index 36c24671..00000000
--- a/src/locales/de/common.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "field": {
- "BASE_TYPE_REQUIRED": "Dieses Feld ist erforderlich",
- "BASE_TYPE_STRING": "Dieses Feld muss ein Text sein",
- "BASE_TYPE_NUMBER": "Dieses Feld muss eine Nummer sein",
- "BASE_TYPE_BIGINT": "Dieses Feld muss eine Nummer sein",
- "BASE_TYPE_BOOLEAN": "Diese Feld muss true oder false sein",
- "BASE_TYPE_CHOICES": "Dieses Feld muss ({{types}}) sein",
- "BASE_TYPE_CLASS": "Dieses Feld muss ein {{type}} sein",
- "BASE_TYPE_OBJECT": "Dieses Feld muss ein Objekt sein",
- "BASE_TYPE_ARRAY": "Dieses Feld muss ein Array sein",
- "UNKOWN_FIELD": "Unbekannter Wert: {{key}}",
- "BASE_TYPE_CONSTANT": "Dieses Feld muss {{value}} sein",
- "EMAIL_TYPE_INVALID_EMAIL": "Keine gültige E-Mail Adresse",
- "DATE_TYPE_PARSE": "Ungültiges Datum {{date}}, muss dem ISO8601 Standard entsprechen",
- "BASE_TYPE_BAD_LENGTH": "Muss {{length}} lang sein"
- }
-}
\ No newline at end of file
diff --git a/src/locales/en/auth.json b/src/locales/en/auth.json
deleted file mode 100644
index ea5633df..00000000
--- a/src/locales/en/auth.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "login": {
- "INVALID_LOGIN": "Invalid E-Mail or Phone",
- "INVALID_PASSWORD": "Invalid Password"
- },
- "register": {
- "REGISTRATION_DISABLED": "New user registration is disabled",
- "INVITE_ONLY": "You must be invited to register",
- "EMAIL_INVALID": "Invalid Email",
- "EMAIL_ALREADY_REGISTERED": "Email is already registered",
- "DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} or older",
- "CONSENT_REQUIRED": "You must agree to Terms of Service and Privacy Policy.",
- "USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
- }
-}
\ No newline at end of file
diff --git a/src/locales/en/common.json b/src/locales/en/common.json
deleted file mode 100644
index 7a0254fc..00000000
--- a/src/locales/en/common.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "field": {
- "BASE_TYPE_REQUIRED": "This field is required",
- "BASE_TYPE_STRING": "This field must be a string",
- "BASE_TYPE_NUMBER": "This field must be a number",
- "BASE_TYPE_BIGINT": "This field must be a bigint",
- "BASE_TYPE_BOOLEAN": "This field must be a boolean",
- "BASE_TYPE_CHOICES": "This field must be one of ({{types}})",
- "BASE_TYPE_CLASS": "This field must be an instance of {{type}}",
- "BASE_TYPE_OBJECT": "This field must be a object",
- "BASE_TYPE_ARRAY": "This field must be an array",
- "UNKOWN_FIELD": "Unkown key: {{key}}",
- "BASE_TYPE_CONSTANT": "This field must be {{value}}",
- "EMAIL_TYPE_INVALID_EMAIL": "Not a well formed email address",
- "DATE_TYPE_PARSE": "Could not parse {{date}}. Should be ISO8601",
- "BASE_TYPE_BAD_LENGTH": "Must be between {{length}} in length"
- }
-}
\ No newline at end of file
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
|