add: all payments page authentication
This commit is contained in:
165
index.js
165
index.js
@@ -25,6 +25,10 @@ app.use(express.json());
|
|||||||
app.use(cors());
|
app.use(cors());
|
||||||
const querystring = require("querystring");
|
const querystring = require("querystring");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
// const mellat = new mellatCheckout({
|
// const mellat = new mellatCheckout({
|
||||||
// terminalId: "7269507",
|
// terminalId: "7269507",
|
||||||
@@ -59,7 +63,6 @@ const { getAllCities } = require("./lib/getAllCities");
|
|||||||
const { getAllProvinces } = require("./lib/getAllProvinces");
|
const { getAllProvinces } = require("./lib/getAllProvinces");
|
||||||
const { MongoClient, ObjectId } = require("mongodb");
|
const { MongoClient, ObjectId } = require("mongodb");
|
||||||
|
|
||||||
// MongoDB for SEP pay requests (use MONGODB_URI env to override)
|
|
||||||
const MONGODB_URI =
|
const MONGODB_URI =
|
||||||
process.env.MONGODB_URI ||
|
process.env.MONGODB_URI ||
|
||||||
"mongodb://root:2pCCFs4wrsLDsO1pjQVA9jORT2WCjLNO5uauS6FUUaGLXCcfjw28IJmAO8RxlEJN@31.7.78.133:14365/?authSource=admin";
|
"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 {
|
try {
|
||||||
const coll = await getSepPayCollection();
|
const coll = await getSepPayCollection();
|
||||||
const token = response.data?.Token ?? response.data?.token ?? null;
|
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)
|
const ALL_PAYMENTS_USER = "09011110919";
|
||||||
app.post("/all-payments/send", async (req, res) => {
|
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;
|
const { id } = req.body;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return res.status(400).json({ error: "id is required" });
|
return res.status(400).json({ error: "شناسه الزامی است" });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const coll = await getSepPayCollection();
|
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", requireAllPaymentsAuth, async (req, res) => {
|
||||||
app.post("/all-payments/remove", async (req, res) => {
|
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return res.status(400).json({ error: "id is required" });
|
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(
|
||||||
app.post("/all-payments/remove-all", async (req, res) => {
|
"/all-payments/remove-all",
|
||||||
try {
|
requireAllPaymentsAuth,
|
||||||
const coll = await getSepPayCollection();
|
async (req, res) => {
|
||||||
const result = await coll.deleteMany({});
|
try {
|
||||||
return res.json({
|
const coll = await getSepPayCollection();
|
||||||
ok: true,
|
const result = await coll.deleteMany({});
|
||||||
message: "همه حذف شد",
|
return res.json({
|
||||||
deletedCount: result.deletedCount,
|
ok: true,
|
||||||
});
|
message: "همه حذف شد",
|
||||||
} catch (err) {
|
deletedCount: result.deletedCount,
|
||||||
console.error("all-payments remove-all error", err);
|
});
|
||||||
return res.status(500).json({ error: err.message });
|
} 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 = {
|
const PROVINCE_NAMES = {
|
||||||
10: "تست",
|
10: "تست",
|
||||||
18: "همدان",
|
18: "همدان",
|
||||||
@@ -1037,8 +1077,7 @@ const PROVINCE_NAMES = {
|
|||||||
51: "کردستان",
|
51: "کردستان",
|
||||||
};
|
};
|
||||||
|
|
||||||
// all-payments/data: paginated + filtered list (JSON API)
|
app.get("/all-payments/data", requireAllPaymentsAuth, async (req, res) => {
|
||||||
app.get("/all-payments/data", async (req, res) => {
|
|
||||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
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));
|
const limit = Math.min(100, Math.max(5, parseInt(req.query.limit, 10) || 20));
|
||||||
let dateFrom = (req.query.dateFrom || "").trim(); // YYYY-MM-DD
|
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 coll = await getSepPayCollection();
|
||||||
const filter = {};
|
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;
|
const IRAN_OFFSET_MS = 3.5 * 60 * 60 * 1000;
|
||||||
if (dateFrom || dateTo) {
|
if (dateFrom || dateTo) {
|
||||||
filter.createdAt = {};
|
filter.createdAt = {};
|
||||||
@@ -1115,8 +1153,76 @@ function escapeRegex(s) {
|
|||||||
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
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) => {
|
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>
|
const html = `<!DOCTYPE html>
|
||||||
<html dir="rtl" lang="fa">
|
<html dir="rtl" lang="fa">
|
||||||
<head>
|
<head>
|
||||||
@@ -1208,6 +1314,7 @@ app.get("/all-payments", async (req, res) => {
|
|||||||
<button type="button" class="btn btn-ghost" id="btn-apply">اعمال فیلتر</button>
|
<button type="button" class="btn btn-ghost" id="btn-apply">اعمال فیلتر</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-danger" id="btn-remove-all">حذف همه</button>
|
<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>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
@@ -1414,6 +1521,10 @@ app.get("/all-payments", async (req, res) => {
|
|||||||
.catch(function(e) { alert(e.message); })
|
.catch(function(e) { alert(e.message); })
|
||||||
.finally(function() { btn.disabled = false; });
|
.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') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', function() { initDatepickers(); load(); });
|
document.addEventListener('DOMContentLoaded', function() { initDatepickers(); load(); });
|
||||||
|
|||||||
39
package-lock.json
generated
39
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
@@ -284,6 +285,28 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
"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": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"mongodb": "^6.10.0",
|
"mongodb": "^6.10.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user