Server-side 2FA
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user