add: all payments page authentication

This commit is contained in:
2026-02-01 10:26:22 +03:30
parent f7818a57c0
commit d1c0112b43
3 changed files with 178 additions and 27 deletions

165
index.js
View File

@@ -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,23 +1051,25 @@ 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) => {
try {
const coll = await getSepPayCollection();
const result = await coll.deleteMany({});
return res.json({
ok: true,
message: "همه حذف شد",
deletedCount: result.deletedCount,
});
} catch (err) {
console.error("all-payments remove-all error", err);
return res.status(500).json({ error: err.message });
}
});
app.post(
"/all-payments/remove-all",
requireAllPaymentsAuth,
async (req, res) => {
try {
const coll = await getSepPayCollection();
const result = await coll.deleteMany({});
return res.json({
ok: true,
message: "همه حذف شد",
deletedCount: result.deletedCount,
});
} catch (err) {
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
View File

@@ -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",

View File

@@ -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",