Repository: http-party/http-server (JavaScript)

Issue:
Compliance for basic auth credentials
since `http-server` use `secure-compare` to validate basic authentication credentials and according to the predication here https://github.com/vadimdemedes/secure-compare/blob/master/index.js#L13, secure-compare will only accept string type credential arguments, this will lead to unexpected behavior with `http-server`:

if I start a server with basic authentication, let's say

```
node bin/http-server --username 1 --password 2
```

then after I visit `127.0.0.1:8080` and even I enter the correct credentials:

![image](https://user-images.githubusercontent.com/6234553/70026333-7b113300-15da-11ea-8d67-23ce4fad62a0.png)

the server will respond to me with 401 since it will automatically parse `--username` and `--password` arguments to `Number` type if it can.

hence I think we need to format `options.username` and `options.password` options into `String` type before we use `secure-compare` to valid them :)


Discussion:

Patch:
diff --git a/lib/http-server.js b/lib/http-server.js
index ab340d813..dcd3c1030 100644
--- a/lib/http-server.js
+++ b/lib/http-server.js
@@ -108,8 +108,11 @@ function HttpServer(options) {
       // an attacker knowledge of whether the username is correct via a timing
       // attack.
       if (credentials) {
-        var usernameEqual = secureCompare(options.username, credentials.name);
-        var passwordEqual = secureCompare(options.password, credentials.pass);
+        // since the `name` and `pass` attributes of `credentials` are always string type
+        // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/HEAD/types/basic-auth/index.d.ts#L15-L16
+        // so we use `.toString()` to fix https://github.com/http-party/http-server/issues/583
+        var usernameEqual = secureCompare(options.username.toString(), credentials.name);
+        var passwordEqual = secureCompare(options.password.toString(), credentials.pass);
         if (usernameEqual && passwordEqual) {
           return res.emit('next');
         }


Test patch:
diff --git a/test/http-server-test.js b/test/http-server-test.js
index 470267619..6d5fce462 100644
--- a/test/http-server-test.js
+++ b/test/http-server-test.js
@@ -379,5 +379,140 @@ vows.describe('http-server').addBatch({
     teardown: function (server) {
       server.close();
     }
+  },
+  'When http-server is listening on 8086 with username "good_username" and Number type password 123456': {
+    topic: function () {
+      var server = httpServer.createServer({
+        root: root,
+        robots: true,
+        headers: {
+          'Access-Control-Allow-Origin': '*',
+          'Access-Control-Allow-Credentials': 'true'
+        },
+        username: 'good_username',
+        password: 123456
+      });
+
+      server.listen(8086);
+      this.callback(null, server);
+    },
+    'and the user requests an existent file with no auth details': {
+      topic: function () {
+        request('http://127.0.0.1:8086/file', this.callback);
+      },
+      'status code should be 401': function (res) {
+        assert.equal(res.statusCode, 401);
+      },
+      'and file content': {
+        topic: function (res, body) {
+          var self = this;
+          fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) {
+            self.callback(err, data, body);
+          });
+        },
+        'should be a forbidden message': function (err, file, body) {
+          assert.equal(body, 'Access denied');
+        }
+      }
+    },
+    'and the user requests an existent file with incorrect username': {
+      topic: function () {
+        request('http://127.0.0.1:8086/file', {
+          auth: {
+            user: 'wrong_username',
+            pass: '123456'
+          }
+        }, this.callback);
+      },
+      'status code should be 401': function (res) {
+        assert.equal(res.statusCode, 401);
+      },
+      'and file content': {
+        topic: function (res, body) {
+          var self = this;
+          fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) {
+            self.callback(err, data, body);
+          });
+        },
+        'should be a forbidden message': function (err, file, body) {
+          assert.equal(body, 'Access denied');
+        }
+      }
+    },
+    'and the user requests an existent file with incorrect password': {
+      topic: function () {
+        request('http://127.0.0.1:8086/file', {
+          auth: {
+            user: 'good_username',
+            pass: '654321'
+          }
+        }, this.callback);
+      },
+      'status code should be 401': function (res) {
+        assert.equal(res.statusCode, 401);
+      },
+      'and file content': {
+        topic: function (res, body) {
+          var self = this;
+          fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) {
+            self.callback(err, data, body);
+          });
+        },
+        'should be a forbidden message': function (err, file, body) {
+          assert.equal(body, 'Access denied');
+        }
+      }
+    },
+    'and the user requests a non-existent file with incorrect password': {
+      topic: function () {
+        request('http://127.0.0.1:8086/404', {
+          auth: {
+            user: 'good_username',
+            pass: '654321'
+          }
+        }, this.callback);
+      },
+      'status code should be 401': function (res) {
+        assert.equal(res.statusCode, 401);
+      },
+      'and file content': {
+        topic: function (res, body) {
+          var self = this;
+          fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) {
+            self.callback(err, data, body);
+          });
+        },
+        'should be a forbidden message': function (err, file, body) {
+          assert.equal(body, 'Access denied');
+        }
+      }
+    },
+    'and the user requests an existent file with correct auth details': {
+      topic: function () {
+        request('http://127.0.0.1:8086/file', {
+          auth: {
+            user: 'good_username',
+            pass: '123456'
+          }
+        }, this.callback);
+      },
+      'status code should be 200': function (res) {
+        assert.equal(res.statusCode, 200);
+      },
+      'and file content': {
+        topic: function (res, body) {
+          var self = this;
+          fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) {
+            self.callback(err, data, body);
+          });
+        },
+        'should match content of served file': function (err, file, body) {
+          assert.equal(body.trim(), file.trim());
+        }
+      }
+    },
+    teardown: function (server) {
+      server.close();
+    }
   }
 }).export(module);