diff --git a/src/ReverseProxy.js b/src/ReverseProxy.js index b03cc9f..1f52ec9 100644 --- a/src/ReverseProxy.js +++ b/src/ReverseProxy.js @@ -21,7 +21,6 @@ class ReverseProxy { console.log(`Database opened with '${routeCount}' routes`); // Bind to ports - const bindedProcessReq = this.#ProcessRequest.bind(this); const httpPort = 80, httpsPort = 443; try { this.HttpEndpoint = await this.#CreateHttpEndpoint(httpPort); @@ -40,22 +39,7 @@ class ReverseProxy { this.HttpEndpoint.on("upgrade", this.#HandleWebSocketUpgrade.bind(this)); this.HttpsEndpoint.on("upgrade", this.#HandleWebSocketUpgrade.bind(this)); // This is for logging proxy results - this.ProxyService.on("proxyRes", (proxyResponse, request, response) => { - // Check if there is an auth/access error - let internalStatus = response.statusCode; - if (response.statusCode == 511) { - if (IsNullOrUndefined(request.bailHandler)) { - response.writeHead(404, { "Content-Type": "text/plain" }); - response.end(`You do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down`); - console.log(`[${internalStatus} as 404] ${request.method} ${hostname ?? request.headers?.host}${request.url}`); - proxyResponse.destroy(); - return; - } - return request.bailHandler(internalStatus); - } - // Else let the response go through - console.log(`[${proxyResponse.statusCode}] ${request.method} '${request.headers?.host ?? ''}${request.url}'`); - }); + this.ProxyService.on("proxyRes", this.#PostProcessResponse.bind(this)); // Start listening // except we already started listening when initializing the http/s endpoints @@ -63,7 +47,7 @@ class ReverseProxy { } async #CreateHttpEndpoint(port = 80, { cancellationToken } = {}) { - let endpoint = CreateHttpListener(this.#ProcessRequest.bind(this)); + let endpoint = CreateHttpListener(this.#HandleRequest.bind(this)); const raceConditions = [ once(endpoint, "listening"), once(endpoint, "error").then(([err]) => Promise.reject(err)), @@ -95,7 +79,7 @@ class ReverseProxy { let endpoint = CreateHttpsListener({ key: readFileSync(keyPath), cert: readFileSync(certPath) - }, this.#ProcessRequest.bind(this)); + }, this.#HandleRequest.bind(this)); // Subscribing to these events once before calling .listen // to make sure they get called, even though they should be @@ -147,7 +131,7 @@ class ReverseProxy { return urlStr.substring(pathStartIndex, queryStartIndex); } - #ProcessRequest(request, response) { + #HandleRequest(request, response) { if (ReverseProxy.IsInternalAddress(request.socket.remoteAddress)) { switch (ReverseProxy.GetPath(request.url)) { case "/ready": @@ -160,42 +144,78 @@ class ReverseProxy { return; } } - // Regular - const { host } = request.headers; - const resolvedHost = this.TryResolveInternalHost(host); - const { hostname } = new URL(resolvedHost); + const _bail = (internalStatus = 404) => { response.writeHead(404, { "Content-Type": "text/plain" }); - response.end(`You do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down`); - console.log(`[${internalStatus} as 404] ${request.method} ${hostname ?? request.headers?.host}${request.url}`); + response.end(`You do not have the permissions to access '${request.originalHost}', it's endpoint does not exist or the origin may be down`); + console.log(`[${internalStatus} as 404] ${request.method} ${request.headers?.host ?? ''}${request.url}`); }; - if (IsNullOrUndefined(resolvedHost)) - return _bail(404); - try { - request.headers.host = hostname; - this.ProxyService.web(request, response, { target: resolvedHost, xfwd: true }, proxyException => { - return _bail(502); // Bad Gateway: Error related to proxying - }); - } catch (proxyError) { return _bail(500); } + request = this.#PostProcessRequest(request, _bail); + if (IsNullOrUndefined(request)) return; + + this.ProxyService.web(request, response, { target: request.resolvedHost, xfwd: true }, proxyException => { + return _bail(502); // Bad Gateway: Error related to proxying + }); } #HandleWebSocketUpgrade(request, socket, head) { - const { host } = request.headers; - const resolvedHost = this.TryResolveInternalHost(host); - const { hostname } = new URL(resolvedHost); const _bail = (internalStatus = 404) => { - console.log(`[${internalStatus} as 404] ${request.method}=>WS ${req.headers?.host ?? ''}${req.url}`); - socket.write(`HTTP/1.1 404 Not Found\r\nYou do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down\r\n`); + console.log(`[${internalStatus} as 404] ${request.method}=>WS ${request.headers?.host ?? ''}${req.url}`); + socket.write(`HTTP/1.1 404 Not Found\r\nYou do not have the permissions to access '${request.originalHost}', it's endpoint does not exist or the origin may be down\r\n`); socket.destroy(); }; - if (IsNullOrUndefined(resolvedHost)) - return _bail(404); + request = this.#PostProcessRequest(request, _bail); + if (IsNullOrUndefined(request)) return; + + this.ProxyService.ws(request, socket, head, { target: request.resolvedHost, xfwd: true }, proxyException => { + return _bail(502); // Bad Gateway: Error related to proxying + }); + } + + #PostProcessRequest(request, bailHandler = undefined) { + const host = request.headers.host.trim(); + if (IsNullOrUndefined(host) || host.length == 0) return bailHandler(412); // Preconditions Failed: Missing 'Host' header + const resolvedHost = this.TryResolveInternalHost(host); + if (IsNullOrUndefined(resolvedHost)) return bailHandler(404); // Not found: No server to proxy/forward to was found + let resolvedHostname; try { - request.headers.host = hostname; - this.ProxyService.ws(Request, socket, head, { target: resolvedHost, xfwd: true }, proxyException => { - return _bail(502); - }); - } catch (proxyError) { return _bail(500); } + // In the future I could make a path differ, so that I can forward + // https://domain.com[/my/path] => http://internalHostname/sub[/my/path] + resolvedHostname = new URL(resolvedHost).hostname.trim(); + + // Appropriate to add here, since throwing will cause a 500, + // since its the source of our data that caused the issue + if (IsNullOrUndefined(resolvedHostname) + || resolvedHostname.length == 0 + ) throw new TypeError(`Malformed internal hostname for '${host}'`); + } catch { return bailHandler(500); } // Internal Server Error: Because data on server-side is messed up + + // Lets do some forgery!! We want the internal server to know the hostname that it was accessed with, + // port is irrelevent in this case, so don't include it + request.originalHost = host; + request.resolvedHost = resolvedHost; + request.headers.host = resolvedHostname; + // Attach the bailHandler to the request, so proxyRes can call it in case it needs to + request.bailHandler = bailHandler; + // Request is ready to proxy, send it out + return request; + } + + #PostProcessResponse(proxyResponse, request, response) { + // Check if there is an auth/access error + let internalStatus = response.statusCode; + if (response.statusCode == 511) { + if (IsNullOrUndefined(request.bailHandler)) { + response.writeHead(404, { "Content-Type": "text/plain" }); + response.end(`You do not have the permissions to access '${host}', it's endpoint does not exist or the origin may be down`); + console.log(`[${internalStatus} as 404] ${request.method} ${hostname ?? request.headers?.host}${request.url}`); + proxyResponse.destroy(); + return; + } + return request.bailHandler(internalStatus); + } + // Else let the response go through + console.log(`[${proxyResponse.statusCode}] ${request.method} '${request.headers?.host ?? ''}${request.url}'`); } TryResolveInternalHost(publicHost = "") {