Server-side 2FA

This commit is contained in:
2024-08-14 21:49:44 +08:00
parent be1c8de5bf
commit ffd40f6d39
3 changed files with 98 additions and 0 deletions

View File

@@ -62,6 +62,10 @@ module.exports = (sequelize) => {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,
}, },
secret: {
type: DataTypes.STRING(64),
allowNull: true,
},
}, },
{ {
tableName: "users", tableName: "users",

View File

@@ -17,11 +17,14 @@
"dayjs": "^1.11.12", "dayjs": "^1.11.12",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"hi-base32": "^0.5.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"mysql2": "^3.10.1", "mysql2": "^3.10.1",
"nodemon": "^3.1.3", "nodemon": "^3.1.3",
"openai": "^4.53.2", "openai": "^4.53.2",
"otpauth": "^9.3.1",
"qrcode": "^1.5.4",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",
"sequelize": "^6.37.3", "sequelize": "^6.37.3",
"sharp": "^0.33.4", "sharp": "^0.33.4",

View File

@@ -10,6 +10,10 @@ const multer = require("multer");
const sharp = require("sharp"); const sharp = require("sharp");
const { sendPasswordResetEmail } = require("../connections/mailersend"); const { sendPasswordResetEmail } = require("../connections/mailersend");
const fs = require("fs"); const fs = require("fs");
const OTPAuth = require("otpauth");
const base32 = require("hi-base32");
const QRCode = require("qrcode");
const crypto = require("crypto");
const { const {
generatePasswordResetToken, generatePasswordResetToken,
} = require("../security/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; module.exports = router;