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