Commit 467c8600 by tdgiang

Scope transaction creation and listing by logged-in account; retire basicAuth

createTransaction now stamps createdByUsername from the session. listTransactions
scopes results to the logged-in staff's own transactions, or (for admins) all
transactions with an optional ?staff= filter sourced from real AdminUser
records - the filter is ignored for non-admin sessions so staff cannot view
another account's data by editing the query string. Transaction routes now use
requireLogin instead of basicAuth, which is now unused and deleted.
Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
parent a36f12f5
...@@ -3,10 +3,14 @@ var moment = require("moment"); ...@@ -3,10 +3,14 @@ var moment = require("moment");
var uuidv4 = require("uuid").v4; var uuidv4 = require("uuid").v4;
var config = require(__config_path + "/config"); var config = require(__config_path + "/config");
var AdminTransaction = require("../models/AdminTransaction"); var AdminTransaction = require("../models/AdminTransaction");
var AdminUser = require("../models/AdminUser");
var epaySign = require("../libs/epaySign"); var epaySign = require("../libs/epaySign");
exports.newTransactionForm = function (req, res) { exports.newTransactionForm = function (req, res) {
res.render("admin/transactions-new", {}); res.render("admin/transactions-new", {
username: req.session.username,
isAdmin: req.session.role === "admin",
});
}; };
exports.createTransaction = function (req, res) { exports.createTransaction = function (req, res) {
...@@ -46,6 +50,7 @@ exports.createTransaction = function (req, res) { ...@@ -46,6 +50,7 @@ exports.createTransaction = function (req, res) {
amount: amount, amount: amount,
merchantToken: merchantToken, merchantToken: merchantToken,
timeStamp: timeStamp, timeStamp: timeStamp,
createdByUsername: req.session.username,
}, },
function (err, tx) { function (err, tx) {
if (err) { if (err) {
...@@ -195,35 +200,64 @@ exports.epayIPN = function (req, res) { ...@@ -195,35 +200,64 @@ exports.epayIPN = function (req, res) {
exports.listTransactions = function (req, res) { exports.listTransactions = function (req, res) {
var page = parseInt(req.query.page) || 1; var page = parseInt(req.query.page) || 1;
var perPage = 20; var perPage = 20;
var isAdmin = req.session.role === "admin";
var filter = {};
AdminTransaction.find({}) if (!isAdmin) {
.sort({ createdAt: -1 }) filter.createdByUsername = req.session.username;
.skip((page - 1) * perPage) } else if (req.query.staff) {
.limit(perPage) filter.createdByUsername = req.query.staff;
.exec(function (err, list) { }
if (err) {
console.error("listTransactions: DB error:", err.message); function render(staffOptions) {
return res.status(500).send("Lỗi cơ sở dữ liệu"); AdminTransaction.find(filter)
} .sort({ createdAt: -1 })
var transactions = list.map(function (tx) { .skip((page - 1) * perPage)
return { .limit(perPage)
merTrxId: tx.merTrxId, .exec(function (err, list) {
customerName: tx.customerName, if (err) {
customerPhone: tx.customerPhone, console.error("listTransactions: DB error:", err.message);
amount: tx.amount, return res.status(500).send("Lỗi cơ sở dữ liệu");
status: tx.status, }
resultMsg: tx.resultMsg, var transactions = list.map(function (tx) {
createdAt: moment(tx.createdAt).format("DD/MM/YYYY HH:mm"), return {
paymentUrl: config.epay.req_domain + "/admin/pay/" + tx.merTrxId, merTrxId: tx.merTrxId,
}; customerName: tx.customerName,
customerPhone: tx.customerPhone,
amount: tx.amount,
status: tx.status,
resultMsg: tx.resultMsg,
createdByUsername: tx.createdByUsername,
createdAt: moment(tx.createdAt).format("DD/MM/YYYY HH:mm"),
paymentUrl: config.epay.req_domain + "/admin/pay/" + tx.merTrxId,
};
});
res.render("admin/transactions-list", {
transactions: transactions,
page: page,
prevPage: page - 1,
nextPage: page + 1,
hasPrev: page > 1,
hasNext: transactions.length === perPage,
isAdmin: isAdmin,
username: req.session.username,
staffOptions: staffOptions || [],
selectedStaff: req.query.staff || "",
});
}); });
res.render("admin/transactions-list", { }
transactions: transactions,
page: page, if (isAdmin) {
prevPage: page - 1, AdminUser.find({ role: "staff" }, "username")
nextPage: page + 1, .sort({ username: 1 })
hasPrev: page > 1, .exec(function (err, staffUsers) {
hasNext: transactions.length === perPage, if (err) {
console.error("listTransactions: DB error loading staff list:", err.message);
return render([]);
}
render(staffUsers.map(function (u) { return u.username; }));
}); });
}); } else {
render([]);
}
}; };
"use strict";
var crypto = require("crypto");
function safeEqual(a, b) {
var bufA = Buffer.from(String(a));
var bufB = Buffer.from(String(b));
if (bufA.length !== bufB.length) {
crypto.timingSafeEqual(bufA, bufA);
return false;
}
return crypto.timingSafeEqual(bufA, bufB);
}
module.exports = function basicAuth(req, res, next) {
var config = require(__config_path + "/config");
var expectedUser = config.admin && config.admin.user;
var expectedPassword = config.admin && config.admin.password;
if (!expectedUser || !expectedPassword) {
console.error("basicAuth: ADMIN_USER/ADMIN_PASSWORD not configured - denying all admin access");
return res.status(500).send("Admin auth not configured.");
}
var header = req.headers.authorization || "";
var token = header.slice(0, 6).toLowerCase() === "basic " ? header.slice(6) : "";
var decoded = Buffer.from(token, "base64").toString("utf8");
var sepIndex = decoded.indexOf(":");
var user = sepIndex === -1 ? decoded : decoded.slice(0, sepIndex);
var password = sepIndex === -1 ? "" : decoded.slice(sepIndex + 1);
if (safeEqual(user, expectedUser) && safeEqual(password, expectedPassword)) {
return next();
}
res.set("WWW-Authenticate", 'Basic realm="Admin"');
return res.status(401).send("Authentication required.");
};
"use strict"; "use strict";
module.exports = function (app) { module.exports = function (app) {
var admin = require("../../app/controllers/admin.server.controller"); var admin = require("../../app/controllers/admin.server.controller");
var basicAuth = require("../middlewares/basicAuth");
var adminAuth = require("../../app/controllers/adminAuth.server.controller"); var adminAuth = require("../../app/controllers/adminAuth.server.controller");
var requireLogin = require("../middlewares/requireLogin"); var requireLogin = require("../middlewares/requireLogin");
...@@ -12,10 +11,10 @@ module.exports = function (app) { ...@@ -12,10 +11,10 @@ module.exports = function (app) {
app.route("/admin/logout").post(requireLogin, adminAuth.logout); app.route("/admin/logout").post(requireLogin, adminAuth.logout);
app.route("/admin/transactions") app.route("/admin/transactions")
.get(basicAuth, admin.listTransactions) .get(requireLogin, admin.listTransactions)
.post(basicAuth, admin.createTransaction); .post(requireLogin, admin.createTransaction);
app.route("/admin/transactions/new").get(basicAuth, admin.newTransactionForm); app.route("/admin/transactions/new").get(requireLogin, admin.newTransactionForm);
app.route("/admin/pay/:merTrxId").get(admin.payPage); app.route("/admin/pay/:merTrxId").get(admin.payPage);
app.route("/admin/epay/return").get(admin.epayReturn); app.route("/admin/epay/return").get(admin.epayReturn);
......
...@@ -43,6 +43,10 @@ ...@@ -43,6 +43,10 @@
}) })
.then(function (r) { return r.json(); }) .then(function (r) { return r.json(); })
.then(function (res) { .then(function (res) {
if (res.data === 'LOGIN_REQUIRED') {
window.location.href = '/admin/login';
return;
}
var el = document.getElementById('result'); var el = document.getElementById('result');
el.style.display = 'block'; el.style.display = 'block';
if (res.code === '00') { if (res.code === '00') {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment