diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 1b7b03daa269..2c181c08db79 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -1119,6 +1119,14 @@ "type": "string", "description": "The URL that the browser client (or live-reload client, if enabled) should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies." }, + "allowedHosts": { + "type": "array", + "description": "Whitelist of hosts that are allowed to access the dev server.", + "default": [], + "items": { + "type": "string" + } + }, "servePath": { "type": "string", "description": "The pathname where the app will be served." diff --git a/packages/angular_devkit/build_angular/src/dev-server/index.ts b/packages/angular_devkit/build_angular/src/dev-server/index.ts index 6b15ab96ebd4..b253622c6d08 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/index.ts +++ b/packages/angular_devkit/build_angular/src/dev-server/index.ts @@ -342,6 +342,7 @@ export function buildServerConfig( // inline is always false, because we add live reloading scripts in _addLiveReload when needed inline: false, public: serverOptions.publicHost, + allowedHosts: serverOptions.allowedHosts, disableHostCheck: serverOptions.disableHostCheck, publicPath: servePath, hot: serverOptions.hmr, diff --git a/packages/angular_devkit/build_angular/src/dev-server/schema.json b/packages/angular_devkit/build_angular/src/dev-server/schema.json index 5b7c153fb451..ff1623b9d55b 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/schema.json +++ b/packages/angular_devkit/build_angular/src/dev-server/schema.json @@ -55,6 +55,14 @@ "type": "string", "description": "The URL that the browser client (or live-reload client, if enabled) should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies." }, + "allowedHosts": { + "type": "array", + "description": "Whitelist of hosts that are allowed to access the dev server.", + "default": [], + "items": { + "type": "string" + } + }, "servePath": { "type": "string", "description": "The pathname where the app will be served." diff --git a/packages/angular_devkit/build_angular/test/dev-server/allowed-hosts_spec_large.ts b/packages/angular_devkit/build_angular/test/dev-server/allowed-hosts_spec_large.ts new file mode 100644 index 000000000000..3b6767c9abf2 --- /dev/null +++ b/packages/angular_devkit/build_angular/test/dev-server/allowed-hosts_spec_large.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { Architect, BuilderRun } from '@angular-devkit/architect'; +import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies +import { DevServerBuilderOutput } from '../../src/dev-server'; +import { createArchitect, host } from '../utils'; + + +describe('Dev Server Builder allowed host', () => { + // We have to spoof the host to a non-numeric one because Webpack Dev Server does not + // check the hosts anymore when requests come from numeric IP addresses. + const headers = { host: 'spoofy.mcspoofface' }; + + const target = { project: 'app', target: 'serve' }; + let architect: Architect; + // We use runs like this to ensure it WILL stop the servers at the end of each tests. + let runs: BuilderRun[]; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + runs = []; + }); + afterEach(async () => { + await host.restore().toPromise(); + await Promise.all(runs.map(r => r.stop())); + }); + + it('works', async () => { + const run = await architect.scheduleTarget(target, { allowedHosts: ['spoofy.mcspoofface'] }); + runs.push(run); + const output = await run.result as DevServerBuilderOutput; + expect(output.success).toBe(true); + expect(output.baseUrl).toBe('http://localhost:4200/'); + + const response = await fetch(`${output.baseUrl}`, { headers }); + expect(await response.text()).toContain('