UNPKG

@cocalc/hub

Version:
125 lines 5.54 kB
"use strict"; /* 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