initial commit
[squeep-resource-authentication-module] / test / lib / resource-authenticator.js
1 /* eslint-env mocha */
2 'use strict';
3
4 const assert = require('assert');
5 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
6 const { ResourceAuthenticator, ResourceAuthenticatorError } = require('../../lib/resource-authenticator');
7 const Enum = require('../../lib/enum');
8 const { ResponseError } = require('../../lib/errors');
9 const StubLogger = require('../stub-logger');
10 const StubDb = require('../stub-db');
11
12 const noExpectedException = 'did not receive expected exception';
13
14 describe('Resource Authenticator', function () {
15 let stubLogger, stubDb;
16 let options, ra;
17 let req, res, ctx;
18 beforeEach(function () {
19 stubLogger = new StubLogger();
20 stubLogger._reset();
21 stubDb = new StubDb();
22 stubDb._reset();
23 options = {};
24 ra = new ResourceAuthenticator(stubLogger, stubDb, options);
25 req = {
26 getHeader: sinon.stub(),
27 };
28 res = {
29 setHeader: sinon.stub(),
30 };
31 ctx = {
32 params: {},
33 parsedBody: {},
34 queryParams: {},
35 session: {},
36 };
37 });
38 afterEach(function () {
39 sinon.restore();
40 });
41
42 it('covers no options', function () {
43 ra = new ResourceAuthenticator(stubLogger, stubDb);
44 });
45
46 describe('required', function () {
47 let resource;
48 const validBearerHeader = 'Bearer bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:fdUYC8Gqe0nAyX_-SWvRsPsx0UjY-vV-Ff0A52j6Zfw';
49 beforeEach(function () {
50 resource = {
51 secret: 'secrety',
52 };
53 });
54 it('requires auth header', async function () {
55 try {
56 await ra.required(req, res, ctx);
57 assert.fail(noExpectedException);
58 } catch (e) {
59 assert(e instanceof ResponseError, noExpectedException);
60 assert.strictEqual(e.statusCode, 401);
61 }
62 });
63 it('requires bearer token', async function () {
64 req.getHeader.returns('Basic Zm9vcABiYXJr');
65 try {
66 await ra.required(req, res, ctx);
67 assert.fail(noExpectedException);
68 } catch (e) {
69 assert(e instanceof ResponseError, noExpectedException);
70 assert.strictEqual(e.statusCode, 401);
71 }
72 });
73 it('requires proper bearer token', async function () {
74 req.getHeader.returns('Bearer Zm9vcABiYXJr');
75 try {
76 await ra.required(req, res, ctx);
77 assert.fail(noExpectedException);
78 } catch (e) {
79 assert(e instanceof ResponseError, noExpectedException);
80 assert.strictEqual(e.statusCode, 401);
81 }
82 });
83 it('requires identifier to exist', async function () {
84 req.getHeader.returns(validBearerHeader);
85 try {
86 await ra.required(req, res, ctx);
87 assert.fail(noExpectedException);
88 } catch (e) {
89 assert(e instanceof ResponseError, noExpectedException);
90 assert.strictEqual(e.statusCode, 401);
91 }
92 });
93 it('covers db failure', async function () {
94 const expected = new Error('oh no');
95 ra.db.resourceGet.rejects(expected);
96 req.getHeader.returns(validBearerHeader);
97 try {
98 await ra.required(req, res, ctx);
99 assert.fail(noExpectedException);
100 } catch (e) {
101 assert.deepStrictEqual(e, expected, noExpectedException);
102 }
103 });
104 it('requires timestamp within grace', async function () {
105 sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648838184);
106 ra.db.resourceGet.resolves(resource);
107 req.getHeader.returns(validBearerHeader);
108 try {
109 await ra.required(req, res, ctx);
110 assert.fail(noExpectedException);
111 } catch (e) {
112 assert(e instanceof ResponseError, noExpectedException);
113 assert.strictEqual(e.statusCode, 401);
114 }
115 });
116 it('requires digest to match', async function () {
117 sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648836031);
118 ra.db.resourceGet.resolves(resource);
119 req.getHeader.returns('Bearer bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:invalid1M2j9wtoerc3Pqe6kRzqFrkrkwqdeYXG331Q');
120 try {
121 await ra.required(req, res, ctx);
122 assert.fail(noExpectedException);
123 } catch (e) {
124 assert(e instanceof ResponseError, noExpectedException);
125 assert.strictEqual(e.statusCode, 401);
126 }
127 });
128 it('succeeds', async function () {
129 sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648836031);
130 ra.db.resourceGet.resolves(resource);
131 req.getHeader.returns(validBearerHeader);
132 await ra.required(req, res, ctx);
133 assert.strictEqual(ra.logger.debug.args[1][1], 'success');
134 });
135 }); // required
136
137 describe('currentEpoch', function () {
138 it('covers', function () {
139 const now = 1648836413503;
140 const expected = Math.ceil(now / 1000);
141 sinon.stub(Date, 'now').returns(now);
142 const result = ResourceAuthenticator.currentEpoch;
143 assert.strictEqual(result, expected);
144 });
145 }); // currentEpoch
146
147 describe('getSalt', function () {
148 it('covers', async function () {
149 const result = await ra.getSalt();
150 assert(result);
151 assert.strictEqual(result.length, 28);
152 });
153 }); // getSalt
154
155 describe('authenticate', function () {
156 it('covers', async function () {
157 const identifier = '6eaed948-b1e4-11ec-9a91-0025905f714a';
158 const secret = 'secrety';
159 sinon.stub(ResourceAuthenticator, 'currentEpoch').get(() => 1648836029);
160 sinon.stub(ra, 'getSalt').resolves('xxxxx');
161 const expected = 'bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:fdUYC8Gqe0nAyX_-SWvRsPsx0UjY-vV-Ff0A52j6Zfw';
162 const result = await ra.authenticate(identifier, secret);
163 assert.strictEqual(result, expected);
164 });
165 }); // authenticate
166
167 describe('Identifier Compaction', function () {
168 it('reciprocates', function () {
169 const identifier = '6eaed948-b1e4-11ec-9a91-0025905f714a';
170 const smaller = ResourceAuthenticator.ensmallenIdentifier(identifier);
171 const bigger = ResourceAuthenticator.embiggenIdentifier(smaller);
172 assert.strictEqual(bigger, identifier);
173 });
174 }); // Identifier Compaction
175
176 describe('createDigest', function () {
177 let secret;
178 beforeEach(function () {
179 secret = 'secret';
180 });
181 it('creates empty digest', function () {
182 const result = ra.createDigest(secret);
183 const expected = '-eZuF5tnR65UEI-C-K3os8Jddv0wr95sOVgixTAZYWk';
184 assert.strictEqual(result, expected);
185 });
186 it('creates digest', function () {
187 const result = ra.createDigest(secret, 'data');
188 const expected = 'GywWt1vSqHDBFBU8zaW8_KYzFLxyL6Fg1pDeEzzLuds';
189 assert.strictEqual(result, expected);
190 });
191 }); // createDigest
192
193 describe('requestBearer', function () {
194 it('covers default response', function () {
195 try {
196 ResourceAuthenticator.requestBearer(res);
197 assert.fail(noExpectedException);
198 } catch (e) {
199 assert(res.setHeader.called);
200 assert.strictEqual(res.setHeader.args[0][0], 'WWW-Authenticate');
201 assert.strictEqual(res.setHeader.args[0][1], 'Bearer');
202 assert(e instanceof ResponseError, noExpectedException);
203 assert.strictEqual(e.statusCode, 401);
204 }
205 });
206 it('covers other response', function () {
207 try {
208 ResourceAuthenticator.requestBearer(res, Enum.ErrorResponse.Forbidden);
209 assert.fail(noExpectedException);
210 } catch (e) {
211 assert(res.setHeader.called);
212 assert.strictEqual(res.setHeader.args[0][0], 'WWW-Authenticate');
213 assert.strictEqual(res.setHeader.args[0][1], 'Bearer');
214 assert(e instanceof ResponseError);
215 assert.strictEqual(e.statusCode, 403);
216 }
217 });
218 }); // requestBearer
219
220 describe('ResourceAuthenticatorError', function () {
221 it('covers', function () {
222 const e = new ResourceAuthenticatorError();
223 const result = e.name;
224 assert.strictEqual(result, 'ResourceAuthenticatorError');
225 });
226 });
227
228 }); // ResourceAuthenticator