관리자 페이지 IP 허용 목록 적용
This commit is contained in:
@@ -15,6 +15,7 @@ const publicDir = path.resolve("src", "public");
|
||||
const adminPassword = process.env.ADMIN_PASSWORD;
|
||||
const sessionSecret = process.env.SESSION_SECRET;
|
||||
const publicBaseUrl = (process.env.PUBLIC_BASE_URL || "").replace(/\/$/, "");
|
||||
const adminAllowedIps = parseIpList(process.env.ADMIN_ALLOWED_IPS || "112.159.64.27,192.168.219.100,192.168.219.1");
|
||||
|
||||
if (!adminPassword || !sessionSecret) {
|
||||
throw new Error("ADMIN_PASSWORD and SESSION_SECRET are required.");
|
||||
@@ -175,6 +176,53 @@ function wantsJson(req) {
|
||||
return String(req.get("accept") || "").includes("application/json");
|
||||
}
|
||||
|
||||
function parseIpList(value) {
|
||||
return String(value || "")
|
||||
.split(/[\s,;]+/)
|
||||
.map(normalizeIp)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function normalizeIp(value) {
|
||||
let ip = String(value || "").trim();
|
||||
if (!ip) return "";
|
||||
if (ip.startsWith("[") && ip.endsWith("]")) ip = ip.slice(1, -1);
|
||||
if (ip.startsWith("::ffff:")) ip = ip.slice("::ffff:".length);
|
||||
return ip;
|
||||
}
|
||||
|
||||
function clientIp(req) {
|
||||
const forwarded = String(req.get("x-forwarded-for") || "")
|
||||
.split(",")
|
||||
.map(normalizeIp)
|
||||
.filter(Boolean);
|
||||
const closestPublicIp = forwarded.toReversed().find((ip) => !isPrivateIp(ip)) || "";
|
||||
return normalizeIp(closestPublicIp || forwarded[0] || req.get("x-real-ip") || req.ip || req.socket.remoteAddress || "");
|
||||
}
|
||||
|
||||
function isPrivateIp(ip) {
|
||||
if (ip === "localhost" || ip === "::1") return true;
|
||||
if (ip.startsWith("fe80:") || ip.startsWith("fc") || ip.startsWith("fd")) return true;
|
||||
const parts = ip.split(".").map((part) => Number(part));
|
||||
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part))) return false;
|
||||
const [a, b] = parts;
|
||||
return a === 10
|
||||
|| a === 127
|
||||
|| (a === 172 && b >= 16 && b <= 31)
|
||||
|| (a === 192 && b === 168)
|
||||
|| (a === 169 && b === 254);
|
||||
}
|
||||
|
||||
function requireAdminIp(req, res, next) {
|
||||
const ip = clientIp(req);
|
||||
if (adminAllowedIps.includes(ip)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
console.warn(`blocked admin path from ${ip || "unknown"}: ${req.method} ${req.originalUrl}`);
|
||||
res.status(404).send(page("페이지 없음", `<section class="panel"><h1>페이지를 찾을 수 없어</h1></section>`));
|
||||
}
|
||||
|
||||
function uploadResultPage(request, { heading, message, isError = false }) {
|
||||
const slug = escapeHtml(request.slug);
|
||||
return `<!doctype html>
|
||||
@@ -320,6 +368,8 @@ app.get("/", (req, res) => {
|
||||
res.status(404).send(page("페이지 없음", `<section class="panel"><h1>페이지를 찾을 수 없어</h1></section>`));
|
||||
});
|
||||
|
||||
app.use("/admin", requireAdminIp);
|
||||
|
||||
app.get("/admin/login", (req, res) => {
|
||||
res.send(page("관리자 로그인", `
|
||||
<section class="panel">
|
||||
|
||||
Reference in New Issue
Block a user