From 3d70ed96862d0830adaa414f4a2a976d51c2e0ce Mon Sep 17 00:00:00 2001 From: gowthamidommety-okta Date: Thu, 15 Feb 2018 16:40:38 -0800 Subject: [PATCH] :seedling: Adds support for autoPush url param in poll and verify functions (#91) Resolves: OKTA-155565 --- README.md | 28 +- lib/tx.js | 49 ++- test/spec/mfa-challenge.js | 714 +++++++++++++++++++++++++++++++++++++ test/spec/mfa-required.js | 308 ++++++++++++++++ 4 files changed, 1085 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 58e224671..3472e47ff 100644 --- a/README.md +++ b/README.md @@ -996,6 +996,20 @@ questionFactor.verify({ }); ``` +###### [OKTA push](https://developer.okta.com/docs/api/resources/authn.html#verify-push-factor) + + - `autoPush` - Optional parameter to send a push notification immediately the next time `verify` is called on a push factor + +```javascript +var pushFactor = transaction.factors.find(function(factor) { + return factor.provider === 'OKTA' && factor.factorType === 'push'; +}); + +pushFactor.verify({ + autoPush: true +}); +``` + ###### [All other factors](http://developer.okta.com/docs/api/resources/authn.html#verify-factor) ```javascript @@ -1044,19 +1058,25 @@ The user must verify the factor-specific challenge. #### [verify(options)](http://developer.okta.com/docs/api/resources/authn.html#verify-factor) - `passCode` - OTP sent to device + - `autoPush` - Optional parameter to send a push notification immediately the next time `verify` is called on a push factor ```javascript transaction.verify({ - passCode: '615243' + passCode: '615243', + autoPush: true }); ``` -#### [poll()](http://developer.okta.com/docs/api/resources/authn.html#activate-push-factor) +#### [poll(options)](http://developer.okta.com/docs/api/resources/authn.html#activate-push-factor) -Poll until factorResult is not WAITING. Throws AuthPollStopError if prev or cancel is called. + - `autoPush` - Optional parameter to send a push notification immediately the next time `verify` is called on a push factor + +Poll until factorResult is not WAITING. Throws AuthPollStopError if prev, resend, or cancel is called. ```javascript -transaction.poll(); +transaction.poll({ + autoPush: true +}); ``` #### [prev()](http://developer.okta.com/docs/api/resources/authn.html#previous-transaction-state) diff --git a/lib/tx.js b/lib/tx.js index e1a1a6ccf..40e162e79 100644 --- a/lib/tx.js +++ b/lib/tx.js @@ -1,4 +1,4 @@ -/* eslint-disable complexity */ +/* eslint-disable complexity, max-statements */ var http = require('./http'); var util = require('./util'); var Q = require('q'); @@ -7,7 +7,8 @@ var AuthPollStopError = require('./errors/AuthPollStopError'); var config = require('./config'); function addStateToken(res, options) { - var builtArgs = util.clone(options) || {}; + var builtArgs = {}; + util.extend(builtArgs, options); // Add the stateToken if one isn't passed and we have one if (!builtArgs.stateToken && res.stateToken) { @@ -59,12 +60,14 @@ function getPollFn(sdk, res, ref) { return function (options) { var delay; var rememberDevice; + var autoPush; if (util.isNumber(options)) { delay = options; } else if (util.isObject(options)) { delay = options.delay; rememberDevice = options.rememberDevice; + autoPush = options.autoPush; } if (!delay && delay !== 0) { @@ -74,10 +77,22 @@ function getPollFn(sdk, res, ref) { // Get the poll function var pollLink = util.getLink(res, 'next', 'poll'); function pollFn() { - var href = pollLink.href; + var opts = {}; + if (typeof autoPush === 'function') { + try { + opts.autoPush = !!autoPush(); + } + catch (e) { + return Q.reject(new AuthSdkError('AutoPush resulted in an error.')); + } + } + else if (autoPush !== undefined && autoPush !== null) { + opts.autoPush = !!autoPush; + } if (rememberDevice) { - href += '?rememberDevice=true'; + opts.rememberDevice = true; } + var href = pollLink.href + util.toQueryParams(opts); return http.post(sdk, href, getStateToken(res), { saveAuthnState: false }); @@ -87,12 +102,10 @@ function getPollFn(sdk, res, ref) { var retryCount = 0; var recursivePoll = function () { - // If the poll was manually stopped during the delay if (!ref.isPolling) { return Q.reject(new AuthPollStopError()); } - return pollFn() .then(function (pollRes) { // Reset our retry counter on success @@ -180,21 +193,37 @@ function link2fn(sdk, res, obj, link, ref) { }); } - var href = link.href; + var params = {}; + var autoPush = data.autoPush; + if (autoPush !== undefined) { + if (typeof autoPush === 'function') { + try { + params.autoPush = !!autoPush(); + } + catch (e) { + return Q.reject(new AuthSdkError('AutoPush resulted in an error.')); + } + } + else if (autoPush !== null) { + params.autoPush = !!autoPush; + } + data = util.omit(data, 'autoPush'); + } + if (data.rememberDevice !== undefined) { if (data.rememberDevice) { - href += '?rememberDevice=true'; + params.rememberDevice = true; } data = util.omit(data, 'rememberDevice'); } else if (data.profile && data.profile.updatePhone !== undefined) { if (data.profile.updatePhone) { - href += '?updatePhone=true'; + params.updatePhone = true; } data.profile = util.omit(data.profile, 'updatePhone'); } - + var href = link.href + util.toQueryParams(params); return postToTransaction(sdk, href, data); }; } diff --git a/test/spec/mfa-challenge.js b/test/spec/mfa-challenge.js index 18caceaba..a6f752f44 100644 --- a/test/spec/mfa-challenge.js +++ b/test/spec/mfa-challenge.js @@ -65,6 +65,266 @@ define(function(require) { }); } }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush true', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=true', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: true + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush as false', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=false', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: false + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush as a function', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=true', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: function () { + return true; + } + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification when autoPush function returns truthy value', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=true', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: function () { + return 'test'; + } + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification when autoPush function returns falsy value', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=false', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: function () { + return ''; + } + }); + } + }); + + util.itErrorsCorrectly({ + title: 'throws an error when autoPush function throws an error', + setup: { + status: 'mfa-challenge-sms' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: function () { + throw new Error('test'); + } + }); + }, + expectations: function (test, err) { + expect(err.name).toEqual('AuthSdkError'); + expect(err.errorSummary).toEqual('AutoPush resulted in an error.'); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush as undefined', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: undefined + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush as null', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: null + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush and rememberDevice true', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=true&rememberDevice=true', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: true, + rememberDevice: true + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush and rememberDevice false', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=false', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: false, + rememberDevice: false + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush undefined and rememberDevice true', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?rememberDevice=true', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: undefined, + rememberDevice: true + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows verification with autoPush null and rememberDevice true', + setup: { + status: 'mfa-challenge-sms', + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?rememberDevice=true', + data: { + stateToken: '00rt1IY9c6Q3RVc4a2jJPbS2uAtFNWJz_d8A26KTdF', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + return test.trans.verify({ + passCode: '123456', + autoPush: null, + rememberDevice: true + }); + } + }); }); describe('trans.poll', function () { @@ -149,6 +409,460 @@ define(function(require) { } }); + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push with autoPush true', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: true + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push with autoPush false', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: false + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push with autoPush as a function', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: function () { + return true; + } + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push with autoPush value changing during poll', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + var count = 1; + return test.trans.poll({ + delay: 0, + autoPush: function () { + if(count === 3) { + return false; + } + count ++; + return true; + } + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push when autoPush function returns truthy value', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: function () { + return 'test'; + } + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push when autoPush function returns falsy value', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=false', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: function () { + return ''; + } + }); + } + }); + + util.itErrorsCorrectly({ + title: 'throws an error when autoPush function throws an error during polling', + setup: { + status: 'mfa-challenge-push' + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: function () { + throw new Error('test'); + } + }); + }, + expectations: function (test, err) { + expect(err.name).toEqual('AuthSdkError'); + expect(err.errorSummary).toEqual('AutoPush resulted in an error.'); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'does not include autoPush for polling for push if autoPush undefined', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: undefined + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'does not include autoPush for polling for push if autoPush null', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: null + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'allows polling for push with autoPush and rememberDevice', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true&rememberDevice=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true&rememberDevice=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true&rememberDevice=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: true, + rememberDevice: true + }); + } + }); + + util.itMakesCorrectRequestResponse({ + title: 'does not include rememberDevice for polling for push if rememberDevice is falsy', + setup: { + status: 'mfa-challenge-push', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'mfa-challenge-push' + }, + { + request: { + uri: '/api/v1/authn/factors/opf492vmb3s1blLTs0h7/verify?autoPush=true', + data: { + stateToken: '00T4jcVNRzJy5dkWJ4P7c9051dY3FUYY9O2zvbU_vI' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + return test.trans.poll({ + delay: 0, + autoPush: true, + rememberDevice: false + }); + } + }); + util.itMakesCorrectRequestResponse({ title: 'allows polling for push after a network error', setup: { diff --git a/test/spec/mfa-required.js b/test/spec/mfa-required.js index 0cc223fa3..e0d333f4c 100644 --- a/test/spec/mfa-required.js +++ b/test/spec/mfa-required.js @@ -107,6 +107,266 @@ define(function(require) { }); } }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush as a query param if true', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: true + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush as a query param if false', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=false', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: false + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush as a query param if function returns true', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + 'passCode': '123456', + 'autoPush': function() { + return true; + } + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush as a boolean query param if function returns a truthy value', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + 'passCode': '123456', + 'autoPush': function() { + return 'test'; + } + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush as a boolean query param if function returns a falsy value', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=false', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + 'passCode': '123456', + 'autoPush': function() { + return ''; + } + }); + } + }); + util.itErrorsCorrectly({ + title: 'throws an error when autoPush function throws an error', + setup: { + status: 'mfa-required' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + 'passCode': '123456', + 'autoPush': function () { + throw new Error('test'); + } + }); + }, + expectations: function (test, err) { + expect(err.name).toEqual('AuthSdkError'); + expect(err.errorSummary).toEqual('AutoPush resulted in an error.'); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'doesn\'t pass autoPush as a query param if undefined', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: undefined + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'doesn\'t pass autoPush as a query param if null', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: null + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush and rememberDevice as a query param if true', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=true&rememberDevice=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: true, + rememberDevice: true + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes autoPush as a query param if autoPush is true and rememberDevice is false', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?autoPush=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: true, + rememberDevice: false + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes rememberDevice as a query param if autoPush is undefined and rememberDevice is true', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?rememberDevice=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: undefined, + rememberDevice: true + }); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'passes rememberDevice as a query param if autoPush is null and rememberDevice is true', + setup: { + status: 'mfa-required', + request: { + uri: '/api/v1/authn/factors/uftigiEmYTPOmvqTS0g3/verify?rememberDevice=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + }, + execute: function (test) { + var factor = _.find(test.trans.factors, {id: 'uftigiEmYTPOmvqTS0g3'}); + return factor.verify({ + passCode: '123456', + autoPush: null, + rememberDevice: true + }); + } + }); util.itErrorsCorrectly({ title: 'returns correct error when invalid answer provided (403)', setup: { @@ -175,6 +435,54 @@ define(function(require) { }); } }); + util.itMakesCorrectRequestResponse({ + title: 'allows correct answer after invalid answer with autoPush', + setup: { + status: 'mfa-required', + calls: [ + { + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: 'invalidanswer' + } + }, + response: 'mfa-required-error' + }, + { + request: { + uri: '/api/v1/authn/factors/smsigwDlH85L9FyQK0g3/verify?autoPush=true', + data: { + stateToken: '004KscPNUS2LswGp26qiu4Hetqt_zcgz-PcQhPseVP', + passCode: '123456' + } + }, + response: 'success' + } + ] + }, + execute: function (test) { + var invalidError; + var factor = _.find(test.trans.factors, {id: 'smsigwDlH85L9FyQK0g3'}); + return factor.verify({ + passCode: 'invalidanswer', + autoPush: true + }) + .fail(function(err) { + invalidError = err; + }) + .then(function() { + return factor.verify({ + passCode: '123456', + autoPush: true + }); + }) + .fin(function() { + expect(invalidError).not.toBeUndefined(); + }); + } + }); }); describe('trans.cancel', function () {