feat: implement basic authentication for UI access

This commit is contained in:
ZareMate 2026-04-02 15:47:26 +02:00
parent 49b89b3d81
commit 4a4cafecf1

View File

@ -22,9 +22,14 @@ const fs = require("fs");
const path = require("path");
const { WebSocketServer, WebSocket } = require("ws");
require("dotenv").config();
const WEB_PORT = Number(process.env.AFK_PARENT_PORT || 3008);
const PARENT_SECRET = process.env.PARENT_SECRET || "";
const WEB_UI_PATH = path.join(__dirname, "web", "index.html");
const UI_AUTH_USER = process.env.UI_AUTH_USER || "admin";
const UI_AUTH_PASS = process.env.UI_AUTH_PASS || "afk";
const UI_AUTH_REALM = process.env.UI_AUTH_REALM || "AFK Bot UI";
// ─── state ───────────────────────────────────────────────────────────────────
@ -63,6 +68,40 @@ function listCachedBots() {
return [...cachedBots.values()];
}
function decodeBasicAuth(authHeader) {
if (!authHeader || !authHeader.startsWith("Basic ")) return null;
const base64 = authHeader.slice("Basic ".length).trim();
if (!base64) return null;
try {
const decoded = Buffer.from(base64, "base64").toString("utf8");
const sep = decoded.indexOf(":");
if (sep === -1) return null;
return {
user: decoded.slice(0, sep),
pass: decoded.slice(sep + 1),
};
} catch {
return null;
}
}
function isUiAuthorized(req) {
const auth = decodeBasicAuth(req.headers.authorization || "");
return Boolean(
auth && auth.user === UI_AUTH_USER && auth.pass === UI_AUTH_PASS,
);
}
function requestUiAuth(res) {
res.writeHead(401, {
"Content-Type": "text/plain; charset=utf-8",
"WWW-Authenticate": `Basic realm="${UI_AUTH_REALM}"`,
"Cache-Control": "no-store",
});
res.end("Authentication required");
}
// ─── forward server console logs to browsers ─────────────────────────────────
{
const _log = console.log.bind(console);
@ -107,6 +146,11 @@ const httpServer = http.createServer((req, res) => {
}
if (req.url === "/" || req.url === "/index.html") {
if (!isUiAuthorized(req)) {
requestUiAuth(res);
return;
}
try {
const html = fs.readFileSync(WEB_UI_PATH, "utf8");
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@ -167,6 +211,14 @@ httpServer.on("upgrade", (req, socket, head) => {
parentWssI.emit("connection", ws, req),
);
} else if (pathname === "/ws") {
if (!isUiAuthorized(req)) {
socket.write(
`HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm="${UI_AUTH_REALM}"\r\nConnection: close\r\n\r\n`,
);
socket.destroy();
return;
}
browserWss.handleUpgrade(req, socket, head, (ws) =>
browserWss.emit("connection", ws),
);