Files
psu-schedule/server/auth.js
Zakhar Timoshenko 3ae7b38fca feat: implement dashboard with authentication and event logging
- Added dashboard functionality at /status with Basic Auth using DASHBOARD_PASSWORD.
- Created server-side event logging to track catalog and university status.
- Implemented traffic metrics tracking and display on the dashboard.
- Enhanced API tests to cover new dashboard features and authentication.
- Introduced CSS and JS for dashboard styling and functionality.
- Added tests for dashboard access control and data retrieval.
2026-10-21 00:04:33 +03:00

46 lines
2.1 KiB
JavaScript

import { createHash, timingSafeEqual } from "node:crypto";
// The dashboard's password (DASHBOARD_PASSWORD), asked by the browser's own sign-in window
// (HTTP Basic). Without it set, the dashboard is off. Ten wrong passwords from one address lock
// it out for ten minutes.
const PASSWORD = process.env.DASHBOARD_PASSWORD || "";
const MAX_FAILURES = 10;
const LOCKOUT_MS = 10 * 60 * 1000;
export const dashboardEnabled = Boolean(PASSWORD);
/** Compares in constant time: the digests have equal length whatever was typed. */
const same = (a, b) => timingSafeEqual(createHash("sha256").update(a).digest(), createHash("sha256").update(b).digest());
export function dashboardAuth() {
const failures = new Map();
return (req, res, next) => {
// Status pages are nobody's search result and nobody's cache.
res.set({ "Cache-Control": "no-store", "X-Robots-Tag": "noindex" });
if (!PASSWORD)
return res.status(404).type("text/plain; charset=utf-8").send("Дашборд выключен: задайте DASHBOARD_PASSWORD.");
const now = Date.now();
for (const [ip, f] of failures) if (now - f.start >= LOCKOUT_MS) failures.delete(ip);
const tries = failures.get(req.ip);
if (tries && tries.count >= MAX_FAILURES) {
res.set("Retry-After", String(Math.ceil((tries.start + LOCKOUT_MS - now) / 1000)));
return res.status(429).type("text/plain; charset=utf-8").send("Слишком много неверных паролей. Попробуйте через 10 минут.");
}
const [scheme, value = ""] = (req.get("Authorization") || "").split(" ");
if (scheme === "Basic") {
const decoded = Buffer.from(value, "base64").toString("utf8");
// Any user name; only the password counts.
if (same(decoded.slice(decoded.indexOf(":") + 1), PASSWORD)) {
failures.delete(req.ip);
return next();
}
const f = tries || { count: 0, start: now };
f.count++;
failures.set(req.ip, f);
}
res.set("WWW-Authenticate", 'Basic realm="PSU schedule status", charset="UTF-8"');
res.status(401).type("text/plain; charset=utf-8").send("Нужен пароль дашборда.");
};
}