diff --git a/lib/Server.js b/lib/Server.js index 9dff455f7d..ee1bf43997 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -40,6 +40,7 @@ function Server(compiler, options) { this.clientOverlay = options.overlay; this.disableHostCheck = !!options.disableHostCheck; this.publicHost = options.public; + this.allowedHosts = options.allowedHosts; this.sockets = []; this.contentBaseWatchers = []; @@ -443,6 +444,21 @@ Server.prototype.checkHost = function(headers) { // always allow localhost host, for convience if(hostname === "127.0.0.1" || hostname === "localhost") return true; + // allow if hostname is in allowedHosts + if(this.allowedHosts && this.allowedHosts.length) { + for(let hostIdx = 0; hostIdx < this.allowedHosts.length; hostIdx++) { + const allowedHost = this.allowedHosts[hostIdx]; + if(allowedHost === hostname) return true; + + // support "." as a subdomain wildcard + // e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc + if(allowedHost[0] === ".") { + if(hostname === allowedHost.substring(1)) return true; // "example.com" + if(hostname.endsWith(allowedHost)) return true; // "*.example.com" + } + } + } + // allow hostname of listening adress if(hostname === this.listenHostname) return true; diff --git a/lib/optionsSchema.json b/lib/optionsSchema.json index 49c60db1ee..34b73e5ad2 100644 --- a/lib/optionsSchema.json +++ b/lib/optionsSchema.json @@ -17,6 +17,13 @@ "description": "The host the server listens to.", "type": "string" }, + "allowedHosts": { + "description": "Specifies which hosts are allowed to access the dev server.", + "items": { + "type": "string" + }, + "type": "array" + }, "filename": { "description": "The filename that needs to be requested in order to trigger a recompile (only in lazy mode).", "anyOf": [ diff --git a/test/Validation.test.js b/test/Validation.test.js index 2be20c4fc3..aeef67b703 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -22,6 +22,14 @@ describe("Validation", function() { message: [ " - configuration.public should be a string." ] + }, { + name: "invalid `allowedHosts` configuration", + config: { allowedHosts: 1 }, + message: [ + " - configuration.allowedHosts should be an array:", + " [string]", + " Specifies which hosts are allowed to access the dev server." + ] }, { name: "invalid `contentBase` configuration", config: { contentBase: [0] }, @@ -40,7 +48,7 @@ describe("Validation", function() { config: { asdf: true }, message: [ " - configuration has an unknown property 'asdf'. These properties are valid:", - " object { hot?, hotOnly?, lazy?, host?, filename?, publicPath?, port?, socket?, " + + " object { hot?, hotOnly?, lazy?, host?, allowedHosts?, filename?, publicPath?, port?, socket?, " + "watchOptions?, headers?, clientLogLevel?, overlay?, key?, cert?, ca?, pfx?, pfxPassphrase?, " + "inline?, disableHostCheck?, public?, https?, contentBase?, watchContentBase?, open?, features?, " + "compress?, proxy?, historyApiFallback?, staticOptions?, setup?, stats?, reporter?, " + @@ -115,5 +123,43 @@ describe("Validation", function() { throw new Error("Validation didn't fail"); } }); + + describe("allowedHosts", function() { + it("should allow hosts in allowedHosts", function() { + const testHosts = [ + "test.host", + "test2.host", + "test3.host" + ]; + const options = { allowedHosts: testHosts }; + const server = new Server(compiler, options); + + testHosts.forEach(function(testHost) { + const headers = { host: testHost }; + if(!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); + }); + it("should allow hosts that pass a wildcard in allowedHosts", function() { + const options = { allowedHosts: [".example.com"] }; + const server = new Server(compiler, options); + const testHosts = [ + "www.example.com", + "subdomain.example.com", + "example.com", + "subsubcomain.subdomain.example.com", + "example.com:80", + "subdomain.example.com:80" + ]; + + testHosts.forEach(function(testHost) { + const headers = { host: testHost }; + if(!server.checkHost(headers)) { + throw new Error("Validation didn't fail"); + } + }); + }); + }); }) });