2 /* eslint-disable capitalized-comments */
5 const assert
= require('assert');
6 const sinon
= require('sinon');
8 const Authenticator
= require('../../src/authenticator');
9 const { Errors: { ResponseError
} } = require('@squeep/api-dingus');
12 const noExpectedException
= 'did not get expected exception';
14 describe('Authenticator', function () {
15 let authenticator
, logger
, db
, options
;
17 beforeEach(function () {
18 logger
= { debug: () => {} };
21 context: async (fn
) => fn({}),
22 getAuthById: async () => {},
23 getLinkById: async () => {},
25 authenticator
= new Authenticator(logger
, db
, options
);
28 afterEach(function () {
32 describe('b64 armor', function () {
33 it('reciprocates', function () {
34 const src
= '3qmOlsGY5I3qY4O/tqF7LuvS400BBPh/AMzbyoIlvyXPwMA1tg==';
36 const armored
= Authenticator
.b64Armor(src
);
37 const unarmored
= Authenticator
.b64Unarmor(armored
);
39 assert
.strictEqual(unarmored
, src
);
43 describe('generateToken', function () {
44 it('generates a token', async
function () {
45 const result
= await Authenticator
.generateToken();
46 assert
.strictEqual(result
.length
, 36 * 4 / 3);
50 describe('signature', function () {
51 it('generates a signature', async
function () {
52 const secret
= 'secret';
53 const id
= 'identifier';
54 const epoch
= '1602887798';
55 const body
= 'some data';
56 const expected
= 'P5IXYXu5a7aobQvRinPQlN_k1g8GHycRpx3JrK1O7YJlqmhv3WRP5M3ubObPdUWM';
57 const result
= await Authenticator
.signature(secret
, id
, epoch
, body
);
58 assert
.strictEqual(result
, expected
);
62 describe('requestSignature', function () {
65 beforeEach(function () {
67 setHeader: sinon
.stub(),
71 it('requests custom auth', function () {
73 authenticator
.requestSignature(res
);
74 assert
.fail(noExpectedException
);
76 assert(e
instanceof ResponseError
);
77 assert
.strictEqual(e
.statusCode
, 401);
78 assert(res
.setHeader
.called
);
81 }); // requestSignature
83 describe('isValidSignature', function () {
86 beforeEach(function () {
89 rawBody: 'all about werewolves',
91 sinon
.stub(authenticator
.db
, 'getAuthById').resolves({ secret: 'bluemoon' });
94 it('passes valid signature', async
function () {
95 reqSig
= 'awoo:1604155860:Zz43TbCdVeqRAqhxTnVpTcC2aMdh3PLXNw_FuQ4KVhjsEFTEnpokwTHb-Qbt3ttz';
96 sinon
.stub(Date
, 'now').returns(1604155861 * 1000);
97 const result
= await authenticator
.isValidSignature(reqSig
, ctx
);
98 assert
.strictEqual(result
, true);
99 assert
.strictEqual(ctx
.authenticationId
, 'awoo');
102 it('fails invalid data', async
function () {
103 reqSig
= 'not a signature';
104 sinon
.stub(Date
, 'now').returns(1604155861 * 1000);
105 const result
= await authenticator
.isValidSignature(reqSig
, ctx
);
106 assert
.strictEqual(result
, false);
109 it('fails missing data', async
function () {
110 sinon
.stub(Date
, 'now').returns(1604155861 * 1000);
111 const result
= await authenticator
.isValidSignature(reqSig
, ctx
);
112 assert
.strictEqual(result
, false);
115 it('fails invalid signature', async
function () {
116 reqSig
= 'awoo:1604155860:bad signature';
117 sinon
.stub(Date
, 'now').returns(1604155861 * 1000);
118 const result
= await authenticator
.isValidSignature(reqSig
, ctx
);
119 assert
.strictEqual(result
, false);
122 it('fails invalid timestamp', async
function () {
123 reqSig
= 'awoo:0:Zz43TbCdVeqRAqhxTnVpTcC2aMdh3PLXNw_FuQ4KVhjsEFTEnpokwTHb-Qbt3ttz';
124 sinon
.stub(Date
, 'now').returns(1604155861 * 1000);
125 const result
= await authenticator
.isValidSignature(reqSig
, ctx
);
126 assert
.strictEqual(result
, false);
129 it('fails invalid auth id', async
function () {
130 reqSig
= 'awoo:1604155860:Zz43TbCdVeqRAqhxTnVpTcC2aMdh3PLXNw_FuQ4KVhjsEFTEnpokwTHb-Qbt3ttz';
131 authenticator
.db
.getAuthById
.restore();
132 sinon
.stub(authenticator
.db
, 'getAuthById').resolves({});
133 sinon
.stub(Date
, 'now').returns(1604155861 * 1000);
134 const result
= await authenticator
.isValidSignature(reqSig
, ctx
);
135 assert
.strictEqual(result
, false);
137 }); // isValidSignature
139 describe('isValidToken', function () {
142 beforeEach(function () {
144 linkId
= 'identifier';
145 sinon
.stub(authenticator
.db
, 'getLinkById');
148 it('accepts token', async
function () {
149 token
= 'this_is_a_token';
150 authenticator
.db
.getLinkById
.resolves({ authToken: token
});
151 const result
= await authenticator
.isValidToken(linkId
, token
);
152 assert
.strictEqual(result
, true);
155 it('rejects missing token', async
function () {
156 const result
= await authenticator
.isValidToken(linkId
, token
);
157 assert
.strictEqual(result
, false);
160 it('rejects wrong token', async
function () {
161 token
= 'this_is_a_token';
162 authenticator
.db
.getLinkById
.resolves({ authToken: 'some_other_token' });
163 const result
= await authenticator
.isValidToken(linkId
, token
);
164 assert
.strictEqual(result
, false);
167 it('rejects missing id', async
function () {
168 token
= 'this_is_a_token';
170 authenticator
.db
.getLinkById
.resolves();
171 const result
= await authenticator
.isValidToken(linkId
, token
);
172 assert
.strictEqual(result
, false);
175 it('rejects invalid id', async
function () {
176 token
= 'this_is_a_token';
177 const result
= await authenticator
.isValidToken(linkId
, token
);
178 assert
.strictEqual(result
, false);
182 describe('isValidBasic', function () {
183 let credentials
, ctx
;
185 beforeEach(function () {
186 sinon
.stub(authenticator
, 'requestBasic');
187 credentials
= 'id:password';
191 it('accepts credentials', async
function () {
192 sinon
.stub(authenticator
.db
, 'getAuthById').resolves({ password: 'password' });
193 const result
= await authenticator
.isValidBasic(credentials
, ctx
);
194 assert
.strictEqual(result
, true);
195 assert
.strictEqual(ctx
.authenticationId
, 'id');
198 it('rejects wrong password', async
function () {
199 sinon
.stub(authenticator
.db
, 'getAuthById').resolves({ password: 'wrong_password' });
200 const result
= await authenticator
.isValidBasic(credentials
, ctx
);
201 assert
.strictEqual(result
, false);
202 assert(!('authenticationId' in ctx
));
205 it('rejects missing id', async
function () {
206 sinon
.stub(authenticator
.db
, 'getAuthById').resolves();
207 const result
= await authenticator
.isValidBasic(credentials
, ctx
);
208 assert
.strictEqual(result
, false);
209 assert(!('authenticationId' in ctx
));
213 describe('isValidBearer', function () {
214 let credentials
, ctx
;
216 beforeEach(function () {
217 credentials
= 'id:password';
223 it('accepts token', async
function () {
224 ctx
.params
.id
= 'identifier';
225 credentials
= 'token';
226 sinon
.stub(authenticator
.db
, 'getLinkById').resolves({ authToken: 'token' });
227 const result
= await authenticator
.isValidBearer(credentials
, ctx
);
228 assert
.strictEqual(result
, true);
231 it('rejects wrong token', async
function () {
232 ctx
.params
.id
= 'identifier';
233 sinon
.stub(authenticator
.db
, 'getLinkById').resolves({ authToken: 'wrong_token' });
234 const result
= await authenticator
.isValidBearer(credentials
, ctx
);
235 assert
.strictEqual(result
, false);
238 it('rejects missing id', async
function () {
239 sinon
.stub(authenticator
.db
, 'getLinkById').resolves();
240 const result
= await authenticator
.isValidBearer(credentials
, ctx
);
241 assert
.strictEqual(result
, false);
245 describe('isValidAuthorization', function () {
248 beforeEach(function () {
250 sinon
.stub(authenticator
, 'isValidBasic');
251 sinon
.stub(authenticator
, 'isValidBearer');
252 sinon
.stub(authenticator
, 'requestBasic');
255 it('dispatches basic', async
function () {
256 header
= 'Basic blahblahblah=';
257 await authenticator
.isValidAuthorization(header
, ctx
);
258 assert(authenticator
.isValidBasic
.called
);
261 it('dispatches bearer', async
function () {
262 header
= 'Bearer blahblahblah=';
263 await authenticator
.isValidAuthorization(header
, ctx
);
264 assert(authenticator
.isValidBearer
.called
);
267 it('handles fallback', async
function () {
268 header
= 'Digest blahblahblah=';
269 const result
= await authenticator
.isValidAuthorization(header
, ctx
);
270 assert
.strictEqual(result
, false);
272 }); // isValidAuthorization
274 describe('requestBasic', function () {
277 beforeEach(function () {
279 setHeader: sinon
.stub(),
283 it('requests custom auth', function () {
285 authenticator
.requestBasic(res
);
286 assert
.fail(noExpectedException
);
288 assert(e
instanceof ResponseError
);
289 assert
.strictEqual(e
.statusCode
, 401);
290 assert(res
.setHeader
.called
);
295 describe('required', function () {
298 beforeEach(function () {
300 getHeader: sinon
.stub(),
304 sinon
.stub(authenticator
, 'isValidToken').resolves(false);
305 sinon
.stub(authenticator
, 'isValidSignature').resolves(false);
306 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(false);
307 sinon
.stub(authenticator
, 'requestBasic');
308 sinon
.stub(authenticator
, 'requestSignature');
311 it('validates signature auth', async
function () {
312 req
.getHeader
.returns('signature');
313 authenticator
.isValidSignature
.restore();
314 sinon
.stub(authenticator
, 'isValidSignature').resolves(true);
315 const result
= await authenticator
.required(req
, res
, ctx
);
316 assert
.strictEqual(result
, true);
317 assert(!authenticator
.requestBasic
.called
);
320 it('requests signature auth on signature failure', async
function () {
321 req
.getHeader
.returns('signature');
322 await authenticator
.required(req
, res
, ctx
);
323 assert(authenticator
.requestSignature
.called
);
326 it('validates authorization auth', async
function () {
327 req
.getHeader
.onCall(1).returns('signature');
328 authenticator
.isValidAuthorization
.restore();
329 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(true);
330 const result
= await authenticator
.required(req
, res
, ctx
);
331 assert
.strictEqual(result
, true);
332 assert(!authenticator
.requestBasic
.called
);
335 it('requests authorization on auth failure', async
function () {
336 req
.getHeader
.onCall(1).returns('signature');
337 await authenticator
.required(req
, res
, ctx
);
338 assert(authenticator
.requestBasic
.called
);
341 it('validates a queryparams token', async
function () {
342 ctx
.params
= { id: 'identifier' };
343 ctx
.queryParams
= { token: 'token' };
344 authenticator
.isValidToken
.restore();
345 sinon
.stub(authenticator
, 'isValidToken').resolves(true);
346 const result
= await authenticator
.required(req
, res
, ctx
);
347 assert
.strictEqual(result
, true);
348 assert(!authenticator
.requestBasic
.called
);
351 it('validates a body token', async
function () {
352 ctx
.params
= { id: 'identifier' };
353 ctx
.parsedBody
= { token: 'token' };
354 authenticator
.isValidToken
.restore();
355 sinon
.stub(authenticator
, 'isValidToken').resolves(true);
356 const result
= await authenticator
.required(req
, res
, ctx
);
357 assert
.strictEqual(result
, true);
358 assert(!authenticator
.requestBasic
.called
);
361 it('fails invalid token', async
function () {
362 ctx
.params
= { id: 'identifier' };
363 ctx
.parsedBody
= { token: 'token' };
364 await authenticator
.required(req
, res
, ctx
);
365 assert(authenticator
.requestBasic
.called
);
368 it('fails missing token', async
function () {
369 ctx
.params
= { id: 'identifier' };
370 await authenticator
.required(req
, res
, ctx
);
371 assert(authenticator
.requestBasic
.called
);
374 it('requests basic when all else fails', async
function () {
375 await authenticator
.required(req
, res
, ctx
);
376 assert(authenticator
.requestBasic
.called
);
381 describe('optional', function () {
384 beforeEach(function () {
386 getHeader: sinon
.stub(),
390 sinon
.stub(authenticator
, 'isValidToken').resolves(false);
391 sinon
.stub(authenticator
, 'isValidSignature').resolves(false);
392 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(false);
395 it('rejects with no auth', async
function () {
396 const result
= await authenticator
.optional(req
, res
, ctx
);
397 assert
.strictEqual(result
, false);
400 it('validates signature auth', async
function () {
401 req
.getHeader
.onCall(0).returns('signature');
402 authenticator
.isValidSignature
.restore();
403 sinon
.stub(authenticator
, 'isValidSignature').resolves(true);
404 const result
= await authenticator
.optional(req
, res
, ctx
);
405 assert
.strictEqual(result
, true);
408 it('rejects invalid signature auth', async
function () {
409 req
.getHeader
.onCall(0).returns('signature');
410 const result
= await authenticator
.optional(req
, res
, ctx
);
411 assert
.strictEqual(result
, false);
414 it('validates auth', async
function () {
415 req
.getHeader
.onCall(1).returns('basic auth');
416 authenticator
.isValidAuthorization
.restore();
417 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(true);
418 const result
= await authenticator
.optional(req
, res
, ctx
);
419 assert
.strictEqual(result
, true);
422 it('rejects invalid auth', async
function () {
423 req
.getHeader
.onCall(1).returns('basic auth');
424 const result
= await authenticator
.optional(req
, res
, ctx
);
425 assert
.strictEqual(result
, false);
428 it('validates queryparam token', async
function () {
429 ctx
.queryParams
= { token: 'token' };
430 ctx
.params
= { id: 'identifier' };
431 authenticator
.isValidToken
.restore();
432 sinon
.stub(authenticator
, 'isValidToken').resolves(true);
433 const result
= await authenticator
.optional(req
, res
, ctx
);
434 assert
.strictEqual(result
, true);
437 it('validates body token', async
function () {
438 ctx
.parsedBody
= { token: 'token' };
439 ctx
.params
= { id: 'identifier' };
440 authenticator
.isValidToken
.restore();
441 sinon
.stub(authenticator
, 'isValidToken').resolves(true);
442 const result
= await authenticator
.optional(req
, res
, ctx
);
443 assert
.strictEqual(result
, true);
446 it('rejects invalid token', async
function () {
447 ctx
.queryParams
= { token: 'token' };
448 ctx
.params
= { id: 'identifier' };
449 const result
= await authenticator
.optional(req
, res
, ctx
);
450 assert
.strictEqual(result
, false);
453 it('rejects missing token', async
function () {
454 ctx
.params
= { id: 'identifier' };
455 const result
= await authenticator
.optional(req
, res
, ctx
);
456 assert
.strictEqual(result
, false);
461 describe('signResponse', function () {
463 beforeEach(function () {
466 setHeader: sinon
.stub(),
470 it('does nothing without auth', function () {
471 authenticator
.signResponse(req
, res
, ctx
);
472 assert(!res
.setHeader
.called
);
474 it('signs a response', function () {
475 ctx
.authenticationId
= 'identified';
476 ctx
.authenticationSecret
= 'secret';
477 ctx
.responseBody
= 'awoo';
478 authenticator
.signResponse(req
, res
, ctx
);
479 assert(res
.setHeader
.called
);
481 it('signs an empty response', function () {
482 ctx
.authenticationId
= 'identified';
483 ctx
.authenticationSecret
= 'secret';
484 authenticator
.signResponse(req
, res
, ctx
);
485 assert(res
.setHeader
.called
);
487 it('covers big response non-logging', function () {
488 ctx
.responseBody
= 'orgle'.repeat(1000);
489 authenticator
.signResponse(req
, res
, ctx
);
491 it('covers buffer response', function () {
492 ctx
.responseBody
= Buffer
.from('orgle'.repeat(1000));
493 authenticator
.signResponse(req
, res
, ctx
);