Only handle exact domain matches

This simplifies the logic a bit.
This commit is contained in:
Asher 2020-03-23 14:51:58 -05:00
parent 3a98d856a5
commit 2086648c87
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
2 changed files with 32 additions and 36 deletions

View File

@ -6,8 +6,15 @@ import { HttpProvider, HttpProviderOptions, HttpProxyProvider, HttpResponse, Rou
* Proxy HTTP provider. * Proxy HTTP provider.
*/ */
export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider { export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider {
/**
* Proxy domains are stored here without the leading `*.`
*/
public readonly proxyDomains: string[] public readonly proxyDomains: string[]
/**
* Domains can be provided in the form `coder.com` or `*.coder.com`. Either
* way, `<number>.coder.com` will be proxied to `number`.
*/
public constructor(options: HttpProviderOptions, proxyDomains: string[] = []) { public constructor(options: HttpProviderOptions, proxyDomains: string[] = []) {
super(options) super(options)
this.proxyDomains = proxyDomains.map((d) => d.replace(/^\*\./, "")).filter((d, i, arr) => arr.indexOf(d) === i) this.proxyDomains = proxyDomains.map((d) => d.replace(/^\*\./, "")).filter((d, i, arr) => arr.indexOf(d) === i)
@ -29,12 +36,14 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
throw new HttpError("Not found", HttpCode.NotFound) throw new HttpError("Not found", HttpCode.NotFound)
} }
public getProxyDomain(host?: string): string | undefined { public getCookieDomain(host: string): string {
if (!host || !this.proxyDomains) { let current: string | undefined
return undefined this.proxyDomains.forEach((domain) => {
} if (host.endsWith(domain) && (!current || domain.length < current.length)) {
current = domain
return this.proxyDomains.find((d) => host.endsWith(d)) }
})
return current || host
} }
public maybeProxy(request: http.IncomingMessage): HttpResponse | undefined { public maybeProxy(request: http.IncomingMessage): HttpResponse | undefined {
@ -44,23 +53,21 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
return undefined return undefined
} }
// At minimum there needs to be sub.domain.tld.
const host = request.headers.host const host = request.headers.host
const proxyDomain = this.getProxyDomain(host) const parts = host && host.split(".")
if (!host || !proxyDomain) { if (!parts || parts.length < 3) {
return undefined return undefined
} }
const proxyDomainLength = proxyDomain.split(".").length // There must be an exact match.
const portStr = host const port = parts.shift()
.split(".") const proxyDomain = parts.join(".")
.slice(0, -proxyDomainLength) if (!port || !this.proxyDomains.includes(proxyDomain)) {
.pop()
if (!portStr) {
return undefined return undefined
} }
return this.proxy(portStr) return this.proxy(port)
} }
private proxy(portStr: string): HttpResponse { private proxy(portStr: string): HttpResponse {

View File

@ -397,27 +397,20 @@ export interface HttpProvider3<A1, A2, A3, T> {
export interface HttpProxyProvider { export interface HttpProxyProvider {
/** /**
* Return a response if the request should be proxied. Anything that ends in a * Return a response if the request should be proxied. Anything that ends in a
* proxy domain and has a subdomain should be proxied. The port is found in * proxy domain and has a *single* subdomain should be proxied. Anything else
* the top-most subdomain. * should return `undefined` and will be handled as normal.
* *
* For example, if the proxy domain is `coder.com` then `8080.coder.com` and * For example if `coder.com` is specified `8080.coder.com` will be proxied
* `test.8080.coder.com` will both proxy to `8080` but `8080.test.coder.com` * but `8080.test.coder.com` and `test.8080.coder.com` will not.
* will have an error because `test` isn't a port. If the proxy domain was
* `test.coder.com` then it would work.
*/ */
maybeProxy(request: http.IncomingMessage): HttpResponse | undefined maybeProxy(request: http.IncomingMessage): HttpResponse | undefined
/** /**
* Get the matching proxy domain based on the provided host. * Get the domain that should be used for setting a cookie. This will allow
* the user to authenticate only once. This will return the highest level
* domain (e.g. `coder.com` over `test.coder.com` if both are specified).
*/ */
getProxyDomain(host: string): string | undefined getCookieDomain(host: string): string | undefined
/**
* Domains can be provided in the form `coder.com` or `*.coder.com`. Either
* way, `<number>.coder.com` will be proxied to `number`. The domains are
* stored here without the `*.`.
*/
readonly proxyDomains: string[]
} }
/** /**
@ -560,12 +553,8 @@ export class HttpServer {
"Set-Cookie": [ "Set-Cookie": [
`${payload.cookie.key}=${payload.cookie.value}`, `${payload.cookie.key}=${payload.cookie.value}`,
`Path=${normalize(payload.cookie.path || "/", true)}`, `Path=${normalize(payload.cookie.path || "/", true)}`,
// Set the cookie against the host so it can be used in
// subdomains. Use a matching proxy domain if possible so
// requests to any of those subdomains will already be
// authenticated.
request.headers.host request.headers.host
? `Domain=${(this.proxy && this.proxy.getProxyDomain(request.headers.host)) || request.headers.host}` ? `Domain=${(this.proxy && this.proxy.getCookieDomain(request.headers.host)) || request.headers.host}`
: undefined, : undefined,
// "HttpOnly", // "HttpOnly",
"SameSite=strict", "SameSite=strict",