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');
12 describe('Resource Authenticator', function () {
13 const noExpectedException
= 'did not receive expected exception';
16 beforeEach(function () {
19 options
= new Config('test');
20 ra
= new ResourceAuthenticator(stubLogger
, stubDb
, options
);
23 afterEach(function () {
27 it('covers no option default', function () {
28 ra
= new ResourceAuthenticator(stubLogger
, stubDb
);
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
);
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
);
48 }); // Identifier Compaction
50 describe('getSalt', function () {
51 it('covers', async
function () {
52 const result
= await ra
.getSalt();
54 assert
.strictEqual(result
.length
, 28);
58 describe('createDigest', function () {
60 beforeEach(function () {
63 it('creates empty digest', function () {
64 const result
= ra
.createDigest(secret
);
65 const expected
= '-eZuF5tnR65UEI-C-K3os8Jddv0wr95sOVgixTAZYWk';
66 assert
.strictEqual(result
, expected
);
68 it('creates digest', function () {
69 const result
= ra
.createDigest(secret
, 'data');
70 const expected
= 'GywWt1vSqHDBFBU8zaW8_KYzFLxyL6Fg1pDeEzzLuds';
71 assert
.strictEqual(result
, expected
);
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
);
87 describe('required', function () {
88 let resource
, res
, req
, ctx
;
89 const validBearerHeader
= 'Bearer bq7ZSLHkEeyakQAlkF9xSg:1648836029:xxxxx:fdUYC8Gqe0nAyX_-SWvRsPsx0UjY-vV-Ff0A52j6Zfw';
90 beforeEach(function () {
95 getHeader: sinon
.stub(),
98 setHeader: sinon
.stub(),
107 it('requires auth header', async
function () {
109 await ra
.required(req
, res
, ctx
);
110 assert
.fail(noExpectedException
);
112 assert(e
instanceof ResponseError
, noExpectedException
);
113 assert
.strictEqual(e
.statusCode
, 401);
116 it('requires bearer token', async
function () {
117 req
.getHeader
.returns('Basic Zm9vcABiYXJr');
119 await ra
.required(req
, res
, ctx
);
120 assert
.fail(noExpectedException
);
122 assert(e
instanceof ResponseError
, noExpectedException
);
123 assert
.strictEqual(e
.statusCode
, 401);
126 it('requires proper bearer token', async
function () {
127 req
.getHeader
.returns('Bearer Zm9vcABiYXJr');
129 await ra
.required(req
, res
, ctx
);
130 assert
.fail(noExpectedException
);
132 assert(e
instanceof ResponseError
, noExpectedException
);
133 assert
.strictEqual(e
.statusCode
, 401);
136 it('requires identifier to exist', async
function () {
137 req
.getHeader
.returns(validBearerHeader
);
139 await ra
.required(req
, res
, ctx
);
140 assert
.fail(noExpectedException
);
142 assert(e
instanceof ResponseError
, noExpectedException
);
143 assert
.strictEqual(e
.statusCode
, 401);
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
);
151 await ra
.required(req
, res
, ctx
);
152 assert
.fail(noExpectedException
);
154 assert
.deepStrictEqual(e
, expected
, noExpectedException
);
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
);
162 await ra
.required(req
, res
, ctx
);
163 assert
.fail(noExpectedException
);
165 assert(e
instanceof ResponseError
, noExpectedException
);
166 assert
.strictEqual(e
.statusCode
, 401);
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');
174 await ra
.required(req
, res
, ctx
);
175 assert
.fail(noExpectedException
);
177 assert(e
instanceof ResponseError
, noExpectedException
);
178 assert
.strictEqual(e
.statusCode
, 401);
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');
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');
198 describe('requestBearer', function () {
200 beforeEach(function () {
202 setHeader: sinon
.stub(),
205 it('covers default response', function () {
207 ResourceAuthenticator
.requestBearer(res
);
208 assert
.fail(noExpectedException
);
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);
217 it('covers other response', function () {
219 ResourceAuthenticator
.requestBearer(res
, Enum
.ErrorResponse
.Forbidden
);
220 assert
.fail(noExpectedException
);
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);
231 describe('ResourceAuthenticatorError', function () {
232 it('covers', function () {
233 const e
= new ResourceAuthenticatorError();
234 const result
= e
.name
;
235 assert
.strictEqual(result
, 'ResourceAuthenticatorError');
237 }); // ResourceAuthenticationError
239 }); // Resource Authenticator