Move domain proxy to routes
This matches better with the other routes. Also add a missing authentication check to the path proxy web socket.
This commit is contained in:
parent
f6c4434191
commit
f7076247f9
|
@ -1,10 +1,8 @@
|
||||||
import { Request, Router } from "express"
|
|
||||||
import proxyServer from "http-proxy"
|
import proxyServer from "http-proxy"
|
||||||
import { HttpCode, HttpError } from "../common/http"
|
import { HttpCode } from "../common/http"
|
||||||
import { authenticated, ensureAuthenticated, redirect } from "./http"
|
|
||||||
import { Router as WsRouter } from "./wsRouter"
|
|
||||||
|
|
||||||
export const proxy = proxyServer.createProxyServer({})
|
export const proxy = proxyServer.createProxyServer({})
|
||||||
|
|
||||||
proxy.on("error", (error, _, res) => {
|
proxy.on("error", (error, _, res) => {
|
||||||
res.writeHead(HttpCode.ServerError)
|
res.writeHead(HttpCode.ServerError)
|
||||||
res.end(error.message)
|
res.end(error.message)
|
||||||
|
@ -16,85 +14,3 @@ proxy.on("proxyRes", (res, req) => {
|
||||||
res.headers.location = (req as any).base + res.headers.location
|
res.headers.location = (req as any).base + res.headers.location
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const router = Router()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the port if the request should be proxied. Anything that ends in a
|
|
||||||
* proxy domain and has a *single* subdomain should be proxied. Anything else
|
|
||||||
* should return `undefined` and will be handled as normal.
|
|
||||||
*
|
|
||||||
* For example if `coder.com` is specified `8080.coder.com` will be proxied
|
|
||||||
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
|
|
||||||
*/
|
|
||||||
const maybeProxy = (req: Request): string | undefined => {
|
|
||||||
// Split into parts.
|
|
||||||
const host = req.headers.host || ""
|
|
||||||
const idx = host.indexOf(":")
|
|
||||||
const domain = idx !== -1 ? host.substring(0, idx) : host
|
|
||||||
const parts = domain.split(".")
|
|
||||||
|
|
||||||
// There must be an exact match.
|
|
||||||
const port = parts.shift()
|
|
||||||
const proxyDomain = parts.join(".")
|
|
||||||
if (!port || !req.args["proxy-domain"].includes(proxyDomain)) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
|
|
||||||
router.all("*", (req, res, next) => {
|
|
||||||
const port = maybeProxy(req)
|
|
||||||
if (!port) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be authenticated to use the proxy.
|
|
||||||
if (!authenticated(req)) {
|
|
||||||
// Let the assets through since they're used on the login page.
|
|
||||||
if (req.path.startsWith("/static/") && req.method === "GET") {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume anything that explicitly accepts text/html is a user browsing a
|
|
||||||
// page (as opposed to an xhr request). Don't use `req.accepts()` since
|
|
||||||
// *every* request that I've seen (in Firefox and Chromium at least)
|
|
||||||
// includes `*/*` making it always truthy.
|
|
||||||
if (typeof req.headers.accepts === "string" && req.headers.accepts.split(",").includes("text/html")) {
|
|
||||||
// Let the login through.
|
|
||||||
if (/\/login\/?/.test(req.path)) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
// Redirect all other pages to the login.
|
|
||||||
return redirect(req, res, "login", {
|
|
||||||
to: req.path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything else gets an unauthorized message.
|
|
||||||
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy.web(req, res, {
|
|
||||||
ignorePath: true,
|
|
||||||
target: `http://0.0.0.0:${port}${req.originalUrl}`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
export const wsRouter = WsRouter()
|
|
||||||
|
|
||||||
wsRouter.ws("*", (req, _, next) => {
|
|
||||||
const port = maybeProxy(req)
|
|
||||||
if (!port) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be authenticated to use the proxy.
|
|
||||||
ensureAuthenticated(req)
|
|
||||||
|
|
||||||
proxy.ws(req, req.ws, req.head, {
|
|
||||||
ignorePath: true,
|
|
||||||
target: `http://0.0.0.0:${port}${req.originalUrl}`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Request, Router } from "express"
|
||||||
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
|
import { authenticated, ensureAuthenticated, redirect } from "../http"
|
||||||
|
import { proxy } from "../proxy"
|
||||||
|
import { Router as WsRouter } from "../wsRouter"
|
||||||
|
|
||||||
|
export const router = Router()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the port if the request should be proxied. Anything that ends in a
|
||||||
|
* proxy domain and has a *single* subdomain should be proxied. Anything else
|
||||||
|
* should return `undefined` and will be handled as normal.
|
||||||
|
*
|
||||||
|
* For example if `coder.com` is specified `8080.coder.com` will be proxied
|
||||||
|
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
|
||||||
|
*/
|
||||||
|
const maybeProxy = (req: Request): string | undefined => {
|
||||||
|
// Split into parts.
|
||||||
|
const host = req.headers.host || ""
|
||||||
|
const idx = host.indexOf(":")
|
||||||
|
const domain = idx !== -1 ? host.substring(0, idx) : host
|
||||||
|
const parts = domain.split(".")
|
||||||
|
|
||||||
|
// There must be an exact match.
|
||||||
|
const port = parts.shift()
|
||||||
|
const proxyDomain = parts.join(".")
|
||||||
|
if (!port || !req.args["proxy-domain"].includes(proxyDomain)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
router.all("*", (req, res, next) => {
|
||||||
|
const port = maybeProxy(req)
|
||||||
|
if (!port) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be authenticated to use the proxy.
|
||||||
|
if (!authenticated(req)) {
|
||||||
|
// Let the assets through since they're used on the login page.
|
||||||
|
if (req.path.startsWith("/static/") && req.method === "GET") {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume anything that explicitly accepts text/html is a user browsing a
|
||||||
|
// page (as opposed to an xhr request). Don't use `req.accepts()` since
|
||||||
|
// *every* request that I've seen (in Firefox and Chromium at least)
|
||||||
|
// includes `*/*` making it always truthy.
|
||||||
|
if (typeof req.headers.accepts === "string" && req.headers.accepts.split(",").includes("text/html")) {
|
||||||
|
// Let the login through.
|
||||||
|
if (/\/login\/?/.test(req.path)) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
// Redirect all other pages to the login.
|
||||||
|
return redirect(req, res, "login", {
|
||||||
|
to: req.path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else gets an unauthorized message.
|
||||||
|
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.web(req, res, {
|
||||||
|
ignorePath: true,
|
||||||
|
target: `http://0.0.0.0:${port}${req.originalUrl}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const wsRouter = WsRouter()
|
||||||
|
|
||||||
|
wsRouter.ws("*", (req, _, next) => {
|
||||||
|
const port = maybeProxy(req)
|
||||||
|
if (!port) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be authenticated to use the proxy.
|
||||||
|
ensureAuthenticated(req)
|
||||||
|
|
||||||
|
proxy.ws(req, req.ws, req.head, {
|
||||||
|
ignorePath: true,
|
||||||
|
target: `http://0.0.0.0:${port}${req.originalUrl}`,
|
||||||
|
})
|
||||||
|
})
|
|
@ -13,12 +13,12 @@ import { rootPath } from "../constants"
|
||||||
import { Heart } from "../heart"
|
import { Heart } from "../heart"
|
||||||
import { replaceTemplates } from "../http"
|
import { replaceTemplates } from "../http"
|
||||||
import { loadPlugins } from "../plugin"
|
import { loadPlugins } from "../plugin"
|
||||||
import * as domainProxy from "../proxy"
|
|
||||||
import { getMediaMime, paths } from "../util"
|
import { getMediaMime, paths } from "../util"
|
||||||
import { WebsocketRequest } from "../wsRouter"
|
import { WebsocketRequest } from "../wsRouter"
|
||||||
|
import * as domainProxy from "./domainProxy"
|
||||||
import * as health from "./health"
|
import * as health from "./health"
|
||||||
import * as login from "./login"
|
import * as login from "./login"
|
||||||
import * as proxy from "./proxy"
|
import * as proxy from "./pathProxy"
|
||||||
// static is a reserved keyword.
|
// static is a reserved keyword.
|
||||||
import * as _static from "./static"
|
import * as _static from "./static"
|
||||||
import * as update from "./update"
|
import * as update from "./update"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Request, Router } from "express"
|
import { Request, Router } from "express"
|
||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
import { HttpCode, HttpError } from "../../common/http"
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
import { authenticated, redirect } from "../http"
|
import { authenticated, ensureAuthenticated, redirect } from "../http"
|
||||||
import { proxy } from "../proxy"
|
import { proxy } from "../proxy"
|
||||||
import { Router as WsRouter } from "../wsRouter"
|
import { Router as WsRouter } from "../wsRouter"
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ router.all("/(:port)(/*)?", (req, res) => {
|
||||||
|
|
||||||
export const wsRouter = WsRouter()
|
export const wsRouter = WsRouter()
|
||||||
|
|
||||||
wsRouter.ws("/(:port)(/*)?", (req) => {
|
wsRouter.ws("/(:port)(/*)?", ensureAuthenticated, (req) => {
|
||||||
proxy.ws(req, req.ws, req.head, {
|
proxy.ws(req, req.ws, req.head, {
|
||||||
ignorePath: true,
|
ignorePath: true,
|
||||||
target: getProxyTarget(req, true),
|
target: getProxyTarget(req, true),
|
Loading…
Reference in New Issue