add: all payments page authentication
This commit is contained in:
141
index.js
141
index.js
@@ -25,6 +25,10 @@ app.use(express.json());
|
||||
app.use(cors());
|
||||
const querystring = require("querystring");
|
||||
const https = require("https");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const crypto = require("crypto");
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
// const mellat = new mellatCheckout({
|
||||
// terminalId: "7269507",
|
||||
@@ -59,7 +63,6 @@ const { getAllCities } = require("./lib/getAllCities");
|
||||
const { getAllProvinces } = require("./lib/getAllProvinces");
|
||||
const { MongoClient, ObjectId } = require("mongodb");
|
||||
|
||||
// MongoDB for SEP pay requests (use MONGODB_URI env to override)
|
||||
const MONGODB_URI =
|
||||
process.env.MONGODB_URI ||
|
||||
"mongodb://root:2pCCFs4wrsLDsO1pjQVA9jORT2WCjLNO5uauS6FUUaGLXCcfjw28IJmAO8RxlEJN@31.7.78.133:14365/?authSource=admin";
|
||||
@@ -869,7 +872,6 @@ app.post("/sep-pay-request", async (req, res) => {
|
||||
},
|
||||
);
|
||||
|
||||
// Save to MongoDB before returning
|
||||
try {
|
||||
const coll = await getSepPayCollection();
|
||||
const token = response.data?.Token ?? response.data?.token ?? null;
|
||||
@@ -961,11 +963,48 @@ app.post("/sepverify", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// all-payments/send: send one payment to Taavon (like sepverify)
|
||||
app.post("/all-payments/send", async (req, res) => {
|
||||
const ALL_PAYMENTS_USER = "09011110919";
|
||||
const ALL_PAYMENTS_PASS = "666666";
|
||||
const ALL_PAYMENTS_SECRET =
|
||||
process.env.ALL_PAYMENTS_SECRET || "rasadyar_all_payments_secret_2026";
|
||||
const ALL_PAYMENTS_AUTH_TOKEN = crypto
|
||||
.createHmac("sha256", ALL_PAYMENTS_SECRET)
|
||||
.update(ALL_PAYMENTS_USER + ":" + ALL_PAYMENTS_PASS)
|
||||
.digest("hex");
|
||||
|
||||
function requireAllPaymentsAuth(req, res, next) {
|
||||
const token = req.cookies && req.cookies.all_payments_auth;
|
||||
if (token === ALL_PAYMENTS_AUTH_TOKEN) return next();
|
||||
res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
app.post("/all-payments/login", (req, res) => {
|
||||
const { username, password } = req.body || {};
|
||||
if (
|
||||
String(username).trim() === ALL_PAYMENTS_USER &&
|
||||
String(password) === ALL_PAYMENTS_PASS
|
||||
) {
|
||||
res
|
||||
.cookie("all_payments_auth", ALL_PAYMENTS_AUTH_TOKEN, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 24 * 60 * 60 * 1000,
|
||||
sameSite: "lax",
|
||||
})
|
||||
.json({ ok: true });
|
||||
} else {
|
||||
res.status(401).json({ error: "نام کاربری یا رمز عبور اشتباه است" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/all-payments/logout", (req, res) => {
|
||||
res.clearCookie("all_payments_auth", { path: "/" }).json({ ok: true });
|
||||
});
|
||||
|
||||
app.post("/all-payments/send", requireAllPaymentsAuth, async (req, res) => {
|
||||
const { id } = req.body;
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: "id is required" });
|
||||
return res.status(400).json({ error: "شناسه الزامی است" });
|
||||
}
|
||||
try {
|
||||
const coll = await getSepPayCollection();
|
||||
@@ -994,8 +1033,7 @@ app.post("/all-payments/send", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// all-payments/remove: remove one payment from MongoDB
|
||||
app.post("/all-payments/remove", async (req, res) => {
|
||||
app.post("/all-payments/remove", requireAllPaymentsAuth, async (req, res) => {
|
||||
const { id } = req.body;
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: "id is required" });
|
||||
@@ -1013,8 +1051,10 @@ app.post("/all-payments/remove", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// all-payments/remove-all: remove all payments from MongoDB
|
||||
app.post("/all-payments/remove-all", async (req, res) => {
|
||||
app.post(
|
||||
"/all-payments/remove-all",
|
||||
requireAllPaymentsAuth,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const coll = await getSepPayCollection();
|
||||
const result = await coll.deleteMany({});
|
||||
@@ -1027,9 +1067,9 @@ app.post("/all-payments/remove-all", async (req, res) => {
|
||||
console.error("all-payments remove-all error", err);
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Province code (first 2 digits) -> name for all-payments
|
||||
const PROVINCE_NAMES = {
|
||||
10: "تست",
|
||||
18: "همدان",
|
||||
@@ -1037,8 +1077,7 @@ const PROVINCE_NAMES = {
|
||||
51: "کردستان",
|
||||
};
|
||||
|
||||
// all-payments/data: paginated + filtered list (JSON API)
|
||||
app.get("/all-payments/data", async (req, res) => {
|
||||
app.get("/all-payments/data", requireAllPaymentsAuth, async (req, res) => {
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
||||
const limit = Math.min(100, Math.max(5, parseInt(req.query.limit, 10) || 20));
|
||||
let dateFrom = (req.query.dateFrom || "").trim(); // YYYY-MM-DD
|
||||
@@ -1054,7 +1093,6 @@ app.get("/all-payments/data", async (req, res) => {
|
||||
const coll = await getSepPayCollection();
|
||||
const filter = {};
|
||||
|
||||
// Iran timezone UTC+3:30 - treat selected date as full calendar day in Iran
|
||||
const IRAN_OFFSET_MS = 3.5 * 60 * 60 * 1000;
|
||||
if (dateFrom || dateTo) {
|
||||
filter.createdAt = {};
|
||||
@@ -1115,8 +1153,76 @@ function escapeRegex(s) {
|
||||
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
// all-payments: modern UI with pagination, date filter, search
|
||||
const allPaymentsLoginHtml = `<!DOCTYPE html>
|
||||
<html dir="rtl" lang="fa">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ورود - همه پرداختها</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root { --bg: #0f0f14; --surface: #18181f; --surface-hover: #1e1e28; --border: #2a2a36; --text: #e4e4e7; --text-muted: #a1a1aa; --primary: #6366f1; --primary-hover: #818cf8; --danger: #ef4444; --radius: 12px; --radius-sm: 8px; }
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
|
||||
.login-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 32px; width: 100%; max-width: 360px; }
|
||||
.login-card h1 { font-size: 1.25rem; margin-bottom: 24px; text-align: center; }
|
||||
.form-group { margin-bottom: 16px; }
|
||||
.form-group label { display: block; font-size: 0.8125rem; color: var(--text-muted); margin-bottom: 6px; }
|
||||
.form-group input { width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text); padding: 10px 14px; font-size: 0.875rem; }
|
||||
.form-group input:focus { outline: none; border-color: var(--primary); }
|
||||
.btn { width: 100%; border: none; border-radius: var(--radius-sm); padding: 12px; font-size: 0.875rem; font-weight: 500; cursor: pointer; background: var(--primary); color: #fff; margin-top: 8px; }
|
||||
.btn:hover:not(:disabled) { background: #818cf8; }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.err-msg { color: var(--danger); font-size: 0.8125rem; margin-top: 12px; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<h1>ورود به بخش پرداختها</h1>
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">نام کاربری</label>
|
||||
<input type="text" id="username" name="username" autocomplete="username" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">رمز عبور</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password" required />
|
||||
</div>
|
||||
<button type="submit" class="btn" id="btn-login">ورود</button>
|
||||
<div class="err-msg" id="err-msg"></div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var form = document.getElementById('login-form');
|
||||
var btn = document.getElementById('btn-login');
|
||||
var errEl = document.getElementById('err-msg');
|
||||
form.onsubmit = function(e) {
|
||||
e.preventDefault();
|
||||
errEl.textContent = '';
|
||||
var username = (document.getElementById('username').value || '').trim();
|
||||
var password = (document.getElementById('password').value || '');
|
||||
btn.disabled = true;
|
||||
fetch('/all-payments/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: username, password: password }) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(j) {
|
||||
if (j.ok) { window.location.href = '/all-payments'; return; }
|
||||
errEl.textContent = j.error || 'نام کاربری یا رمز عبور اشتباه است';
|
||||
})
|
||||
.catch(function(e) { errEl.textContent = e.message || 'خطا در ارتباط'; })
|
||||
.finally(function() { btn.disabled = false; });
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
app.get("/all-payments", async (req, res) => {
|
||||
const token = req.cookies && req.cookies.all_payments_auth;
|
||||
if (token !== ALL_PAYMENTS_AUTH_TOKEN) {
|
||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
return res.send(allPaymentsLoginHtml);
|
||||
}
|
||||
const html = `<!DOCTYPE html>
|
||||
<html dir="rtl" lang="fa">
|
||||
<head>
|
||||
@@ -1208,6 +1314,7 @@ app.get("/all-payments", async (req, res) => {
|
||||
<button type="button" class="btn btn-ghost" id="btn-apply">اعمال فیلتر</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger" id="btn-remove-all">حذف همه</button>
|
||||
<button type="button" class="btn btn-ghost" id="btn-logout">خروج</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="table-wrap">
|
||||
@@ -1414,6 +1521,10 @@ app.get("/all-payments", async (req, res) => {
|
||||
.catch(function(e) { alert(e.message); })
|
||||
.finally(function() { btn.disabled = false; });
|
||||
};
|
||||
document.getElementById('btn-logout').onclick = function() {
|
||||
fetch('/all-payments/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
|
||||
.then(function() { window.location.href = '/all-payments'; });
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() { initDatepickers(); load(); });
|
||||
|
||||
39
package-lock.json
generated
39
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.2.0",
|
||||
"express": "^4.18.2",
|
||||
@@ -284,6 +285,28 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser/node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@@ -1719,6 +1742,22 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||
"requires": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"mongodb": "^6.10.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
Reference in New Issue
Block a user