@cocalc/hub
Version:
CoCalc: Backend webserver component
125 lines • 5.54 kB
JavaScript
;
/* Handle a proxy request */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_proxy_1 = require("http-proxy");
const lru_cache_1 = __importDefault(require("lru-cache"));
const strip_remember_me_cookie_1 = __importDefault(require("./strip-remember-me-cookie"));
const version_1 = require("./version");
const target_1 = require("./target");
const logger_1 = __importDefault(require("../logger"));
const util_1 = require("./util");
const site_url_1 = __importDefault(require("@cocalc/server/settings/site-url"));
const winston = (0, logger_1.default)("proxy: handle-request");
function init({ projectControl, isPersonal }) {
/* Cache at most 5000 proxies, each for up to 3 minutes.
Throwing away proxies at any time from the cache is fine since
the proxy is just used to handle *individual* http requests,
and the cache is entirely for speed. Also, invalidating cache entries
works around weird cases, where maybe error/close don't get
properly called, but the proxy is not working due to network
issues. Invalidating cache entries quickly is also good from
a permissions and security point of view.
*/
const cache = new lru_cache_1.default({
max: 5000,
maxAge: 1000 * 60 * 3,
dispose: (_key, proxy) => {
// important to close the proxy whenever it gets removed
// from the cache, to avoid wasting resources.
proxy?.close();
},
});
async function handleProxyRequest(req, res) {
const dbg = (m) => {
// for low level debugging -- silly isn't logged by default
winston.silly(`${req.url}: ${m}`);
};
dbg("got request");
if (!isPersonal && (0, version_1.versionCheckFails)(req, res)) {
dbg("version check failed");
// note that the versionCheckFails function already sent back an error response.
return;
}
// Before doing anything further with the request on to the proxy, we remove **all** cookies whose
// name contains "remember_me", to prevent the project backend from getting at
// the user's session cookie, since one project shouldn't be able to get
// access to any user's account.
let remember_me;
if (req.headers["cookie"] != null) {
let cookie;
({ cookie, remember_me } = (0, strip_remember_me_cookie_1.default)(req.headers["cookie"]));
req.headers["cookie"] = cookie;
}
if (!isPersonal && !remember_me) {
dbg("no rememember me set, so blocking");
// Not in personal mode and there is no remember me set all, so
// definitely block access. 4xx since this is a *client* problem.
res.writeHead(426, { "Content-Type": "text/html" });
const url = await (0, site_url_1.default)();
res.end(`Please login to <a target='_blank' href='${url}'>${url}</a> with cookies enabled, then refresh this page.`);
return;
}
const url = (0, util_1.stripBasePath)(req.url);
const { host, port, internal_url } = await (0, target_1.getTarget)({
remember_me,
url,
isPersonal,
projectControl,
});
// It's http here because we've already got past the ssl layer. This is all internal.
const target = `http://${host}:${port}`;
dbg(`target resolves to ${target}`);
let proxy;
if (cache.has(target)) {
// we already have the proxy for this target in the cache
dbg("using cached proxy");
proxy = cache.get(target);
}
else {
dbg(`make a new proxy server to ${target}`);
proxy = (0, http_proxy_1.createProxyServer)({
ws: false,
target,
timeout: 60000,
});
// and cache it.
cache.set(target, proxy);
dbg("created new proxy");
// setup error handler, so that if something goes wrong with this proxy (it will,
// e.g., on project restart), we properly invalidate it.
const remove_from_cache = () => {
cache.del(target); // this also closes the proxy.
(0, target_1.invalidateTargetCache)(remember_me, url);
};
proxy.on("error", (e) => {
dbg(`http proxy error event (ending proxy) -- ${e}`);
remove_from_cache();
});
proxy.on("close", remove_from_cache);
}
if (internal_url != null) {
dbg(`changing req url from ${req.url} to ${internal_url}`);
req.url = internal_url;
}
dbg("handling the request using the proxy");
proxy.web(req, res);
}
return async (req, res) => {
try {
await handleProxyRequest(req, res);
}
catch (err) {
const msg = `WARNING: error proxying request ${req.url} -- ${err}`;
res.writeHead(500, { "Content-Type": "text/html" });
res.end(msg);
// Not something to log as an error; it's normal for it to happen, e.g., when
// a project isn't running.
winston.debug(msg);
}
};
}
exports.default = init;
//# sourceMappingURL=handle-request.js.map