X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=test%2Flib%2Fresource-authenticator.js;fp=test%2Flib%2Fresource-authenticator.js;h=826c484250628a159da1a15ca00595e204a987ed;hb=69a2f5e7d73dd3f58e07b652c306daa8b253245d;hp=0000000000000000000000000000000000000000;hpb=49cf45515817fbd4479c654d89c8a56c292298bb;p=squeep-resource-authentication-module diff --git a/test/lib/resource-authenticator.js b/test/lib/resource-authenticator.js new file mode 100644 index 0000000..826c484 --- /dev/null +++ b/test/lib/resource-authenticator.js @@ -0,0 +1,228 @@ +/* eslint-env mocha */ +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require +const { ResourceAuthenticator, ResourceAuthenticatorError } = require('../../lib/resource-authenticator'); +const Enum = require('../../lib/enum'); +const { ResponseError } = require('../../lib/errors'); +const StubLogger = require('../stub-logger'); +const StubDb = require('../stub-db'); + +const noExpectedException = 'did not receive expected exception'; + +describe('Resource Authenticator', function () { + let stubLogger, stubDb; + let options, ra; + let req, res, ctx; + beforeEach(function () { + stubLogger = new StubLogger(); + stubLogger._reset(); + stubDb = new StubDb(); + stubDb._reset(); + options = {}; + ra = new ResourceAuthenticator(stubLogger, stubDb, options); + req = { + getHeader: sinon.stub(), + }; + res = { + setHeader: sinon.stub(), + }; + ctx = { + params: {}, + parsedBody: {}, + queryParams: {}, + session: {}, + }; + }); + afterEach(function () { + sinon.restore(); + }); + + it('covers no options', function () { + ra = new ResourceAuthenticator(stubLogger, stubDb); + }); + + describe('required', function () { + let resource; + const validBearerHeader = 'Bearer bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:fdUYC8Gqe0nAyX_-SWvRsPsx0UjY-vV-Ff0A52j6Zfw'; + beforeEach(function () { + resource = { + secret: 'secrety', + }; + }); + it('requires auth header', async function () { + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('requires bearer token', async function () { + req.getHeader.returns('Basic Zm9vcABiYXJr'); + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('requires proper bearer token', async function () { + req.getHeader.returns('Bearer Zm9vcABiYXJr'); + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('requires identifier to exist', async function () { + req.getHeader.returns(validBearerHeader); + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('covers db failure', async function () { + const expected = new Error('oh no'); + ra.db.resourceGet.rejects(expected); + req.getHeader.returns(validBearerHeader); + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert.deepStrictEqual(e, expected, noExpectedException); + } + }); + it('requires timestamp within grace', async function () { + sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648838184); + ra.db.resourceGet.resolves(resource); + req.getHeader.returns(validBearerHeader); + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('requires digest to match', async function () { + sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648836031); + ra.db.resourceGet.resolves(resource); + req.getHeader.returns('Bearer bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:invalid1M2j9wtoerc3Pqe6kRzqFrkrkwqdeYXG331Q'); + try { + await ra.required(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('succeeds', async function () { + sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648836031); + ra.db.resourceGet.resolves(resource); + req.getHeader.returns(validBearerHeader); + await ra.required(req, res, ctx); + assert.strictEqual(ra.logger.debug.args[1][1], 'success'); + }); + }); // required + + describe('currentEpoch', function () { + it('covers', function () { + const now = 1648836413503; + const expected = Math.ceil(now / 1000); + sinon.stub(Date, 'now').returns(now); + const result = ResourceAuthenticator.currentEpoch; + assert.strictEqual(result, expected); + }); + }); // currentEpoch + + describe('getSalt', function () { + it('covers', async function () { + const result = await ra.getSalt(); + assert(result); + assert.strictEqual(result.length, 28); + }); + }); // getSalt + + describe('authenticate', function () { + it('covers', async function () { + const identifier = '6eaed948-b1e4-11ec-9a91-0025905f714a'; + const secret = 'secrety'; + sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648836029); + sinon.stub(ra, 'getSalt').resolves('xxxxx'); + const expected = 'bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:fdUYC8Gqe0nAyX_-SWvRsPsx0UjY-vV-Ff0A52j6Zfw'; + const result = await ra.authenticate(identifier, secret); + assert.strictEqual(result, expected); + }); + }); // authenticate + + describe('Identifier Compaction', function () { + it('reciprocates', function () { + const identifier = '6eaed948-b1e4-11ec-9a91-0025905f714a'; + const smaller = ResourceAuthenticator.ensmallenIdentifier(identifier); + const bigger = ResourceAuthenticator.embiggenIdentifier(smaller); + assert.strictEqual(bigger, identifier); + }); + }); // Identifier Compaction + + describe('createDigest', function () { + let secret; + beforeEach(function () { + secret = 'secret'; + }); + it('creates empty digest', function () { + const result = ra.createDigest(secret); + const expected = '-eZuF5tnR65UEI-C-K3os8Jddv0wr95sOVgixTAZYWk'; + assert.strictEqual(result, expected); + }); + it('creates digest', function () { + const result = ra.createDigest(secret, 'data'); + const expected = 'GywWt1vSqHDBFBU8zaW8_KYzFLxyL6Fg1pDeEzzLuds'; + assert.strictEqual(result, expected); + }); + }); // createDigest + + describe('requestBearer', function () { + it('covers default response', function () { + try { + ResourceAuthenticator.requestBearer(res); + assert.fail(noExpectedException); + } catch (e) { + assert(res.setHeader.called); + assert.strictEqual(res.setHeader.args[0][0], 'WWW-Authenticate'); + assert.strictEqual(res.setHeader.args[0][1], 'Bearer'); + assert(e instanceof ResponseError, noExpectedException); + assert.strictEqual(e.statusCode, 401); + } + }); + it('covers other response', function () { + try { + ResourceAuthenticator.requestBearer(res, Enum.ErrorResponse.Forbidden); + assert.fail(noExpectedException); + } catch (e) { + assert(res.setHeader.called); + assert.strictEqual(res.setHeader.args[0][0], 'WWW-Authenticate'); + assert.strictEqual(res.setHeader.args[0][1], 'Bearer'); + assert(e instanceof ResponseError); + assert.strictEqual(e.statusCode, 403); + } + }); + }); // requestBearer + + describe('ResourceAuthenticatorError', function () { + it('covers', function () { + const e = new ResourceAuthenticatorError(); + const result = e.name; + assert.strictEqual(result, 'ResourceAuthenticatorError'); + }); + }); + +}); // ResourceAuthenticator \ No newline at end of file