fix: code review fixes
This commit is contained in:
+168
-1
@@ -7,7 +7,9 @@ const airportData = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "../airportdata.json"), "utf8")
|
||||
);
|
||||
const db = require("../models");
|
||||
const { create } = require("xmlbuilder2");
|
||||
const { create, parse } = require("xmlbuilder2");
|
||||
const speakeasy = require("speakeasy");
|
||||
const QRCode = require("qrcode");
|
||||
const rateLimitMap = new Map();
|
||||
const multer = require("multer");
|
||||
const uploadDir = path.join(__dirname, "../public/uploads");
|
||||
@@ -28,6 +30,15 @@ const redis = require("redis");
|
||||
const client = redis.createClient();
|
||||
client.connect().catch(console.error);
|
||||
|
||||
// 2FA middleware
|
||||
function require2FA(req, res, next) {
|
||||
if (req.session && req.session.twoFactorVerified) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({ error: "2FA verification required" });
|
||||
}
|
||||
}
|
||||
|
||||
/* GET home page. */
|
||||
router.get("/", function (req, res, next) {
|
||||
res.sendFile(path.join(__dirname, "../views/index.html"));
|
||||
@@ -147,6 +158,84 @@ router.get("/analytic/export", async function (req, res) {
|
||||
}
|
||||
});
|
||||
|
||||
// Import analytic from XML
|
||||
router.post(
|
||||
"/analytic/import",
|
||||
uploadMulter.single("xmlfile"),
|
||||
async function (req, res) {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: "No XML file uploaded" });
|
||||
}
|
||||
|
||||
// Check file type
|
||||
if (
|
||||
req.file.mimetype !== "text/xml" &&
|
||||
req.file.mimetype !== "application/xml"
|
||||
) {
|
||||
return res.status(400).json({ error: "File must be XML format" });
|
||||
}
|
||||
|
||||
// Read and parse the uploaded XML file
|
||||
const xmlContent = fs.readFileSync(req.file.path, "utf8");
|
||||
const doc = parse(xmlContent);
|
||||
|
||||
// Extract analytics data from XML
|
||||
const analytics = [];
|
||||
const analyticNodes = doc.find("//analytic");
|
||||
|
||||
for (const node of analyticNodes) {
|
||||
const id = node.find("id")[0]?.text || null;
|
||||
const createAt = node.find("create_at")[0]?.text || null;
|
||||
const widgetName = node.find("widget_name")[0]?.text || null;
|
||||
const browserType = node.find("browser_type")[0]?.text || null;
|
||||
|
||||
if (widgetName && browserType) {
|
||||
analytics.push({
|
||||
id: id ? parseInt(id) : undefined,
|
||||
create_at: createAt ? new Date(createAt) : new Date(),
|
||||
widget_name: widgetName,
|
||||
browser_type: browserType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (analytics.length === 0) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "No valid analytics data found in XML" });
|
||||
}
|
||||
|
||||
// Insert analytics into database
|
||||
const insertedAnalytics = await db.analytic.bulkCreate(analytics, {
|
||||
ignoreDuplicates: true,
|
||||
updateOnDuplicate: ["widget_name", "browser_type"],
|
||||
});
|
||||
|
||||
// Clean up uploaded file
|
||||
fs.unlinkSync(req.file.path);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Successfully imported ${insertedAnalytics.length} analytics records`,
|
||||
count: insertedAnalytics.length,
|
||||
});
|
||||
} catch (err) {
|
||||
// Clean up uploaded file on error
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
fs.unlinkSync(req.file.path);
|
||||
} catch (cleanupErr) {
|
||||
console.error("Failed to cleanup uploaded file:", cleanupErr);
|
||||
}
|
||||
}
|
||||
|
||||
console.error("Import error:", err);
|
||||
res.status(500).json({ error: "Failed to import analytics from XML" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Reddit widget route
|
||||
router.get("/reddit", async function (req, res) {
|
||||
try {
|
||||
@@ -484,4 +573,82 @@ router.get("/flow/:id/trigger", async function (req, res) {
|
||||
}
|
||||
});
|
||||
|
||||
// 2FA Routes
|
||||
// Generate 2FA secret
|
||||
router.get("/2fa/generate", async function (req, res) {
|
||||
try {
|
||||
const secret = speakeasy.generateSecret({
|
||||
name: "Dashboard 2FA",
|
||||
length: 20,
|
||||
});
|
||||
|
||||
// Store secret in session for verification
|
||||
req.session = req.session || {};
|
||||
req.session.tempSecret = secret.base32;
|
||||
|
||||
// Generate QR code as data URL
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(secret.otpauth_url, {
|
||||
width: 200,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#FFFFFF",
|
||||
},
|
||||
});
|
||||
|
||||
res.json({
|
||||
secret: secret.base32,
|
||||
qrCode: qrCodeDataUrl,
|
||||
qrCodeUrl: secret.otpauth_url,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("2FA generation error:", err);
|
||||
res.status(500).json({ error: "Failed to generate 2FA secret" });
|
||||
}
|
||||
});
|
||||
|
||||
// Verify 2FA token
|
||||
router.post("/2fa/verify", function (req, res) {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
const tempSecret = req.session?.tempSecret;
|
||||
|
||||
if (!tempSecret) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "No 2FA secret found. Please generate a new one." });
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({ error: "Token required" });
|
||||
}
|
||||
|
||||
const verified = speakeasy.totp.verify({
|
||||
secret: tempSecret,
|
||||
encoding: "base32",
|
||||
token: token,
|
||||
window: 2, // Allow 2 time steps for clock skew
|
||||
});
|
||||
|
||||
if (verified) {
|
||||
// Mark 2FA as verified in session
|
||||
req.session = req.session || {};
|
||||
req.session.twoFactorVerified = true;
|
||||
delete req.session.tempSecret; // Clean up temp secret
|
||||
|
||||
res.json({ success: true, message: "2FA verification successful" });
|
||||
} else {
|
||||
res.status(400).json({ error: "Invalid 2FA token" });
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: "Failed to verify 2FA token" });
|
||||
}
|
||||
});
|
||||
|
||||
// Check 2FA status
|
||||
router.get("/2fa/status", function (req, res) {
|
||||
const verified = req.session?.twoFactorVerified || false;
|
||||
res.json({ verified });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user