From ffd40f6d39012950a9f2d9c7911c672d8c359210 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Wed, 14 Aug 2024 21:49:44 +0800 Subject: [PATCH] Server-side 2FA --- server/models/User.js | 4 ++ server/package.json | 3 ++ server/routes/users.js | 91 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/server/models/User.js b/server/models/User.js index 4dd3dce..ca784fb 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -62,6 +62,10 @@ module.exports = (sequelize) => { type: DataTypes.DATE, allowNull: true, }, + secret: { + type: DataTypes.STRING(64), + allowNull: true, + }, }, { tableName: "users", diff --git a/server/package.json b/server/package.json index a7ac525..3f8cfd9 100644 --- a/server/package.json +++ b/server/package.json @@ -17,11 +17,14 @@ "dayjs": "^1.11.12", "dotenv": "^16.4.5", "express": "^4.19.2", + "hi-base32": "^0.5.1", "jsonwebtoken": "^9.0.2", "multer": "1.4.5-lts.1", "mysql2": "^3.10.1", "nodemon": "^3.1.3", "openai": "^4.53.2", + "otpauth": "^9.3.1", + "qrcode": "^1.5.4", "react-router-dom": "^6.26.0", "sequelize": "^6.37.3", "sharp": "^0.33.4", diff --git a/server/routes/users.js b/server/routes/users.js index f93a968..f0e43ab 100644 --- a/server/routes/users.js +++ b/server/routes/users.js @@ -10,6 +10,10 @@ const multer = require("multer"); const sharp = require("sharp"); const { sendPasswordResetEmail } = require("../connections/mailersend"); const fs = require("fs"); +const OTPAuth = require("otpauth"); +const base32 = require("hi-base32"); +const QRCode = require("qrcode"); +const crypto = require("crypto"); const { generatePasswordResetToken, } = require("../security/generatePasswordResetToken"); @@ -490,4 +494,91 @@ router.get("/town-councils-metadata", async (req, res) => { } }); +const generateBase32Secret = () => { + const buffer = crypto.randomBytes(15); + const base32Secret = base32.encode(buffer).replace(/=/g, "").substring(0, 24); + return base32Secret; +}; + +router.post("/enable-2fa/:id", async (req, res) => { + let id = req.params.id; + const user = User.findByPk(id); + + if (!user) { + return res.status(404).send("User not found"); + } + + const base32_secret = generateBase32Secret(); + user.secret = base32_secret; + await User.update( + { secret: base32_secret }, + { + where: { id: id }, + } + ); + + let totp = new OTPAuth.TOTP({ + issuer: "ecoconnect.gov.sg", + label: "ecoconnect", + algorithm: "SHA1", + digits: 6, + secret: base32_secret, + }); + + let otpauth_url = totp.toString(); + + QRCode.toDataURL(otpauth_url, (err, url) => { + if (err) { + return res.status(500).json({ + status: "fail", + message: "Error while generating QR Code", + }); + } + res.json({ + status: "success", + data: { + qrCodeUrl: url, + secret: base32_secret, + }, + }); + }); +}); + +router.post("/verify-2fa", async (req, res) => { + const { id, token } = req.body; + + const user = await User.findByPk(id); + + if (!user) { + console.log("User not found"); + return res.status(404).send("User not found"); + } + + console.log(user.firstName); + + // Verify the token + let totp = new OTPAuth.TOTP({ + issuer: "ecoconnect.gov.sg", + label: "ecoconnect", + algorithm: "SHA1", + digits: 6, + secret: user.secret, + }); + + let delta = totp.validate({ token }); + console.log("Delta: " + delta); + + if (delta == 0) { + return res.json({ + status: "success", + message: "Authentication successful", + }); + } else { + return res.status(401).json({ + status: "fail", + message: "Authentication failed", + }); + } +}); + module.exports = router;