X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=test%2Flib%2Fauthenticator.js;h=71420810f3e878edc841c7c243976fc19e24b677;hb=9c8e775e5ab96a1788f535760bfa72205c430d15;hp=75761689556c9adda18a7773901037c34bb2b71b;hpb=2ca511865b0caf3108819cfd6ee775124ea70dff;p=squeep-authentication-module diff --git a/test/lib/authenticator.js b/test/lib/authenticator.js index 7576168..7142081 100644 --- a/test/lib/authenticator.js +++ b/test/lib/authenticator.js @@ -1,8 +1,10 @@ /* eslint-env mocha */ +/* eslint-disable sonarjs/no-duplicate-string */ +/* eslint-disable jsdoc/require-jsdoc */ 'use strict'; const assert = require('assert'); -const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require +const sinon = require('sinon'); const Authenticator = require('../../lib/authenticator'); const stubLogger = require('../stub-logger'); const stubDb = require('../stub-db'); @@ -25,6 +27,7 @@ describe('Authenticator', function () { ctx = {}; password = 'badPassword'; stubDb._reset(); + stubLogger._reset(); }); afterEach(function () { sinon.restore(); @@ -42,6 +45,100 @@ describe('Authenticator', function () { authenticator = new Authenticator(stubLogger, stubDb, options); }); + it('covers option defaults', function () { + delete options.authenticator.secureAuthOnly; + delete options.dingus?.proxyPrefix; + delete options.authenticator.forbiddenPAMIdentifiers; + options.authenticator.authnEnabled.push('flarpyauth'); + authenticator = new Authenticator(stubLogger, stubDb, options); + }); + + describe('createIdentifier', function () { + let dbCtx; + beforeEach(function () { + dbCtx = {}; + credential = 'badpassword'; + }); + it('covers success', async function () { + const otpKey = '1234567890123456789012'; + await authenticator.createIdentifier(dbCtx, identifier, credential, otpKey); + assert(authenticator.db.authenticationUpsert.called); + }); + it('covers failure', async function () { + const expected = new Error('blah'); + await authenticator.db.authenticationUpsert.rejects(expected); + // assert.rejects was not happy to handle this for some reason + try { + await authenticator.createIdentifier(dbCtx, identifier, credential); + assert.fail('no expecte exception'); + } catch (e) { + assert.deepStrictEqual(e, expected); + assert(authenticator.db.authenticationUpsert.called); + assert(authenticator.logger.error.called); + } + }); + }); // createIdentifier + + describe('updateCredential', function () { + let dbCtx, newCredential; + beforeEach(function () { + dbCtx = {}; + newCredential = 'newpassword'; + }); + it('covers success', async function () { + await authenticator.updateCredential(dbCtx, identifier, newCredential); + assert(authenticator.db.authenticationUpdateCredential.called); + assert(authenticator.logger.info.called); + }); + it('covers failure', async function () { + const expected = new Error('foo'); + authenticator.db.authenticationUpdateCredential.rejects(expected); + try { + await authenticator.updateCredential(dbCtx, identifier, newCredential); + assert.fail('no expected exception'); + } catch (e) { + assert.deepStrictEqual(e, expected); + assert(authenticator.logger.error.called); + } + // assert.rejects was not happy to handle this for some reason + }); + }); // updateCredential + + describe('_secureCredential', function () { + beforeEach(function () { + credential = 'badpassword'; + }); + it('covers plain', async function () { + const result = await authenticator._secureCredential(credential, 'plain'); + assert.strictEqual(result, '$plain$' + credential); + }); + it('covers default (argon2)', async function () { + const result = await authenticator._secureCredential(credential); + assert(result.startsWith('$argon2')); + }); + it('covers invalid authn', async function () { + const authn = 'bogus'; + assert.rejects(async () => await authenticator._secureCredential(credential, authn), RangeError); + }); + }); // _secureCredential + + describe('_validateAuthDataCredential', function () { + let authData; + beforeEach(function () { + credential = 'badpassword'; + authData = {}; + }); + it('fails if not provided a credential', async function () { + const result = await authenticator._validateAuthDataCredential(authData, credential); + assert.strictEqual(result, false); + }); + it('covers plain', async function () { + authData.credential = '$plain$badpassword'; + const result = await authenticator._validateAuthDataCredential(authData, credential); + assert.strictEqual(result, true); + }); + }); // _validateAuthDataCredential + describe('isValidBasic', function () { it('succeeds', async function () { _authMechanismRequired(authenticator, 'argon2'); @@ -95,6 +192,19 @@ describe('Authenticator', function () { assert.strictEqual(result, true); assert.strictEqual(ctx.authenticationId, identifier); }); + it('succeeds with OTP', async function () { + const otpKey = Buffer.from('1234567890'); + _authMechanismRequired(authenticator, 'argon2'); + authenticator.db.authenticationGet.resolves({ + identifier, + credential, + otpKey, + }); + const result = await authenticator.isValidIdentifierCredential(identifier, password, ctx); + assert.strictEqual(result, true); + assert.strictEqual(ctx.authenticationId, identifier); + assert.deepStrictEqual(ctx.otpKey, otpKey); + }); it('fails', async function () { _authMechanismRequired(authenticator, 'argon2'); authenticator.db.authenticationGet.resolves({ @@ -137,68 +247,41 @@ describe('Authenticator', function () { assert.strictEqual(result, true); assert.strictEqual(ctx.authenticationId, identifier); }); - it('covers debug', async function () { - authenticator.authnEnabled = ['DEBUG_ANY']; - const result = await authenticator.isValidIdentifierCredential(identifier, password, ctx); - assert.strictEqual(result, true); - assert.strictEqual(ctx.authenticationId, identifier); - }); }); // isValidIdentifierCredential describe('_isValidPAMIdentifier', function () { + let authData; beforeEach(function () { _authMechanismRequired(authenticator, 'pam'); sinon.stub(authenticator.authn.pam, 'pamAuthenticatePromise'); + authData = { + identifier, + }; }); it('covers success', async function () { authenticator.authn.pam.pamAuthenticatePromise.resolves(true); - const result = await authenticator._isValidPAMIdentifier(identifier, credential); + const result = await authenticator._isValidPAMIdentifier(authData, credential); assert.strictEqual(result, true); }); it('covers failure', async function () { _authMechanismRequired(authenticator, 'pam'); authenticator.authn.pam.pamAuthenticatePromise.rejects(new authenticator.authn.pam.PamError()); - const result = await authenticator._isValidPAMIdentifier(identifier, credential); + const result = await authenticator._isValidPAMIdentifier(authData, credential); assert.strictEqual(result, false); }); it('covers error', async function () { _authMechanismRequired(authenticator, 'pam'); const expected = new Error('blah'); authenticator.authn.pam.pamAuthenticatePromise.rejects(expected); - assert.rejects(() => authenticator._isValidPAMIdentifier(identifier, credential), expected); + assert.rejects(() => authenticator._isValidPAMIdentifier(authData, credential), expected); }); it('covers forbidden', async function () { - identifier = 'root'; - const result = await authenticator._isValidPAMIdentifier(identifier, credential); + authData.identifier = 'root'; + const result = await authenticator._isValidPAMIdentifier(authData, credential); assert.strictEqual(result, false); }); }); // _isValidPAMIdentifier - describe('_cookieParse', function () { - it('covers empty', function () { - const expected = {}; - const result = Authenticator._cookieParse(); - assert.deepStrictEqual(result, expected); - }); - it('covers non variable', function () { - const cookie = 'foo'; - const expected = { - foo: null, - }; - const result = Authenticator._cookieParse(cookie); - assert.deepStrictEqual(result, expected); - }); - it('parses cookie', function () { - const cookie = 'foo=bar; baz="quux"'; - const expected = { - foo: 'bar', - baz: 'quux', - }; - const result = Authenticator._cookieParse(cookie); - assert.deepStrictEqual(result, expected); - }); - }); // _cookieParse - describe('isValidAuthorization', function () { it('handles basic', async function () { const expected = true; @@ -228,40 +311,42 @@ describe('Authenticator', function () { }); // requestBasic describe('isValidCookieAuth', function () { - let cookie; beforeEach(function () { sinon.stub(authenticator.mysteryBox, 'unpack'); - cookie = 'squeepSession=dummy'; + ctx.cookie = { + squeepSession: 'dummy', + otherCookie: 'foo', + }; }); it('covers identifier success', async function () { authenticator.mysteryBox.unpack.resolves({ authenticatedIdentifier: 'identifier', }); - const result = await authenticator.isValidCookieAuth(ctx, cookie); + const result = await authenticator.isValidCookieAuth(ctx); assert.strictEqual(result, true); }); it('covers profile success', async function () { authenticator.mysteryBox.unpack.resolves({ authenticatedProfile: 'profile', }); - const result = await authenticator.isValidCookieAuth(ctx, cookie); + const result = await authenticator.isValidCookieAuth(ctx); assert.strictEqual(result, true); }); it('covers missing cookie', async function () { - cookie = 'wrong=cookie'; - const result = await authenticator.isValidCookieAuth(ctx, cookie); + delete ctx.cookie.squeepSession; + const result = await authenticator.isValidCookieAuth(ctx); assert.strictEqual(result, false); }); it('covers bad cookie', async function () { authenticator.mysteryBox.unpack.rejects(); - const result = await authenticator.isValidCookieAuth(ctx, cookie); + const result = await authenticator.isValidCookieAuth(ctx); assert.strictEqual(result, false); }); it('covers broken session', async function () { authenticator.mysteryBox.unpack.resolves({ randomData: 'foo', }); - const result = await authenticator.isValidCookieAuth(ctx, cookie); + const result = await authenticator.isValidCookieAuth(ctx); assert.strictEqual(result, false); }); }); // isValidCookieAuth @@ -271,7 +356,7 @@ describe('Authenticator', function () { this.beforeEach(function () { sinon.stub(authenticator.TOTP.prototype, 'validate').returns(true); state = { - key: Buffer.from('12345678901234567890'), + key: '12345678901234567890123456789012', attempt: 0, epochMs: Date.now(), }; @@ -300,24 +385,42 @@ describe('Authenticator', function () { }); }); // checkOTP + describe('updateOTPKey', function () { + let dbCtx, otpKey; + beforeEach(function () { + dbCtx = {}; + otpKey = 'CDBGB3U3B2ILECQORMINGGSZN7LXY565'; + }); + it('covers success', async function () { + await authenticator.updateOTPKey(dbCtx, identifier, otpKey); + assert(authenticator.db.authenticationUpdateOTPKey.called); + }); + it('covers failure', async function () { + authenticator.db.authenticationUpdateOTPKey.rejects(); + assert.rejects(authenticator.updateOTPKey(dbCtx, identifier, otpKey)); + }); + }); // updateOTPKey + describe('sessionCheck', function () { - let cookie, req, res, loginPath, required, profilesAllowed; + let req, res, loginPath, required, profilesAllowed; beforeEach(function () { - cookie = 'squeepSession=sessionCookie'; ctx.clientProtocol = 'https'; + ctx.cookie = { + squeepSession: 'squeep_session_blob', + }; req = { getHeader: sinon.stub(), }; res = { end: sinon.stub(), setHeader: sinon.stub(), + appendHeader: sinon.stub(), }; loginPath = '/admin/login'; required = true; profilesAllowed = true; }); it('covers valid cookie session', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedIdentifier: 'user', @@ -327,7 +430,6 @@ describe('Authenticator', function () { }); it('covers valid insecure cookie session', async function () { authenticator.secureAuthOnly = false; - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedIdentifier: 'user', @@ -356,7 +458,6 @@ describe('Authenticator', function () { describe('convenience wrappers', function () { describe('sessionRequiredLocal', function () { it('accepts identifier', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedIdentifier: 'user', @@ -365,7 +466,6 @@ describe('Authenticator', function () { assert.strictEqual(result, true); }); it('redirects with profile', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedProfile: 'user', @@ -378,7 +478,6 @@ describe('Authenticator', function () { }); // sessionRequiredLocal describe('sessionRequired', function () { it('accepts identifier', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedIdentifier: 'user', @@ -387,7 +486,6 @@ describe('Authenticator', function () { assert.strictEqual(result, true); }); it('accepts profile', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedProfile: 'user', @@ -396,7 +494,6 @@ describe('Authenticator', function () { assert.strictEqual(result, true); }); it('rejects invalid', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(false); const result = await authenticator.sessionRequired(req, res, ctx, loginPath); assert.strictEqual(result, false); @@ -405,7 +502,6 @@ describe('Authenticator', function () { }); it('covers insecure allowed', async function () { authenticator.options.authenticator.secureAuthOnly = false; - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(false); const result = await authenticator.sessionRequired(req, res, ctx, loginPath); assert.strictEqual(result, false); @@ -415,7 +511,6 @@ describe('Authenticator', function () { }); // sessionRequired describe('sessionOptionalLocal', function () { it('rejects profile', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); ctx.session = { authenticatedProfile: 'user', @@ -428,7 +523,6 @@ describe('Authenticator', function () { }); // sessionOptionalLocal describe('sessionOptional', function () { it('rejects invalid', async function () { - req.getHeader.returns(cookie); sinon.stub(authenticator, 'isValidCookieAuth').resolves(false); const result = await authenticator.sessionOptional(req, res, ctx, loginPath); assert.strictEqual(result, false);