summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Server.ts18
-rw-r--r--src/middlewares/Authentication.ts9
-rw-r--r--src/middlewares/CORS.ts2
-rw-r--r--src/middlewares/GlobalRateLimit.ts2
-rw-r--r--src/routes/auth/login.ts11
-rw-r--r--src/routes/auth/register.ts59
-rw-r--r--src/routes/channels/#channel_id/messages/bulk-delete.ts3
-rw-r--r--src/routes/channels/#channel_id/messages/index.ts4
-rw-r--r--src/routes/channels/#channel_id/permissions.ts40
-rw-r--r--src/routes/channels/#channel_id/pins.ts11
-rw-r--r--src/routes/gateway.ts7
-rw-r--r--src/routes/guilds/#guild_id/bans.ts24
-rw-r--r--src/routes/guilds/index.ts3
-rw-r--r--src/routes/guilds/templates/index.ts9
-rw-r--r--src/routes/users/@me/delete.ts30
-rw-r--r--src/routes/users/@me/disable.ts10
-rw-r--r--src/start.ts7
-rw-r--r--src/util/Config.ts363
-rw-r--r--src/util/Member.ts4
-rw-r--r--src/util/passwordStrength.ts15
20 files changed, 444 insertions, 187 deletions
diff --git a/src/Server.ts b/src/Server.ts

index e6d3d9c9..b1fe3c90 100644 --- a/src/Server.ts +++ b/src/Server.ts
@@ -3,16 +3,16 @@ import fs from "fs/promises"; import { Connection } from "mongoose"; import { Server, ServerOptions } from "lambert-server"; import { Authentication, CORS, GlobalRateLimit } from "./middlewares/"; -import Config from "./util/Config"; -import { db } from "@fosscord/server-util"; +import { Config, db } from "@fosscord/server-util"; import i18next from "i18next"; import i18nextMiddleware, { I18next } from "i18next-http-middleware"; import i18nextBackend from "i18next-node-fs-backend"; import { ErrorHandler } from "./middlewares/ErrorHandler"; import { BodyParser } from "./middlewares/BodyParser"; -import { Router } from "express"; +import express, { Router } from "express"; import fetch from "node-fetch"; import mongoose from "mongoose"; +import path from "path"; // this will return the new updated document for findOneAndUpdate mongoose.set("returnOriginal", false); // https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndUpdate @@ -55,14 +55,14 @@ export class FosscordServer extends Server { await (db as Promise<Connection>); await this.setupSchema(); console.log("[DB] connected"); - await Promise.all([Config.init()]); + await Config.init(); this.app.use(GlobalRateLimit); this.app.use(Authentication); this.app.use(CORS); this.app.use(BodyParser({ inflate: true })); - const languages = await fs.readdir(__dirname + "/../locales/"); - const namespaces = await fs.readdir(__dirname + "/../locales/en/"); + const languages = await fs.readdir(path.join(__dirname, "..", "locales")); + const namespaces = await fs.readdir(path.join(__dirname, "..", "locales", "en")); const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); await i18next @@ -85,11 +85,13 @@ export class FosscordServer extends Server { // @ts-ignore this.app = prefix; - this.routes = await this.registerRoutes(__dirname + "/routes/"); + this.routes = await this.registerRoutes(path.join(__dirname, "routes", "/")); app.use("/api/v8", prefix); this.app = app; this.app.use(ErrorHandler); - const indexHTML = await fs.readFile(__dirname + "/../client_test/index.html"); + const indexHTML = await fs.readFile(path.join(__dirname, "..", "client_test", "index.html")); + + this.app.use("/assets", express.static(path.join(__dirname, "..", "assets"))); this.app.get("/assets/:file", async (req, res) => { delete req.headers.host; diff --git a/src/middlewares/Authentication.ts b/src/middlewares/Authentication.ts
index 0ecc1bc0..630a45ff 100644 --- a/src/middlewares/Authentication.ts +++ b/src/middlewares/Authentication.ts
@@ -1,13 +1,13 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { checkToken } from "@fosscord/server-util"; +import { checkToken, Config } from "@fosscord/server-util"; export const NO_AUTHORIZATION_ROUTES = [ "/api/v8/auth/login", "/api/v8/auth/register", "/api/v8/webhooks/", "/api/v8/gateway", - "/api/v8/experiments", + "/api/v8/experiments" ]; declare global { @@ -24,10 +24,11 @@ export async function Authentication(req: Request, res: Response, next: NextFunc if (req.url.startsWith("/api/v8/invites") && req.method === "GET") return next(); if (NO_AUTHORIZATION_ROUTES.some((x) => req.url.startsWith(x))) return next(); if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401)); - // TODO: check if user is banned/token expired try { - const decoded: any = await checkToken(req.headers.authorization); + const { jwtSecret } = Config.get().security; + + const decoded: any = await checkToken(req.headers.authorization, jwtSecret); req.token = decoded; req.user_id = decoded.id; diff --git a/src/middlewares/CORS.ts b/src/middlewares/CORS.ts
index e6cc5544..88e90a4b 100644 --- a/src/middlewares/CORS.ts +++ b/src/middlewares/CORS.ts
@@ -9,7 +9,7 @@ export function CORS(req: Request, res: Response, next: NextFunction) { "Content-security-policy", "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';" ); - res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers")); + res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); next(); } diff --git a/src/middlewares/GlobalRateLimit.ts b/src/middlewares/GlobalRateLimit.ts
index fc121911..7260d1a2 100644 --- a/src/middlewares/GlobalRateLimit.ts +++ b/src/middlewares/GlobalRateLimit.ts
@@ -1,5 +1,5 @@ import { NextFunction, Request, Response } from "express"; -import Config from "../util/Config"; +import { Config } from "@fosscord/server-util"; // TODO: use mongodb ttl index // TODO: increment count on serverside diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts
index a0fc1190..2c4084ea 100644 --- a/src/routes/auth/login.ts +++ b/src/routes/auth/login.ts
@@ -2,8 +2,7 @@ import { Request, Response, Router } from "express"; 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 { Config, UserModel } from "@fosscord/server-util"; import { adjustEmail } from "./register"; const router: Router = Router(); @@ -17,7 +16,7 @@ router.post( $undelete: Boolean, $captcha_key: String, $login_source: String, - $gift_code_sku_id: String, + $gift_code_sku_id: String }), async (req: Request, res: Response) => { const { login, password, captcha_key } = req.body; @@ -25,6 +24,8 @@ router.post( const query: any[] = [{ phone: login }]; if (email) query.push({ email }); + // TODO: Rewrite this to have the proper config syntax on the new method + const config = Config.get(); if (config.login.requireCaptcha && config.security.captcha.enabled) { @@ -33,7 +34,7 @@ router.post( return res.status(400).json({ captcha_key: ["captcha-required"], captcha_sitekey: sitekey, - captcha_service: service, + captcha_service: service }); } @@ -71,7 +72,7 @@ export async function generateToken(id: string) { { id: id, iat }, Config.get().security.jwtSecret, { - algorithm, + algorithm }, (err, token) => { if (err) return rej(err); diff --git a/src/routes/auth/register.ts b/src/routes/auth/register.ts
index 265516d7..e24485da 100644 --- a/src/routes/auth/register.ts +++ b/src/routes/auth/register.ts
@@ -1,6 +1,5 @@ import { Request, Response, Router } from "express"; -import Config from "../../util/Config"; -import { trimSpecial, User, Snowflake, UserModel } from "@fosscord/server-util"; +import { trimSpecial, User, Snowflake, UserModel, Config } from "@fosscord/server-util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import "missing-native-js-functions"; @@ -21,7 +20,7 @@ router.post( $invite: String, $date_of_birth: Date, // "2000-04-03" $gift_code_sku_id: String, - $captcha_key: String, + $captcha_key: String }), async (req: Request, res: Response) => { const { @@ -33,14 +32,14 @@ router.post( invite, date_of_birth, gift_code_sku_id, // ? what is this - captcha_key, + captcha_key } = req.body; // TODO: automatically join invite // TODO: gift_code_sku_id? // TODO: check password strength // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email: string | undefined = adjustEmail(email); + let adjusted_email: string | null = adjustEmail(email); // adjusted_password will be the hash of the password let adjusted_password: string = ""; @@ -57,21 +56,21 @@ router.post( // check if registration is allowed if (!register.allowNewRegistration) { throw FieldErrors({ - email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") }, + email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") } }); } // check if the user agreed to the Terms of Service if (!consent) { throw FieldErrors({ - consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") }, + consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") } }); } // require invite to register -> e.g. for organizations to send invites to their employees if (register.requireInvite && !invite) { throw FieldErrors({ - email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") }, + email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } }); } @@ -86,19 +85,19 @@ router.post( throw FieldErrors({ email: { code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth.register.EMAIL_ALREADY_REGISTERED"), - }, + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") + } }); } - } 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") }, + 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") }, + date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } }); } else if (register.dateOfBirth.minimum) { const minimum = new Date(); @@ -109,8 +108,8 @@ router.post( throw FieldErrors({ date_of_birth: { code: "DATE_OF_BIRTH_UNDERAGE", - message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum }), - }, + message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum }) + } }); } } @@ -123,8 +122,8 @@ router.post( throw FieldErrors({ email: { code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), - }, + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") + } }); } } @@ -135,7 +134,7 @@ router.post( return res.status(400).json({ captcha_key: ["captcha-required"], captcha_sitekey: sitekey, - captcha_service: service, + captcha_service: service }); } @@ -160,8 +159,8 @@ router.post( throw FieldErrors({ username: { code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS"), - }, + message: req.t("auth:register.USERNAME_TOO_MANY_USERS") + } }); } @@ -181,17 +180,19 @@ router.post( mobile: false, premium: false, premium_type: 0, - phone: undefined, + phone: null, mfa_enabled: false, verified: false, + disabled: false, + deleted: false, presence: { activities: [], client_status: { desktop: undefined, mobile: undefined, - web: undefined, + web: undefined }, - status: "offline", + status: "offline" }, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age @@ -203,7 +204,7 @@ router.post( valid_tokens_since: new Date(), relationships: [], connected_accounts: [], - fingerprints: [], + fingerprints: [] }, user_settings: { afk_timeout: 300, @@ -216,7 +217,7 @@ router.post( emoji_id: null, emoji_name: null, expires_at: null, - text: null, + text: null }, default_guilds_restricted: false, detect_platform_accounts: true, @@ -241,9 +242,9 @@ router.post( status: "offline", stream_notifications_enabled: true, theme: "dark", - timezone_offset: 0, + timezone_offset: 0 // timezone_offset: // TODO: timezone from request - }, + } }; // insert user into database @@ -253,7 +254,7 @@ router.post( } ); -export function adjustEmail(email: string): string | undefined { +export function adjustEmail(email: string): string | null { // body parser already checked if it is a valid email const parts = <RegExpMatchArray>email.match(EMAIL_REGEX); // @ts-ignore diff --git a/src/routes/channels/#channel_id/messages/bulk-delete.ts b/src/routes/channels/#channel_id/messages/bulk-delete.ts
index ac032c0e..24724d34 100644 --- a/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/src/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -1,7 +1,6 @@ import { Router } from "express"; -import { ChannelModel, getPermission, MessageDeleteBulkEvent, MessageModel } from "@fosscord/server-util"; +import { ChannelModel, Config, getPermission, MessageDeleteBulkEvent, MessageModel } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; -import Config from "../../../../util/Config"; import { emitEvent } from "../../../../util/Event"; import { check } from "../../../../util/instanceOf"; diff --git a/src/routes/channels/#channel_id/messages/index.ts b/src/routes/channels/#channel_id/messages/index.ts
index 7fdff809..cdc46d14 100644 --- a/src/routes/channels/#channel_id/messages/index.ts +++ b/src/routes/channels/#channel_id/messages/index.ts
@@ -71,9 +71,11 @@ router.get("/", async (req, res) => { id: { $gt: (BigInt(around) - BigInt(halfLimit)).toString(), $lt: (BigInt(around) + BigInt(halfLimit)).toString() } }); else { - query = MessageModel.find({ channel_id }).sort({ id: -1 }); + query = MessageModel.find({ channel_id }); } + query = query.sort({ id: -1 }); + const messages = await query.limit(limit).exec(); return res.json( diff --git a/src/routes/channels/#channel_id/permissions.ts b/src/routes/channels/#channel_id/permissions.ts
index 93c33ea5..1a0ec6af 100644 --- a/src/routes/channels/#channel_id/permissions.ts +++ b/src/routes/channels/#channel_id/permissions.ts
@@ -1,5 +1,43 @@ +import { ChannelModel, ChannelPermissionOverwrite, getPermission, MemberModel, RoleModel } from "@fosscord/server-util"; import { Router } from "express"; +import { HTTPError } from "lambert-server"; +import { check } from "../../../util/instanceOf"; const router: Router = Router(); -// TODO: + +// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) + +router.put("/:overwrite_id", check({ allow: BigInt, deny: BigInt, type: Number }), async (req, res) => { + const { channel_id, overwrite_id } = req.params; + const body = req.body as { allow: bigint; deny: bigint; type: number }; + + const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + if (!channel || !channel.guild_id) throw new HTTPError("Channel not found", 404); + + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + permissions.hasThrow("MANAGE_ROLES"); + + if (body.type === 0) { + if (!(await RoleModel.exists({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + } else if (body.type === 1) { + if (await MemberModel.exists({ id: overwrite_id })) throw new HTTPError("user not found", 404); + } else throw new HTTPError("type not supported"); + + // @ts-ignore + var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); + if (!overwrite) { + // @ts-ignore + overwrite = { + id: overwrite_id, + type: body.type + }; + channel.permission_overwrites.push(overwrite); + } + overwrite.allow = body.allow; + overwrite.deny = body.deny; + + await ChannelModel.updateOne({ id: channel_id }, channel).exec(); + + return res.sendStatus(204); +}); export default router; diff --git a/src/routes/channels/#channel_id/pins.ts b/src/routes/channels/#channel_id/pins.ts
index 9d36b5c1..43c504d8 100644 --- a/src/routes/channels/#channel_id/pins.ts +++ b/src/routes/channels/#channel_id/pins.ts
@@ -1,6 +1,13 @@ -import { ChannelModel, ChannelPinsUpdateEvent, getPermission, MessageModel, MessageUpdateEvent, toObject } from "@fosscord/server-util"; +import { + ChannelModel, + ChannelPinsUpdateEvent, + Config, + getPermission, + MessageModel, + MessageUpdateEvent, + toObject +} from "@fosscord/server-util"; import { Router, Request, Response } from "express"; -import Config from "../../../util/Config"; import { HTTPError } from "lambert-server"; import { emitEvent } from "../../../util/Event"; diff --git a/src/routes/gateway.ts b/src/routes/gateway.ts
index 5b6a87e7..ffbbe74c 100644 --- a/src/routes/gateway.ts +++ b/src/routes/gateway.ts
@@ -1,12 +1,11 @@ +import { Config } from "@fosscord/server-util"; import { Router } from "express"; -import Config from "../util/Config"; const router = Router(); router.get("/", (req, res) => { - const endpoint = Config.getAll()?.gateway?.endpoint; - - res.send({ url: endpoint || "ws://localhost:3002" }); + const { endpoint } = Config.get().gateway; + res.send({ url: endpoint || process.env.GATEWAY || "ws://localhost:3002" }); }); export default router; diff --git a/src/routes/guilds/#guild_id/bans.ts b/src/routes/guilds/#guild_id/bans.ts
index f84950f9..87d2e7f8 100644 --- a/src/routes/guilds/#guild_id/bans.ts +++ b/src/routes/guilds/#guild_id/bans.ts
@@ -11,17 +11,17 @@ import { getPublicUser } from "../../../util/User"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const guild_id = req.params.id; + const { guild_id } = req.params; const guild = await GuildModel.exists({ id: guild_id }); if (!guild) throw new HTTPError("Guild not found", 404); - var bans = await BanModel.find({ guild_id: guild_id }).exec(); + var bans = await BanModel.find({ guild_id: guild_id }, { user: true, reason: true }).exec(); return res.json(toObject(bans)); }); router.get("/:user", async (req: Request, res: Response) => { - const guild_id = req.params.id; + const { guild_id } = req.params; const user_id = req.params.ban; var ban = await BanModel.findOne({ guild_id: guild_id, user_id: user_id }).exec(); @@ -29,8 +29,8 @@ router.get("/:user", async (req: Request, res: Response) => { return res.json(ban); }); -router.post("/:user_id", check(BanCreateSchema), async (req: Request, res: Response) => { - const guild_id = req.params.id; +router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Response) => { + const { guild_id } = req.params; const banned_user_id = req.params.user_id; const banned_user = await getPublicUser(banned_user_id); @@ -45,19 +45,19 @@ router.post("/:user_id", check(BanCreateSchema), async (req: Request, res: Respo guild_id: guild_id, ip: getIpAdress(req), executor_id: req.user_id, - reason: req.body.reason, // || otherwise empty + reason: req.body.reason // || otherwise empty }).save(); await emitEvent({ event: "GUILD_BAN_ADD", data: { guild_id: guild_id, - user: banned_user, + user: banned_user }, - guild_id: guild_id, + guild_id: guild_id } as GuildBanAddEvent); - return res.json(ban).send(); + return res.json(toObject(ban)); }); router.delete("/:user_id", async (req: Request, res: Response) => { @@ -73,16 +73,16 @@ router.delete("/:user_id", async (req: Request, res: Response) => { await BanModel.deleteOne({ user_id: banned_user_id, - guild_id, + guild_id }).exec(); await emitEvent({ event: "GUILD_BAN_REMOVE", data: { guild_id, - user: banned_user, + user: banned_user }, - guild_id, + guild_id } as GuildBanRemoveEvent); return res.status(204).send(); diff --git a/src/routes/guilds/index.ts b/src/routes/guilds/index.ts
index c286ad51..17ade355 100644 --- a/src/routes/guilds/index.ts +++ b/src/routes/guilds/index.ts
@@ -1,9 +1,8 @@ import { Router, Request, Response } from "express"; -import { RoleModel, GuildModel, Snowflake, Guild, RoleDocument } from "@fosscord/server-util"; +import { RoleModel, GuildModel, Snowflake, Guild, RoleDocument, Config } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; import { check } from "./../../util/instanceOf"; import { GuildCreateSchema } from "../../schema/Guild"; -import Config from "../../util/Config"; import { getPublicUser } from "../../util/User"; import { addMember } from "../../util/Member"; import { createChannel } from "../../util/Channel"; diff --git a/src/routes/guilds/templates/index.ts b/src/routes/guilds/templates/index.ts
index 7e32e94c..f23d4fbe 100644 --- a/src/routes/guilds/templates/index.ts +++ b/src/routes/guilds/templates/index.ts
@@ -1,11 +1,10 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); -import { TemplateModel, GuildModel, toObject, UserModel, RoleModel, Snowflake, Guild } from "@fosscord/server-util"; +import { TemplateModel, GuildModel, toObject, UserModel, RoleModel, Snowflake, Guild, Config } from "@fosscord/server-util"; 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 { addMember } from "../../../util/Member"; router.get("/:code", async (req: Request, res: Response) => { @@ -37,7 +36,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res ...body, ...template.serialized_source_guild, id: guild_id, - owner_id: req.user_id, + owner_id: req.user_id }; const [guild_doc, role] = await Promise.all([ @@ -52,8 +51,8 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res name: "@everyone", permissions: 2251804225n, position: 0, - tags: null, - }).save(), + tags: null + }).save() ]); await addMember(req.user_id, guild_id, { guild: guild_doc }); diff --git a/src/routes/users/@me/delete.ts b/src/routes/users/@me/delete.ts new file mode 100644
index 00000000..ec4cc223 --- /dev/null +++ b/src/routes/users/@me/delete.ts
@@ -0,0 +1,30 @@ +import { Router, Request, Response } from "express"; +import { UserModel,UserDocument, toObject } from "@fosscord/server-util"; +import { getPublicUser } from "../../../util/User"; +import { HTTPError } from "lambert-server"; +import { UserUpdateSchema } from "../../../schema/User"; +import { check, FieldErrors, Length } from "../../../util/instanceOf"; +import { db } from "@fosscord/server-util"; +import bcrypt from "bcrypt"; +const router = Router(); + +router.post("/", async (req: Request, res: Response) => { + + const user = await UserModel.findOne( + { id: req.user_id }, + + ).exec(); //User object + + let correctpass = await bcrypt.compare(req.body.password,user!.user_data.hash) //Not sure if user typed right password :/ + if(correctpass){ + await UserModel.deleteOne({id: req.user_id}).exec() //Yeetus user deletus + + res.sendStatus(204); + } + else{ + res.sendStatus(401); + + } +}); + +export default router; diff --git a/src/routes/users/@me/disable.ts b/src/routes/users/@me/disable.ts new file mode 100644
index 00000000..ab3ce58c --- /dev/null +++ b/src/routes/users/@me/disable.ts
@@ -0,0 +1,10 @@ +import { Router } from "express"; + +const router = Router(); + +router.post("/", (req, res) => { + // TODO: + res.sendStatus(204); +}); + +export default router; diff --git a/src/start.ts b/src/start.ts
index 8d5c33ce..d799c190 100644 --- a/src/start.ts +++ b/src/start.ts
@@ -7,9 +7,9 @@ config(); import { FosscordServer } from "./Server"; import cluster from "cluster"; import os from "os"; -const cores = os.cpus().length; +const cores = Number(process.env.threads) || os.cpus().length; -if (cluster.isMaster && process.env.production == "true") { +if (cluster.isMaster && process.env.NODE_ENV == "production") { console.log(`Primary ${process.pid} is running`); // Fork workers. @@ -22,8 +22,7 @@ if (cluster.isMaster && process.env.production == "true") { cluster.fork(); }); } else { - var port = Number(process.env.PORT); - if (isNaN(port)) port = 3001; + var port = Number(process.env.PORT) || 3001; const server = new FosscordServer({ port }); server.start().catch(console.error); diff --git a/src/util/Config.ts b/src/util/Config.ts
index f1f0f458..e2e0d312 100644 --- a/src/util/Config.ts +++ b/src/util/Config.ts
@@ -1,19 +1,7 @@ -import { Config, Snowflake } from "@fosscord/server-util"; -import crypto from "crypto"; - -export default { - init() { - return Config.init({ api: DefaultOptions }); - }, - get(): DefaultOptions { - return Config.getAll().api; - }, - set(val: any) { - return Config.setAll({ api: val }); - }, - getAll: Config.getAll, - setAll: Config.setAll, -}; +// @ts-nocheck +import Ajv, { JSONSchemaType } from "ajv"; +import { getConfigPathForFile } from "@fosscord/server-util/dist/util/Config"; +import { Config } from "@fosscord/server-util"; export interface RateLimitOptions { count: number; @@ -21,6 +9,7 @@ export interface RateLimitOptions { } export interface DefaultOptions { + gateway: string; general: { instance_id: string; }; @@ -64,7 +53,7 @@ export interface DefaultOptions { login?: RateLimitOptions; register?: RateLimitOptions; }; - channel?: {}; + channel?: string; // TODO: rate limit configuration for all routes }; }; @@ -84,13 +73,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; @@ -107,85 +96,277 @@ export interface DefaultOptions { }; } -export const DefaultOptions: DefaultOptions = { - general: { - instance_id: Snowflake.generate(), - }, - permissions: { - user: { - createGuilds: true, - }, +const schema: JSONSchemaType<DefaultOptions> & { + definitions: { + rateLimitOptions: JSONSchemaType<RateLimitOptions>; + }; +} = { + type: "object", + definitions: { + rateLimitOptions: { + type: "object", + properties: { + count: { type: "number" }, + timespan: { type: "number" } + }, + required: ["count", "timespan"] + } }, - limits: { - user: { - maxGuilds: 100, - maxUsername: 32, - maxFriends: 1000, - }, - guild: { - maxRoles: 250, - maxMembers: 250000, - maxChannels: 500, - maxChannelsInCategory: 50, - hideOfflineMember: 1000, - }, - message: { - characters: 2000, - ttsCharacters: 200, - maxReactions: 20, - maxAttachmentSize: 8388608, - maxBulkDelete: 100, - }, - channel: { - maxPins: 50, - maxTopic: 1024, + properties: { + gateway: { + type: "string" }, - rate: { - ip: { - enabled: true, - count: 1000, - timespan: 1000 * 60 * 10, + general: { + type: "object", + properties: { + instance_id: { + type: "string" + } }, - routes: {}, + required: ["instance_id"], + additionalProperties: false }, - }, - security: { - jwtSecret: crypto.randomBytes(256).toString("base64"), - forwadedFor: null, - // forwadedFor: "X-Forwarded-For" // nginx/reverse proxy - // forwadedFor: "CF-Connecting-IP" // cloudflare: - captcha: { - enabled: false, - service: null, - sitekey: null, - secret: null, + permissions: { + type: "object", + properties: { + user: { + type: "object", + properties: { + createGuilds: { + type: "boolean" + } + }, + required: ["createGuilds"], + additionalProperties: false + } + }, + required: ["user"], + additionalProperties: false }, - }, - login: { - requireCaptcha: false, - }, - register: { - email: { - required: true, - allowlist: false, - blocklist: true, - domains: [], // TODO: efficiently save domain blocklist in database - // domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"), + limits: { + type: "object", + properties: { + user: { + type: "object", + properties: { + maxFriends: { + type: "number" + }, + maxGuilds: { + type: "number" + }, + maxUsername: { + type: "number" + } + }, + required: ["maxFriends", "maxGuilds", "maxUsername"], + additionalProperties: false + }, + guild: { + type: "object", + properties: { + 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: "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: "number" + }, + maxTopic: { + type: "number" + } + }, + required: ["maxPins", "maxTopic"], + additionalProperties: false + }, + rate: { + type: "object", + properties: { + ip: { + type: "object", + properties: { + enabled: { type: "boolean" }, + count: { type: "number" }, + timespan: { type: "number" } + }, + required: ["enabled", "count", "timespan"], + additionalProperties: false + }, + routes: { + type: "object", + properties: { + auth: { + type: "object", + properties: { + login: { $ref: "#/definitions/rateLimitOptions" }, + register: { $ref: "#/definitions/rateLimitOptions" } + }, + nullable: true, + required: [], + additionalProperties: false + }, + channel: { + type: "string", + nullable: true + } + }, + required: [], + additionalProperties: false + } + }, + required: ["ip", "routes"] + } + }, + required: ["channel", "guild", "message", "rate", "user"], + additionalProperties: false }, - dateOfBirth: { - required: true, - minimum: 13, + security: { + type: "object", + properties: { + jwtSecret: { + type: "string" + }, + forwadedFor: { + type: "string", + nullable: true + }, + captcha: { + type: "object", + properties: { + enabled: { type: "boolean" }, + 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 }, - requireInvite: false, - requireCaptcha: true, - allowNewRegistration: true, - allowMultipleAccounts: true, - password: { - minLength: 8, - minNumbers: 2, - minUpperCase: 2, - minSymbols: 0, - blockInsecureCommonPasswords: false, + login: { + type: "object", + properties: { + requireCaptcha: { type: "boolean" } + }, + required: ["requireCaptcha"], + additionalProperties: false }, + register: { + type: "object", + properties: { + email: { + type: "object", + properties: { + necessary: { type: "boolean" }, + allowlist: { type: "boolean" }, + blocklist: { type: "boolean" }, + 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 + } + }, + required: [ + "allowMultipleAccounts", + "allowNewRegistration", + "dateOfBirth", + "email", + "password", + "requireCaptcha", + "requireInvite" + ], + additionalProperties: false + } }, + required: ["gateway", "general", "limits", "login", "permissions", "register", "security"], + additionalProperties: false }; + +const ajv = new Ajv(); +const validator = ajv.compile(schema); + +const configPath = getConfigPathForFile("fosscord", "api", ".json"); + +export const apiConfig = new Config<DefaultOptions>({ path: configPath, schemaValidator: validator, schema: schema }); diff --git a/src/util/Member.ts b/src/util/Member.ts
index e6df5d2c..7b06720b 100644 --- a/src/util/Member.ts +++ b/src/util/Member.ts
@@ -10,11 +10,11 @@ import { RoleModel, toObject, UserModel, - GuildDocument + GuildDocument, + Config } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; -import Config from "./Config"; import { emitEvent } from "./Event"; import { getPublicUser } from "./User"; diff --git a/src/util/passwordStrength.ts b/src/util/passwordStrength.ts
index f6cec9da..cc503843 100644 --- a/src/util/passwordStrength.ts +++ b/src/util/passwordStrength.ts
@@ -1,5 +1,5 @@ +import { Config } from "@fosscord/server-util"; import "missing-native-js-functions"; -import Config from "./Config"; const reNUMBER = /[0-9]/g; const reUPPERCASELETTER = /[A-Z]/g; @@ -17,13 +17,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored * Returns: 0 > pw > 1 */ export function check(password: string): number { - const { - minLength, - minNumbers, - minUpperCase, - minSymbols, - blockInsecureCommonPasswords, - } = Config.get().register.password; + const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; var strength = 0; // checks for total password len @@ -51,10 +45,5 @@ export function check(password: string): number { strength = 0; } - if (blockInsecureCommonPasswords) { - if (blocklist.includes(password)) { - strength = 0; - } - } return strength; }