diff --git a/package.json b/package.json index 231fffe..8974fab 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "pre-commit": "lint-staged" }, "dependencies": { + "basic-auth": "2.0.1", "bytes": "3.0.0", "content-disposition": "0.5.2", "fast-url-parser": "1.1.3", diff --git a/src/index.js b/src/index.js index 85bbe76..941fdcb 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ const bytes = require('bytes'); const contentDisposition = require('content-disposition'); const isPathInside = require('path-is-inside'); const parseRange = require('range-parser'); +const auth = require('basic-auth'); // Other const directoryTemplate = require('./directory'); @@ -593,6 +594,23 @@ module.exports = async (request, response, config = {}, methods = {}) => { return; } + // Basic Authentication, if specified in the config + if (config.auth) { + if (!config.auth.length || config.auth.length !== 2) { + const err = new Error('You are running basic auth but did not properly configure "auth" in your static config.'); + return internalError(absolutePath, response, acceptsJSON, current, handlers, config, err); + } + const credentials = auth(request); + if (!credentials || credentials.name !== config.auth[0] || credentials.pass !== config.auth[1]) { + response.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"'); + return handlers.sendError(absolutePath, response, acceptsJSON, current, handlers, config, { + statusCode: 401, + code: 'access_denied', + message: 'Access Denied' + }); + } + } + let stats = null; // It's extremely important that we're doing multiple stat calls. This one diff --git a/test/integration.js b/test/integration.js index 61b812b..5ae4e11 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1350,3 +1350,71 @@ test('etag header is set', async t => { '"ba114dbc69e41e180362234807f093c3c4628f90"' ); }); + +test('auth is set improperly (improper number parameters)', async t => { + const url = await getUrl({ + auth: ['testuser'] + }); + const response = await fetch(`${url}/`); + t.is(response.status, 500); +}); +test('auth is set improperly (no parameters)', async t => { + const url = await getUrl({ + auth: [] + }); + const response = await fetch(`${url}/`); + t.is(response.status, 500); +}); + +test('auth is set properly, no credentials', async t => { + const url = await getUrl({ + auth: ['testuser', 'testpassword'] + }); + const response = await fetch(`${url}/`); + t.is(response.status, 401); +}); + +test('auth is set properly, with correct credentials', async t => { + const username = 'testuser'; + const password = 'testpassword'; + const url = await getUrl({ + auth: [username, password] + }); + const passwordBuffer = new Buffer(`${username}:${password}`); + const response = await fetch(`${url}/`, { + headers: { + Authorization: `Basic ${passwordBuffer.toString('base64')}` + } + }); + t.is(response.status, 200); +}); + +test('auth is set properly, with credentials, wrong name', async t => { + const username = 'testuser'; + const password = 'testpassword'; + const url = await getUrl({ + auth: [username, password] + }); + const passwordBuffer = new Buffer(`${username}_wrong:${password}`); + const response = await fetch(`${url}/`, { + headers: { + Authorization: `Basic ${passwordBuffer.toString('base64')}` + } + }); + t.is(response.status, 401); +}); + +test('auth is set properly, with credentials, wrong password', async t => { + const username = 'testuser'; + const password = 'testpassword'; + const url = await getUrl({ + auth: [username, password] + }); + const passwordBuffer = new Buffer(`${username}:${password}_wrong`); + const response = await fetch(`${url}/`, { + headers: { + Authorization: `Basic ${passwordBuffer.toString('base64')}` + } + }); + t.is(response.status, 401); +}); diff --git a/yarn.lock b/yarn.lock index 09873c3..a981ae2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -624,6 +624,25 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +basic-auth@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -3165,7 +3184,7 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"