summary refs log tree commit diff
path: root/src/api/routes/users/@me/mfa
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2022-08-13 02:00:50 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2022-08-13 22:00:55 +0200
commit5e86d7ab9c5200d794c3adb2b422d20a2aefd2ce (patch)
tree0a4b23ee96862077b21dea20cf71205709e15f7c /src/api/routes/users/@me/mfa
parenttry to update build script (diff)
downloadserver-5e86d7ab9c5200d794c3adb2b422d20a2aefd2ce.tar.xz
restructure to single project
Diffstat (limited to 'src/api/routes/users/@me/mfa')
-rw-r--r--src/api/routes/users/@me/mfa/codes.ts45
-rw-r--r--src/api/routes/users/@me/mfa/totp/disable.ts41
-rw-r--r--src/api/routes/users/@me/mfa/totp/enable.ts48
3 files changed, 134 insertions, 0 deletions
diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts
new file mode 100644
index 00000000..4224a1c0
--- /dev/null
+++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -0,0 +1,45 @@
+import { Router, Request, Response } from "express";
+import { route } from "@fosscord/api";
+import { BackupCode, Config, FieldErrors, generateMfaBackupCodes, MfaCodesSchema, User } from "@fosscord/util";
+import bcrypt from "bcrypt";
+
+const router = Router();
+
+// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients
+
+router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Response) => {
+	const { password, regenerate } = req.body as MfaCodesSchema;
+
+	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] });
+
+	if (!await bcrypt.compare(password, user.data.hash || "")) {
+		throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
+	}
+
+	var codes: BackupCode[];
+	if (regenerate && Config.get().security.twoFactor.generateBackupCodes) {
+		await BackupCode.update(
+			{ user: { id: req.user_id } },
+			{ expired: true }
+		);
+
+		codes = generateMfaBackupCodes(req.user_id);
+		await Promise.all(codes.map(x => x.save()));
+	}
+	else {
+		codes = await BackupCode.find({
+			where: {
+				user: {
+					id: req.user_id,
+				},
+				expired: false
+			}
+		});
+	}
+
+	return res.json({
+		backup_codes: codes.map(x => ({ ...x, expired: undefined })),
+	})
+});
+
+export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts
new file mode 100644
index 00000000..2fe9355c
--- /dev/null
+++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -0,0 +1,41 @@
+import { Router, Request, Response } from "express";
+import { route } from "@fosscord/api";
+import { verifyToken } from 'node-2fa';
+import { HTTPError } from "lambert-server";
+import { User, generateToken, BackupCode, TotpDisableSchema } from "@fosscord/util";
+
+const router = Router();
+
+router.post("/", route({ body: "TotpDisableSchema" }), async (req: Request, res: Response) => {
+	const body = req.body as TotpDisableSchema;
+
+	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["totp_secret"] });
+
+	const backup = await BackupCode.findOne({ where: { code: body.code } });
+	if (!backup) {
+		const ret = verifyToken(user.totp_secret!, body.code);
+		if (!ret || ret.delta != 0)
+			throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+	}
+
+	await User.update(
+		{ id: req.user_id },
+		{
+			mfa_enabled: false,
+			totp_secret: "",
+		},
+	);
+
+	await BackupCode.update(
+		{ user: { id: req.user_id } },
+		{
+			expired: true,
+		}
+	);
+
+	return res.json({
+		token: await generateToken(user.id),
+	});
+});
+
+export default router;
\ No newline at end of file
diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts
new file mode 100644
index 00000000..ac668d1d
--- /dev/null
+++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -0,0 +1,48 @@
+import { Router, Request, Response } from "express";
+import { User, generateToken, BackupCode, generateMfaBackupCodes, Config, TotpEnableSchema } from "@fosscord/util";
+import { route } from "@fosscord/api";
+import bcrypt from "bcrypt";
+import { HTTPError } from "lambert-server";
+import { verifyToken } from 'node-2fa';
+
+const router = Router();
+
+router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: Response) => {
+	const body = req.body as TotpEnableSchema;
+
+	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] });
+
+	// TODO: Are guests allowed to enable 2fa?
+	if (user.data.hash) {
+		if (!await bcrypt.compare(body.password, user.data.hash)) {
+			throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+		}
+	}
+
+	if (!body.secret)
+		throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
+
+	if (!body.code)
+		throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+
+	if (verifyToken(body.secret, body.code)?.delta != 0)
+		throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+
+	let backup_codes: BackupCode[] = [];
+	if (Config.get().security.twoFactor.generateBackupCodes) {
+		backup_codes = generateMfaBackupCodes(req.user_id);
+		await Promise.all(backup_codes.map(x => x.save()));
+	}
+
+	await User.update(
+		{ id: req.user_id },
+		{ mfa_enabled: true, totp_secret: body.secret }
+	);
+
+	res.send({
+		token: await generateToken(user.id),
+		backup_codes: backup_codes.map(x => ({ ...x, expired: undefined })),
+	});
+});
+
+export default router;
\ No newline at end of file