Ollama: replace informational stub with real quota tracker hitting ollama.com/api/usage (session 5h + weekly 7d, 0..1 ratio) and /api/me plan label; bind handler to apiKey + add features.usageApikey so apikey connections work.
Token refresh: add backgroundTokenRefresh scheduler that refreshes OAuth connections within max(provider lead, 30min) of expiry, independent of inbound traffic (10s after boot, then every 5min, unref'd timers, DISABLE_BACKGROUND_TOKEN_REFRESH kill-switch, fail-open per tick/connection). Registered from custom-server.js (listening) and initializeApp.js. checkAndRefreshToken gains opt-in {force} for the scheduler; request path unchanged.
72 lines
2.8 KiB
JavaScript
72 lines
2.8 KiB
JavaScript
const http = require("http");
|
|
const path = require("path");
|
|
const { pathToFileURL } = require("url");
|
|
|
|
const origCreate = http.createServer.bind(http);
|
|
|
|
let backgroundRefreshStarted = false;
|
|
|
|
function startBackgroundTokenRefreshFromCustomServer() {
|
|
if (backgroundRefreshStarted) return;
|
|
backgroundRefreshStarted = true;
|
|
// Prefer source path (repo / standalone that still has src). Fail-open if missing
|
|
// — initializeApp also starts the same scheduler when the Next app boots.
|
|
const modPath = path.join(__dirname, "src", "sse", "services", "backgroundTokenRefresh.js");
|
|
import(pathToFileURL(modPath).href)
|
|
.then((m) => {
|
|
try {
|
|
m.startBackgroundTokenRefresh();
|
|
} catch (e) {
|
|
console.error("[BackgroundTokenRefresh] start failed:", e && e.message ? e.message : e);
|
|
}
|
|
const stop = () => {
|
|
try {
|
|
m.stopBackgroundTokenRefresh();
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
};
|
|
process.once("SIGINT", stop);
|
|
process.once("SIGTERM", stop);
|
|
})
|
|
.catch((e) => {
|
|
// Expected in published CLI standalone (src/ not on disk). App bootstrap covers it.
|
|
if (process.env.DEBUG_BACKGROUND_TOKEN_REFRESH) {
|
|
console.error("[BackgroundTokenRefresh] import failed:", e && e.message ? e.message : e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Wrap Next standalone HTTP server: derive client IP from the TCP socket
|
|
// (unspoofable) and strip client-supplied forwarding headers so downstream
|
|
// rate-limiting keys on the real peer address instead of attacker-controlled XFF.
|
|
http.createServer = (...args) => {
|
|
const handler = args.find((a) => typeof a === "function");
|
|
const rest = args.filter((a) => typeof a !== "function");
|
|
if (!handler) return origCreate(...args);
|
|
const wrapped = (req, res) => {
|
|
const socketIp = req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : "";
|
|
const xff = req.headers["x-forwarded-for"];
|
|
const xRealIp = req.headers["x-real-ip"];
|
|
const viaProxy = !!(xff || xRealIp);
|
|
const isLoopbackProxy = socketIp === "127.0.0.1" || socketIp === "::1" || socketIp === "::ffff:127.0.0.1";
|
|
// Trust forwarding headers only when the TCP peer is a local reverse proxy.
|
|
// Direct/public sockets remain keyed by the unspoofable peer address.
|
|
const proxyIp = xRealIp || (xff ? String(xff).split(",")[0].trim() : "");
|
|
const ip = isLoopbackProxy && proxyIp ? proxyIp : socketIp;
|
|
delete req.headers["x-9r-real-ip"];
|
|
delete req.headers["x-forwarded-for"];
|
|
delete req.headers["x-9r-via-proxy"];
|
|
req.headers["x-9r-real-ip"] = ip;
|
|
if (viaProxy) req.headers["x-9r-via-proxy"] = "1";
|
|
return handler(req, res);
|
|
};
|
|
const server = origCreate(...rest, wrapped);
|
|
server.once("listening", () => {
|
|
startBackgroundTokenRefreshFromCustomServer();
|
|
});
|
|
return server;
|
|
};
|
|
|
|
require("./server.js");
|