X-Git-Url: http://git.squeep.com/?p=squeep-authentication-module;a=blobdiff_plain;f=test%2Flib%2Fauthenticator.js;h=dea4a84427e2d9634b10e401a9084b1bea5370f9;hp=75761689556c9adda18a7773901037c34bb2b71b;hb=53ef948ea83106e82d55e60d6695a15e94bf725e;hpb=842a3da269de1ab82e9a2a12aae8ed5677f064d8 diff --git a/test/lib/authenticator.js b/test/lib/authenticator.js index 7576168..dea4a84 100644 --- a/test/lib/authenticator.js +++ b/test/lib/authenticator.js @@ -25,6 +25,7 @@ describe('Authenticator', function () { ctx = {}; password = 'badPassword'; stubDb._reset(); + stubLogger._reset(); }); afterEach(function () { sinon.restore(); @@ -42,6 +43,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.authenticationInsertIdentifier.called); + }); + it('covers failure', async function () { + const expected = new Error('blah'); + await authenticator.db.authenticationInsertIdentifier.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.authenticationInsertIdentifier.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 +190,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 +245,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 +309,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 +354,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(), }; @@ -301,23 +384,25 @@ describe('Authenticator', function () { }); // checkOTP 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 +412,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 +440,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 +448,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 +460,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 +468,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 +476,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 +484,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 +493,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 +505,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);