4 const assert
= require('assert');
5 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
6 const Authenticator
= require('../../src/authenticator');
7 const stubLogger
= require('../stub-logger');
8 const stubDb
= require('../stub-db');
9 const Errors
= require('../../src/errors');
10 const Enum
= require('../../src/enum');
11 const Config
= require('../../config');
13 const noExpectedException
= 'did not receive expected exception';
15 describe('Authenticator', function () {
16 let authenticator
, credential
, ctx
, identifier
, password
, options
;
17 function _authMechanismRequired(a
, m
) {
18 if (!a
.authn
[m
]) { // eslint-disable-line security/detect-object-injection
23 beforeEach(function () {
24 options
= Config('test');
25 authenticator
= new Authenticator(stubLogger
, stubDb
, options
);
26 identifier
= 'username';
27 credential
= '$argon2id$v=19$m=4096,t=3,p=1$1a6zRlX4BI4$sZGcQ72BTpDOlxUI/j3DmE1PMcu+Cs5liZ/D6kk79Ew';
29 password
= 'badPassword';
32 afterEach(function () {
36 it('covers no auth mechanisms', function () {
37 options
.authenticator
.authnEnabled
= [];
39 authenticator
= new Authenticator(stubLogger
, stubDb
, options
);
40 assert
.fail(noExpectedException
);
42 assert
.strictEqual(e
.message
, 'no authentication mechanisms available');
46 describe('isValidBasic', function () {
47 it('succeeds', async
function () {
48 _authMechanismRequired(authenticator
, 'argon2');
49 authenticator
.db
.authenticationGet
.resolves({
53 const authString
= `${identifier}:${password}`;
54 const result
= await authenticator
.isValidBasic(authString
, ctx
);
55 assert
.strictEqual(result
, true);
56 assert
.strictEqual(ctx
.authenticationId
, identifier
);
58 it('fails', async
function () {
59 _authMechanismRequired(authenticator
, 'argon2');
60 authenticator
.db
.authenticationGet
.resolves({
64 const authString
= `${identifier}:wrongPassword}`;
65 const result
= await authenticator
.isValidBasic(authString
, ctx
);
66 assert
.strictEqual(result
, false);
67 assert
.strictEqual(ctx
.authenticationId
, undefined);
69 it('covers no entry', async
function() {
70 authenticator
.db
.authenticationGet
.resolves();
71 const authString
= `${identifier}:wrongPassword}`;
72 const result
= await authenticator
.isValidBasic(authString
, ctx
);
73 assert
.strictEqual(result
, false);
74 assert
.strictEqual(ctx
.authenticationId
, undefined);
76 it('covers unknown password hash', async
function () {
77 authenticator
.db
.authenticationGet
.resolves({
79 credential: '$other$kind_of_credential',
81 const authString
= `${identifier}:wrongPassword}`;
82 const result
= await authenticator
.isValidBasic(authString
, ctx
);
83 assert
.strictEqual(result
, false);
84 assert
.strictEqual(ctx
.authenticationId
, undefined);
88 describe('isValidIdentifierCredential', function () {
89 it('succeeds', async
function () {
90 _authMechanismRequired(authenticator
, 'argon2');
91 authenticator
.db
.authenticationGet
.resolves({
95 const result
= await authenticator
.isValidIdentifierCredential(identifier
, password
, ctx
);
96 assert
.strictEqual(result
, true);
97 assert
.strictEqual(ctx
.authenticationId
, identifier
);
99 it('fails', async
function () {
100 _authMechanismRequired(authenticator
, 'argon2');
101 authenticator
.db
.authenticationGet
.resolves({
105 const result
= await authenticator
.isValidIdentifierCredential(identifier
, 'wrongPassword', ctx
);
106 assert
.strictEqual(result
, false);
107 assert
.strictEqual(ctx
.authenticationId
, undefined);
109 it('covers no entry', async
function() {
110 authenticator
.db
.authenticationGet
.resolves();
111 const result
= await authenticator
.isValidIdentifierCredential(identifier
, 'wrongPassword', ctx
);
112 assert
.strictEqual(result
, false);
113 assert
.strictEqual(ctx
.authenticationId
, undefined);
115 it('covers unknown password hash', async
function () {
116 authenticator
.db
.authenticationGet
.resolves({
118 credential: '$other$kind_of_credential',
120 const result
= await authenticator
.isValidIdentifierCredential(identifier
, 'wrongPassword', ctx
);
121 assert
.strictEqual(result
, false);
122 assert
.strictEqual(ctx
.authenticationId
, undefined);
124 it('covers PAM', async
function () {
125 _authMechanismRequired(authenticator
, 'pam');
126 sinon
.stub(authenticator
, '_isValidPAMIdentifier').resolves(true);
127 authenticator
.db
.authenticationGet
.resolves({
131 const result
= await authenticator
.isValidIdentifierCredential(identifier
, password
, ctx
);
132 assert
.strictEqual(result
, true);
133 assert
.strictEqual(ctx
.authenticationId
, identifier
);
135 it('covers debug', async
function () {
136 authenticator
.authnEnabled
= ['DEBUG_ANY'];
137 const result
= await authenticator
.isValidIdentifierCredential(identifier
, password
, ctx
);
138 assert
.strictEqual(result
, true);
139 assert
.strictEqual(ctx
.authenticationId
, identifier
);
141 }); // isValidIdentifierCredential
143 describe('_isValidPAMIdentifier', function () {
144 beforeEach(function () {
145 _authMechanismRequired(authenticator
, 'pam');
146 sinon
.stub(authenticator
.authn
.pam
, 'pamAuthenticatePromise');
148 it('covers success', async
function () {
149 authenticator
.authn
.pam
.pamAuthenticatePromise
.resolves(true);
150 const result
= await authenticator
._isValidPAMIdentifier(identifier
, credential
);
151 assert
.strictEqual(result
, true);
153 it('covers failure', async
function () {
154 _authMechanismRequired(authenticator
, 'pam');
155 authenticator
.authn
.pam
.pamAuthenticatePromise
.rejects(new authenticator
.authn
.pam
.PamError());
156 const result
= await authenticator
._isValidPAMIdentifier(identifier
, credential
);
157 assert
.strictEqual(result
, false);
159 it('covers error', async
function () {
160 _authMechanismRequired(authenticator
, 'pam');
161 const expected
= new Error('blah');
162 authenticator
.authn
.pam
.pamAuthenticatePromise
.rejects(expected
);
164 await authenticator
._isValidPAMIdentifier(identifier
, credential
);
165 assert
.fail(noExpectedException
);
167 assert
.deepStrictEqual(e
, expected
);
170 it('covers forbidden', async
function () {
172 const result
= await authenticator
._isValidPAMIdentifier(identifier
, credential
);
173 assert
.strictEqual(result
, false);
175 }); // _isValidPAMIdentifier
177 describe('isValidAuthorization', function () {
178 it('handles basic', async
function () {
179 const expected
= true;
180 const authorizationHeader
= 'basic Zm9vOmJhcg==';
181 sinon
.stub(authenticator
, 'isValidBasic').resolves(expected
);
182 const result
= await authenticator
.isValidAuthorization(authorizationHeader
, ctx
);
183 assert
.strictEqual(result
, expected
);
185 it('handles other', async
function () {
186 const expected
= false;
187 const authorizationHeader
= 'bearer Zm9vOmJhcg==';
188 const result
= await authenticator
.isValidAuthorization(authorizationHeader
, ctx
);
189 assert
.strictEqual(result
, expected
);
191 }); // isValidAuthorization
193 describe('requestBasic', function () {
194 it('covers', function () {
199 authenticator
.requestBasic(res
);
200 assert
.fail(noExpectedException
);
202 assert(e
instanceof Errors
.ResponseError
);
203 assert
.strictEqual(e
.statusCode
, Enum
.ErrorResponse
.Unauthorized
.statusCode
);
208 describe('isValidCookieAuth', function () {
209 beforeEach(function () {
210 sinon
.stub(authenticator
.mysteryBox
, 'unpack');
212 it('covers identifier success', async
function () {
213 authenticator
.mysteryBox
.unpack
.resolves({
214 authenticatedIdentifier: 'identifier',
216 const result
= await authenticator
.isValidCookieAuth(ctx
, 'WSHas=dummy');
217 assert
.strictEqual(result
, true);
219 it('covers profile success', async
function () {
220 authenticator
.mysteryBox
.unpack
.resolves({
221 authenticatedProfile: 'profile',
223 const result
= await authenticator
.isValidCookieAuth(ctx
, 'WSHas=dummy');
224 assert
.strictEqual(result
, true);
226 it('covers missing cookie', async
function () {
227 const result
= await authenticator
.isValidCookieAuth(ctx
, 'wrongCookie');
228 assert
.strictEqual(result
, false);
230 it('covers bad cookie', async
function () {
231 authenticator
.mysteryBox
.unpack
.rejects();
232 const result
= await authenticator
.isValidCookieAuth(ctx
, 'WSHas=dummy');
233 assert
.strictEqual(result
, false);
235 }); // isValidCookieAuth
237 describe('required', function () {
239 beforeEach(function () {
240 ctx
.clientProtocol
= 'https';
242 getHeader: sinon
.stub(),
246 setHeader: sinon
.stub(),
249 it('succeeds', async
function() {
250 req
.getHeader
.returns('auth header');
251 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(true);
252 const result
= await authenticator
.required(req
, res
, ctx
);
253 assert
.strictEqual(result
, true);
255 it('covers valid cookie session', async
function () {
256 req
.getHeader
.returns('WSHas=sessionCookie');
257 sinon
.stub(authenticator
, 'isValidCookieAuth').resolves(true);
258 const result
= await authenticator
.required(req
, res
, ctx
);
259 assert
.strictEqual(result
, true);
261 it('rejects insecure connection', async
function () {
262 ctx
.clientProtocol
= 'http';
264 await authenticator
.required(req
, res
, ctx
);
265 assert
.fail(noExpectedException
);
267 assert(e
instanceof Errors
.ResponseError
);
268 assert
.strictEqual(e
.statusCode
, Enum
.ErrorResponse
.Forbidden
.statusCode
);
271 it('rejects invalid auth', async
function () {
273 req
.getHeader
.returns('auth header');
274 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(false);
275 await authenticator
.required(req
, res
, ctx
);
276 assert
.fail(noExpectedException
);
278 assert(e
instanceof Errors
.ResponseError
);
279 assert
.strictEqual(e
.statusCode
, Enum
.ErrorResponse
.Unauthorized
.statusCode
);
282 it('redirects without any auth', async
function () {
283 await authenticator
.required(req
, res
, ctx
);
284 assert(res
.end
.called
);
285 assert(res
.setHeader
.called
);
289 describe('requiredLocal', function () {
291 beforeEach(function () {
292 ctx
.clientProtocol
= 'https';
294 getHeader: sinon
.stub(),
298 setHeader: sinon
.stub(),
301 it('succeeds', async
function() {
302 req
.getHeader
.returns('auth header');
303 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(true);
304 const result
= await authenticator
.requiredLocal(req
, res
, ctx
);
305 assert
.strictEqual(result
, true);
307 it('covers valid cookie session', async
function () {
308 req
.getHeader
.returns('WSHas=sessionCookie');
309 sinon
.stub(authenticator
, 'isValidCookieAuth').resolves(true);
311 authenticatedIdentifier: identifier
,
313 const result
= await authenticator
.requiredLocal(req
, res
, ctx
);
314 assert
.strictEqual(result
, true);
316 it('rejects insecure connection', async
function () {
317 ctx
.clientProtocol
= 'http';
319 await authenticator
.requiredLocal(req
, res
, ctx
);
320 assert
.fail(noExpectedException
);
322 assert(e
instanceof Errors
.ResponseError
);
323 assert
.strictEqual(e
.statusCode
, Enum
.ErrorResponse
.Forbidden
.statusCode
);
326 it('rejects invalid auth', async
function () {
328 req
.getHeader
.returns('auth header');
329 sinon
.stub(authenticator
, 'isValidAuthorization').resolves(false);
330 await authenticator
.requiredLocal(req
, res
, ctx
);
331 assert
.fail(noExpectedException
);
333 assert(e
instanceof Errors
.ResponseError
);
334 assert
.strictEqual(e
.statusCode
, Enum
.ErrorResponse
.Unauthorized
.statusCode
);
337 it('redirects without any auth', async
function () {
338 await authenticator
.requiredLocal(req
, res
, ctx
);
339 assert(res
.end
.called
);
340 assert(res
.setHeader
.called
);