230 lines
5.9 KiB
JavaScript
230 lines
5.9 KiB
JavaScript
// server.js
|
|
const fs = require("fs");
|
|
const http = require("http");
|
|
const https = require("https");
|
|
const tls = require("tls");
|
|
const httpProxy = require("http-proxy");
|
|
const { WebSocketServer } = require('ws')
|
|
const path = require("path");
|
|
const {default: forms} = require("forms")
|
|
const z = require("zod")
|
|
|
|
// ---------------------------
|
|
// Request Log Store
|
|
// ---------------------------
|
|
let store;
|
|
|
|
// ---------------------------
|
|
// IP Helpers
|
|
// ---------------------------
|
|
function isLocalIP(ip) {
|
|
if (ip === "::1" || ip === "127.0.0.1") return true;
|
|
return false;
|
|
}
|
|
|
|
function getIP(req) {
|
|
const headers = req.headers;
|
|
|
|
let ip =
|
|
headers["cf-connecting-ip"] ||
|
|
headers["x-real-ip"] ||
|
|
headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
|
|
req.socket?.remoteAddress ||
|
|
req.connection?.remoteAddress ||
|
|
"";
|
|
|
|
// Normalize IPv6-mapped IPv4
|
|
if (ip.startsWith("::ffff:")) {
|
|
ip = ip.slice(7);
|
|
}
|
|
|
|
// Normalize localhost IPv6
|
|
if (ip === "::1") {
|
|
ip = "127.0.0.1";
|
|
}
|
|
|
|
return ip;
|
|
}
|
|
|
|
// ---------------------------
|
|
// Logging Middleware
|
|
// ---------------------------
|
|
function loggingMiddleware(next) {
|
|
return (req, res) => {
|
|
const ip = getIP(req);
|
|
|
|
let path = req.url.startsWith("/") ? req.url.slice(1) : req.url;
|
|
if (path === "") path = "/";
|
|
|
|
const now = new Date();
|
|
const h = now.getHours();
|
|
const hour12 = h % 12 === 0 ? 12 : h % 12;
|
|
const ampm = h < 12 ? "am" : "pm";
|
|
const timestamp = `${now.getMonth() + 1}.${now.getDate()} ${hour12}:${String(now.getMinutes()).padStart(2,"0")}:${String(now.getSeconds()).padStart(2,"0")}${ampm}`;
|
|
|
|
if (isLocalIP(ip)) {
|
|
console.log(`${req.headers.host} ${ip} ${path}`);
|
|
} else {
|
|
console.log(
|
|
`\x1b[33m${timestamp}\x1b[0m ${ip} \x1b[94m${req.headers.host} ${path}\x1b[0m`
|
|
);
|
|
}
|
|
|
|
store.add("log", {
|
|
time: new Date().toISOString(),
|
|
host: req.headers.host || "",
|
|
ip: ip,
|
|
path: path,
|
|
method: req.method
|
|
})
|
|
|
|
next(req, res);
|
|
};
|
|
}
|
|
|
|
// ---------------------------
|
|
// Proxy helper
|
|
// ---------------------------
|
|
const proxy = httpProxy.createProxyServer({});
|
|
|
|
function serveProxy(req, res, port) {
|
|
proxy.web(req, res, { target: `http://localhost:${port}` });
|
|
}
|
|
|
|
proxy.on("error", (err, req, res) => {
|
|
console.error("Proxy error:", err.message);
|
|
|
|
try {
|
|
res.statusCode = 502;
|
|
res.end("Bad Gateway");
|
|
} catch {}
|
|
});
|
|
|
|
proxy.on("proxyReq", (proxyReq, req, res) => {
|
|
proxyReq.setHeader("X-Forwarded-Proto", "https");
|
|
});
|
|
|
|
// ---------------------------
|
|
// Outside Requests
|
|
// ---------------------------
|
|
function getPortForHost(host) {
|
|
host = host.toLowerCase();
|
|
if (host.endsWith(".parchment.page")) host = "parchment.page";
|
|
|
|
switch (host) {
|
|
case "america.sun.museum":
|
|
return 8000
|
|
case "thefiveprinciples.org":
|
|
return 3001
|
|
case "americanforum.net":
|
|
return 3002
|
|
case "hyperia.so":
|
|
return 3003
|
|
|
|
case "pma.aryan.so":
|
|
case "aryan.so":
|
|
case "apply.aryan.so":
|
|
return 3004
|
|
|
|
case "parchment.page":
|
|
return 3005
|
|
case "government.forum":
|
|
return 3006
|
|
|
|
case "noahkurtis.com":
|
|
return 3007
|
|
case "comalyr.com":
|
|
return 3008
|
|
|
|
case "blockcatcher.sun.museum":
|
|
return 3009
|
|
|
|
case "git.sun.museum":
|
|
return 4000
|
|
case "log.sun.museum":
|
|
return 4001
|
|
case "admin.sun.museum":
|
|
return 8080
|
|
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
function handler(req, res) {
|
|
const port = getPortForHost(req.headers.host);
|
|
if (port) return serveProxy(req, res, port);
|
|
res.writeHead(200, {"Content-Type":"text/plain"});
|
|
res.end("Hello, World! You're from outside.");
|
|
}
|
|
|
|
// ---------------------------
|
|
// Begin
|
|
// ---------------------------
|
|
function startServer() {
|
|
const certs = {};
|
|
|
|
function loadCert(domain) {
|
|
const base = `/etc/letsencrypt/live/${domain}`;
|
|
const cert = {
|
|
key: fs.readFileSync(`${base}/privkey.pem`, "utf8"),
|
|
cert: fs.readFileSync(`${base}/fullchain.pem`, "utf8"),
|
|
};
|
|
certs[domain] = cert;
|
|
}
|
|
|
|
loadCert("parchment.page-0001");
|
|
loadCert("hyperia.so-0001");
|
|
|
|
const httpsOptions = {
|
|
SNICallback: (servername, cb) => {
|
|
servername = servername.toLowerCase();
|
|
|
|
if (certs[servername]) {
|
|
return cb(null, tls.createSecureContext(certs[servername]));
|
|
}
|
|
|
|
if (servername.endsWith(".parchment.page")) {
|
|
return cb(null, tls.createSecureContext(certs["parchment.page-0001"]));
|
|
}
|
|
|
|
return cb(null, tls.createSecureContext(certs["hyperia.so-0001"]));
|
|
},
|
|
minVersion: "TLSv1.2",
|
|
};
|
|
|
|
const server = https.createServer(httpsOptions, loggingMiddleware(handler));
|
|
|
|
server.on("upgrade", (req, socket, head) => {
|
|
const port = getPortForHost(req.headers.host);
|
|
if (!port) return socket.destroy();
|
|
proxy.ws(req, socket, head, { target: `http://localhost:${port}` });
|
|
socket.on("error", (err) => {
|
|
console.error("WebSocket error:", err.message);
|
|
socket.destroy();
|
|
});
|
|
});
|
|
|
|
|
|
async function connectToForms() {
|
|
await store.connect()
|
|
store.register("log", z.toJSONSchema(z.object({
|
|
time: z.string(),
|
|
host: z.string(),
|
|
ip: z.string(),
|
|
path: z.string(),
|
|
method: z.string()
|
|
})))
|
|
}
|
|
|
|
store = new forms.client()
|
|
connectToForms()
|
|
|
|
server.listen(3000, () => {
|
|
console.log("🔒 HTTPS server running on port 3000");
|
|
});
|
|
}
|
|
|
|
startServer()
|
|
|