From e3f6a29df79865ae9a0d842ba5d59a2851894081 Mon Sep 17 00:00:00 2001 From: Diego Magdaleno Date: Wed, 19 May 2021 20:39:31 -0500 Subject: [PATCH] Config: First rewrite of config and working implementation of getting values --- package-lock.json | Bin 361439 -> 783081 bytes package.json | 8 + src/Server.ts | 4 +- src/middlewares/GlobalRateLimit.ts | 6 +- src/routes/auth/login.ts | 9 +- src/routes/auth/register.ts | 9 +- .../#channel_id/messages/bulk-delete.ts | 5 +- src/routes/channels/#channel_id/pins.ts | 5 +- src/routes/gateway.ts | 7 +- src/routes/guilds/index.ts | 5 +- src/routes/guilds/templates/index.ts | 5 +- src/util/Config.ts | 398 ++++++++++++++---- src/util/Member.ts | 5 +- src/util/passwordStrength.ts | 5 +- 14 files changed, 371 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c41e90cc36dfc9f04a0660a7ee340c9fed6a5e1..7891e38b4e2d76ccb67d479d75cc675edcccd92e 100644 GIT binary patch delta 101983 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 delta 592 zcmXAjT}V@57=ZaaKi@guQMb&k64AASzzm)9CpFC!r{?@e(OEccCw~ieoH{B&lqm2b z<3w*zXr&&H{=s0v@2QmW(CZeYDN#O5a5RTq;UD67>0dKqHS8=u2f){ z@TS|DV!~7mUD}UVKFcKp3v%f+)$2FsVW>}xfZv(aqM!TIiY_NTxfjl4iN;+Jv=BQi zG|(4tXpY9hxSKwP!3p^Ywlz`*ha)P@I0`ilBpN)mBnuwI5FW;Q6(c%s(g|*d;N~sb z8G=`Vjy=ND*;0-HyGy0o6fh49MhNsv;TRl{{Be-O2{PtClQ!aZjcGyY5^M!Rbaa1} z_LySK%Wq); await this.setupSchema(); console.log("[DB] connected"); - await Promise.all([Config.init()]); + //await Promise.all([Config.init()]); this.app.use(GlobalRateLimit); this.app.use(Authentication); diff --git a/src/middlewares/GlobalRateLimit.ts b/src/middlewares/GlobalRateLimit.ts index fc121911..5186ae80 100644 --- a/src/middlewares/GlobalRateLimit.ts +++ b/src/middlewares/GlobalRateLimit.ts @@ -1,5 +1,6 @@ import { NextFunction, Request, Response } from "express"; -import Config from "../util/Config"; +import * as Config from '../util/Config' +import crypto from "crypto"; // TODO: use mongodb ttl index // TODO: increment count on serverside @@ -43,7 +44,8 @@ export async function GlobalRateLimit(req: Request, res: Response, next: NextFun } export function getIpAdress(req: Request): string { - const { forwadedFor } = Config.get().security; + const rateLimitProperties = Config.apiConfig.get('security', {jwtSecret: crypto.randomBytes(256).toString("base64"), forwadedFor: null, captcha: {enabled:false, service: null, sitekey: null, secret: null}}) as Config.DefaultOptions; + const { forwadedFor } = rateLimitProperties.security; const ip = forwadedFor ? req.headers[forwadedFor] : req.ip; return ip.replaceAll(".", "_").replaceAll(":", "_"); } diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts index a0fc1190..218a56ae 100644 --- a/src/routes/auth/login.ts +++ b/src/routes/auth/login.ts @@ -3,7 +3,7 @@ import { check, FieldErrors, Length } from "../../util/instanceOf"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { UserModel } from "@fosscord/server-util"; -import Config from "../../util/Config"; +import * as Config from "../../util/Config"; import { adjustEmail } from "./register"; const router: Router = Router(); @@ -25,7 +25,9 @@ router.post( const query: any[] = [{ phone: login }]; if (email) query.push({ email }); - const config = Config.get(); + // TODO: Rewrite this to have the proper config syntax on the new method + + const config = Config.apiConfig.store as unknown as Config.DefaultOptions; if (config.login.requireCaptcha && config.security.captcha.enabled) { if (!captcha_key) { @@ -67,9 +69,10 @@ export async function generateToken(id: string) { const algorithm = "HS256"; return new Promise((res, rej) => { + const securityPropertiesSecret = Config.apiConfig.get('security.jwtSecret') as Config.DefaultOptions; jwt.sign( { id: id, iat }, - Config.get().security.jwtSecret, + securityPropertiesSecret.security.jwtSecret, { algorithm, }, diff --git a/src/routes/auth/register.ts b/src/routes/auth/register.ts index 265516d7..6389fb22 100644 --- a/src/routes/auth/register.ts +++ b/src/routes/auth/register.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import Config from "../../util/Config"; +import * as Config from "../../util/Config"; import { trimSpecial, User, Snowflake, UserModel } from "@fosscord/server-util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; @@ -52,7 +52,8 @@ router.post( let discriminator = ""; // get register Config - const { register, security } = Config.get(); + const securityProperties = Config.apiConfig.store as unknown as Config.DefaultOptions; + const { register, security } = securityProperties; // check if registration is allowed if (!register.allowNewRegistration) { @@ -90,13 +91,13 @@ router.post( }, }); } - } else if (register.email.required) { + } else if (register.email.necessary) { throw FieldErrors({ email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }, }); } - if (register.dateOfBirth.required && !date_of_birth) { + if (register.dateOfBirth.necessary && !date_of_birth) { throw FieldErrors({ date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }, }); diff --git a/src/routes/channels/#channel_id/messages/bulk-delete.ts b/src/routes/channels/#channel_id/messages/bulk-delete.ts index 6ac4d8de..c469e495 100644 --- a/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import { ChannelModel, getPermission, MessageDeleteBulkEvent, MessageModel } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; -import Config from "../../../../util/Config"; +import * as Config from "../../../../util/Config"; import { emitEvent } from "../../../../util/Event"; import { check } from "../../../../util/instanceOf"; @@ -20,7 +20,8 @@ router.post("/", check({ messages: [String] }), async (req, res) => { const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); permission.hasThrow("MANAGE_MESSAGES"); - const { maxBulkDelete } = Config.get().limits.message; + const limitsProperties = Config.apiConfig.get('limits.message') as Config.DefaultOptions; + const { maxBulkDelete } = limitsProperties.limits.message; const { messages } = req.body as { messages: string[] }; if (messages.length < 2) throw new HTTPError("You must at least specify 2 messages to bulk delete"); diff --git a/src/routes/channels/#channel_id/pins.ts b/src/routes/channels/#channel_id/pins.ts index 7dde15d0..d8e2be9b 100644 --- a/src/routes/channels/#channel_id/pins.ts +++ b/src/routes/channels/#channel_id/pins.ts @@ -1,6 +1,6 @@ import { ChannelModel, getPermission, MessageModel, toObject } from "@fosscord/server-util"; import { Router, Request, Response } from "express"; -import Config from "../../../util/Config"; +import * as Config from "../../../util/Config"; import { HTTPError } from "lambert-server"; const router: Router = Router(); @@ -18,7 +18,8 @@ router.put("/:message_id", async (req: Request, res: Response) => { if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); const pinned_count = await MessageModel.count({ channel_id, pinned: true }).exec(); - const { maxPins } = Config.get().limits.channel; + const limitsProperties = Config.apiConfig.get('limits.channel') as Config.DefaultOptions; + const { maxPins } = limitsProperties.limits.channel; if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins); await MessageModel.updateOne({ id: message_id }, { pinned: true }).exec(); diff --git a/src/routes/gateway.ts b/src/routes/gateway.ts index b6c8f49f..f92053e5 100644 --- a/src/routes/gateway.ts +++ b/src/routes/gateway.ts @@ -1,11 +1,12 @@ import { Router } from "express"; -import Config from "../util/Config" +import * as Config from "../util/Config" const router = Router(); router.get("/", (req, res) => { - const { endpoint } = Config.getAll().gateway; - res.send({ url: endpoint || "ws://localhost:3002" }); + const generalConfig = Config.apiConfig.get('gateway', 'ws://localhost:3002') as Config.DefaultOptions; + const { gateway } = generalConfig; + res.send({ url: gateway || "ws://localhost:3002" }); }); export default router; diff --git a/src/routes/guilds/index.ts b/src/routes/guilds/index.ts index 1ed9d0ff..89f60ab2 100644 --- a/src/routes/guilds/index.ts +++ b/src/routes/guilds/index.ts @@ -3,7 +3,7 @@ import { RoleModel, GuildModel, Snowflake, Guild, RoleDocument } from "@fosscord import { HTTPError } from "lambert-server"; import { check } from "./../../util/instanceOf"; import { GuildCreateSchema } from "../../schema/Guild"; -import Config from "../../util/Config"; +import * as Config from "../../util/Config"; import { getPublicUser } from "../../util/User"; import { addMember } from "../../util/Member"; @@ -14,7 +14,8 @@ const router: Router = Router(); router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) => { const body = req.body as GuildCreateSchema; - const { maxGuilds } = Config.get().limits.user; + const limitsProperties = Config.apiConfig.get('limits.user') as Config.DefaultOptions; + const { maxGuilds } = limitsProperties.limits.user; const user = await getPublicUser(req.user_id, { guilds: true }); if (user.guilds.length >= maxGuilds) { diff --git a/src/routes/guilds/templates/index.ts b/src/routes/guilds/templates/index.ts index 7e32e94c..c314728d 100644 --- a/src/routes/guilds/templates/index.ts +++ b/src/routes/guilds/templates/index.ts @@ -5,7 +5,7 @@ import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; import { getPublicUser } from "../../../util/User"; import { check } from "../../../util/instanceOf"; -import Config from "../../../util/Config"; +import * as Config from "../../../util/Config"; import { addMember } from "../../../util/Member"; router.get("/:code", async (req: Request, res: Response) => { @@ -21,7 +21,8 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const { code } = req.params; const body = req.body as GuildTemplateCreateSchema; - const { maxGuilds } = Config.get().limits.user; + const limitsProperties = Config.apiConfig.get('limits.user') as Config.DefaultOptions; + const { maxGuilds } = limitsProperties.limits.user; const user = await getPublicUser(req.user_id, { guilds: true }); if (user.guilds.length >= maxGuilds) { diff --git a/src/util/Config.ts b/src/util/Config.ts index 97322f9e..b3d23179 100644 --- a/src/util/Config.ts +++ b/src/util/Config.ts @@ -1,4 +1,12 @@ -import Ajv, {JTDSchemaType} from "ajv/dist/jtd" +import Ajv, {JSONSchemaType} from "ajv" +import {ValidateFunction} from 'ajv' +import ajvFormats from 'ajv-formats'; +import dotProp from "dot-prop"; +import envPaths from "env-paths"; +import path from "node:path"; +import fs from 'fs' +import assert from "assert"; +import atomically from "atomically" export interface RateLimitOptions { count: number; @@ -6,6 +14,7 @@ export interface RateLimitOptions { } export interface DefaultOptions { + gateway: string; general: { instance_id: string; }; @@ -69,13 +78,13 @@ export interface DefaultOptions { }; register: { email: { - required: boolean; + necessary: boolean; allowlist: boolean; blocklist: boolean; domains: string[]; }; dateOfBirth: { - required: boolean; + necessary: boolean; minimum: number; // in years }; requireCaptcha: boolean; @@ -92,139 +101,380 @@ export interface DefaultOptions { }; } -const schema: JTDSchemaType = { +const schema: JSONSchemaType & { + definitions: { + rateLimitOptions: JSONSchemaType + } +} = { + type: "object", definitions: { rateLimitOptions: { + type: "object", properties: { - count: {type: "int32"}, - timespan: {type: "int32"} - } - } + count: {type: "number"}, + timespan: {type: "number"}, + }, + required: ["count", "timespan"], + }, }, properties: { + gateway: { + type: "string" + }, general: { + type: "object", properties: { - instance_id: {type: "string"} - } + instance_id: { + type: "string" + } + }, + required: ["instance_id"], + additionalProperties: false }, permissions: { + type: "object", properties: { user: { + type: "object", properties: { - createGuilds: {type: "boolean"} - } + createGuilds: { + type: "boolean" + } + }, + required: ["createGuilds"], + additionalProperties: false } - } + }, + required: ["user"], + additionalProperties: false }, limits: { + type: "object", properties: { user: { + type: "object", properties: { - maxGuilds: {type: "int32"}, - maxFriends: {type: "int32"}, - maxUsername: {type: "int32"} - } + maxFriends: { + type: "number" + }, + maxGuilds: { + type: "number" + }, + maxUsername: { + type: "number" + } + }, + required: ["maxFriends", "maxGuilds", "maxUsername"], + additionalProperties: false }, guild: { + type: "object", properties: { - maxRoles: {type: "int32"}, - maxMembers: {type: "int32"}, - maxChannels: {type: "int32"}, - maxChannelsInCategory: {type: "int32"}, - hideOfflineMember: {type: "int32"} - } + maxRoles: { + type: "number" + }, + maxMembers: { + type: "number" + }, + maxChannels: { + type: "number" + }, + maxChannelsInCategory: { + type: "number" + }, + hideOfflineMember: { + type: "number" + } + }, + required: ["maxRoles", "maxMembers", "maxChannels", "maxChannelsInCategory", "hideOfflineMember"], + additionalProperties: false }, message: { + type: "object", properties: { - characters: {type: "int32"}, - ttsCharacters: {type: "int32"}, - maxReactions: {type: "int32"}, - maxAttachmentSize: {type: "int32"}, - maxBulkDelete: {type: "int32"} - } + characters: { + type: "number" + }, + ttsCharacters: { + type: "number" + }, + maxReactions: { + type: "number" + }, + maxAttachmentSize: { + type: "number" + }, + maxBulkDelete: { + type: "number" + } + }, + required: ["characters", "ttsCharacters", "maxReactions", "maxAttachmentSize", "maxBulkDelete"], + additionalProperties: false }, channel: { + type: "object", properties: { - maxPins: {type: "int32"}, - maxTopic: {type: "int32"}, + maxPins: { + type: "number" + }, + maxTopic: { + type: "number" + } }, + required: ["maxPins", "maxTopic"], + additionalProperties: false }, rate: { + type: "object", properties: { ip: { + type: "object", properties: { enabled: {type: "boolean"}, - count: {type: "int32"}, - timespan: {type: "int32"}, - } + count: {type: "number"}, + timespan: {type: "number"} + }, + required: ["enabled", "count", "timespan"], + additionalProperties: false }, routes: { - optionalProperties: { + type: "object", + properties: { auth: { - optionalProperties: { - login: {ref: 'rateLimitOptions'}, - register: {ref: 'rateLimitOptions'} - } + type: "object", + properties: { + login: {$ref: '#/definitions/rateLimitOptions'}, + register: {$ref: '#/definitions/rateLimitOptions'} + }, + nullable: true, + required: [], + additionalProperties: false }, - channel: {type: "string"} - } + channel: { + type: "string", + nullable: true + } + }, + required: [], + additionalProperties: false } - } + }, + required: ["ip", "routes"] } - } + }, + required: ["channel", "guild", "message", "rate", "user"], + additionalProperties: false }, security: { + type: "object", properties: { - jwtSecret: {type: "string"}, - forwadedFor: {type: "string", nullable: true}, + jwtSecret: { + type: "string" + }, + forwadedFor: { + type: "string", + nullable: true + }, captcha: { + type: "object", properties: { enabled: {type: "boolean"}, - service: {enum: ['hcaptcha', 'recaptcha'], nullable: true}, - sitekey: {type: "string", nullable: true}, - secret: {type: "string", nullable: true} - } + service: { + type: "string", + enum: ["hcaptcha", "recaptcha", null], + nullable: true + }, + sitekey: { + type: "string", + nullable: true + }, + secret: { + type: "string", + nullable: true + } + }, + required: ["enabled", "secret", "service", "sitekey"], + additionalProperties: false } - } + }, + required: ["captcha", "forwadedFor", "jwtSecret"], + additionalProperties: false }, login: { + type: "object", properties: { requireCaptcha: {type: "boolean"} - } + }, + required: ["requireCaptcha"], + additionalProperties: false }, register: { + type: "object", properties: { email: { + type: "object", properties: { - required: {type: "boolean"}, + necessary: {type: "boolean"}, allowlist: {type: "boolean"}, blocklist: {type: "boolean"}, - domains: { elements: { - type: "string" + domains: { + type: "array", + items: { + type: "string" + } } - } + }, + required: ["allowlist", "blocklist", "domains", "necessary"], + additionalProperties: false + }, + dateOfBirth: { + type: "object", + properties: { + necessary: {type: "boolean"}, + minimum: {type: "number"} + }, + required: ["minimum", "necessary"], + additionalProperties: false + }, + requireCaptcha: {type: "boolean"}, + requireInvite: {type: "boolean"}, + allowNewRegistration: {type: "boolean"}, + allowMultipleAccounts: {type: "boolean"}, + password: { + type: "object", + properties: { + minLength: {type: "number"}, + minNumbers: {type: "number"}, + minUpperCase: {type: "number"}, + minSymbols: {type: "number"}, + blockInsecureCommonPasswords: {type: "boolean"} + }, + required: ["minLength", "minNumbers", "minUpperCase", "minSymbols", "blockInsecureCommonPasswords"], + additionalProperties: false } }, - dateOfBirth: { - properties: { - required: {type: "boolean"}, - minimum: {type: "int32"} - } - }, - requireCaptcha: {type: "boolean"}, - requireInvite: {type: "boolean"}, - allowNewRegistration: {type: "boolean"}, - allowMultipleAccounts: {type: "boolean"}, - password: { - properties: { - minLength: {type: "int32"}, - minNumbers: {type: "int32"}, - minUpperCase: {type: "int32"}, - minSymbols: {type: "int32"}, - blockInsecureCommonPasswords: {type: "boolean"} - } + required: ["allowMultipleAccounts", "allowNewRegistration", "dateOfBirth", "email", "password", "requireCaptcha", "requireInvite"], + additionalProperties: false + }, + }, + required: ["gateway", "general", "limits", "login", "permissions", "register", "security"], + additionalProperties: false +} + + +const createPlainObject = (): T => { + return Object.create(null); +}; +type Serialize = (value: T) => string; +type Deserialize = (text: string) => T; + + +class Config = Record> implements Iterable<[keyof T, T[keyof T]]> { + readonly path: string; + readonly #validator?: ValidateFunction; + readonly #defaultOptions: Partial = {}; + + constructor() { + + const ajv = new Ajv(); + + ajvFormats(ajv); + + this.#validator = ajv.compile(schema); + + const base = envPaths('fosscord').config; + + this.path = path.resolve(base, 'api.json'); + + + const fileStore = this.store; + const store = Object.assign(createPlainObject(), fileStore); + this._validate(store); + + try { + assert.deepStrictEqual(fileStore, store); + } catch { + this.store = store; + } + } + + private _validate(data: T | unknown): void { + if (!this.#validator) { + return; + } + + const valid = this.#validator(data); + if (valid || !this.#validator.errors) { + return; + } + + const errors = this.#validator.errors.map(({instancePath, message = ''}) => `\`${instancePath.slice(1)}\` ${message}`); + throw new Error('The config schema was violated!: ' + errors.join('; ')); + } + + get store(): T { + try { + const data = fs.readFileSync(this.path).toString(); + const deserializedData = this._deserialize(data); + this._validate(deserializedData); + return Object.assign(Object.create(null), deserializedData) + } catch (error) { + if (error.code == 'ENOENT') { + this._ensureDirectory(); + return createPlainObject(); + } + + throw error; + } + } + + private _ensureDirectory(): void { + fs.mkdirSync(path.dirname(this.path), {recursive: true}) + } + + set store(value: T) { + this._validate(value); + + this._write(value); + } + + private readonly _deserialize: Deserialize = value => JSON.parse(value); + private readonly _serialize: Serialize = value => JSON.stringify(value, undefined, '\t') + + get(key: Key): T[Key]; + get(key: Key, defaultValue: Required[Key]): Required[Key]; + get(key: Exclude, defaultValue?: Value): Value; + get(key: string, defaultValue?: unknown): unknown { + return this._get(key, defaultValue); + } + + private _get(key: Key): T[Key] | undefined; + private _get(key: Key, defaultValue: Default): T[Key] | Default; + private _get(key: Key | string, defaultValue?: Default): Default | undefined { + return dotProp.get(this.store, key as string, defaultValue as T[Key]); + } + + * [Symbol.iterator](): IterableIterator<[keyof T, T[keyof T]]> { + for (const [key, value] of Object.entries(this.store)) { + yield [key, value]; + } + } + + private _write(value: T): void { + let data: string | Buffer = this._serialize(value); + + try { + atomically.writeFileSync(this.path, data); + } catch (error) { + if (error.code == 'EXDEV') { + fs.writeFileSync(this.path, data) + return + } + + throw error; } } } -} \ No newline at end of file + +export const apiConfig = new Config(); \ No newline at end of file diff --git a/src/util/Member.ts b/src/util/Member.ts index fec5aac7..87c3e6e1 100644 --- a/src/util/Member.ts +++ b/src/util/Member.ts @@ -14,7 +14,7 @@ import { } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; -import Config from "./Config"; +import * as Config from "./Config"; import { emitEvent } from "./Event"; import { getPublicUser } from "./User"; @@ -39,7 +39,8 @@ export async function isMember(user_id: string, guild_id: string) { export async function addMember(user_id: string, guild_id: string, cache?: { guild?: GuildDocument }) { const user = await getPublicUser(user_id, { guilds: true }); - const { maxGuilds } = Config.get().limits.user; + const limitsUserProperties = Config.apiConfig.get('limits.user', {maxGuilds: 100, masxUsername: 32, maxFriends: 1000}) as Config.DefaultOptions; + const { maxGuilds } = limitsUserProperties.limits.user; if (user.guilds.length >= maxGuilds) { throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); } diff --git a/src/util/passwordStrength.ts b/src/util/passwordStrength.ts index f6cec9da..71a5b5be 100644 --- a/src/util/passwordStrength.ts +++ b/src/util/passwordStrength.ts @@ -1,5 +1,5 @@ import "missing-native-js-functions"; -import Config from "./Config"; +import * as Config from "./Config"; const reNUMBER = /[0-9]/g; const reUPPERCASELETTER = /[A-Z]/g; @@ -17,13 +17,14 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored * Returns: 0 > pw > 1 */ export function check(password: string): number { + const passwordProperties = Config.apiConfig.get('register.password', { minLength: 8, minNumbers: 2, minUpperCase: 2, minSymbols: 0, blockInsecureCommonPasswords: false }) as Config.DefaultOptions; const { minLength, minNumbers, minUpperCase, minSymbols, blockInsecureCommonPasswords, - } = Config.get().register.password; + } = passwordProperties.register.password; var strength = 0; // checks for total password len