From be508d2939bd1bd6a30af0ac6d7969f92f54fc44 Mon Sep 17 00:00:00 2001 From: lboyette-okta Date: Thu, 28 Jul 2016 14:16:35 -0700 Subject: [PATCH] Add tokenManager.refresh (#35) Resolves: OKTA-94715 --- lib/TokenManager.js | 44 ++++++- lib/clientBuilder.js | 2 +- lib/token.js | 19 ++- lib/util.js | 9 +- test/spec/general.js | 22 ++-- test/spec/oauth.js | 8 +- test/spec/token.js | 22 ++-- test/spec/tokenManager.js | 246 ++++++++++++++++++++++++++++++++++++-- test/util/oauthUtil.js | 17 +++ test/util/tokens.js | 26 ++++ 10 files changed, 366 insertions(+), 49 deletions(-) diff --git a/lib/TokenManager.js b/lib/TokenManager.js index d5caabc80..02a710090 100644 --- a/lib/TokenManager.js +++ b/lib/TokenManager.js @@ -2,6 +2,7 @@ var util = require('./util'); var AuthSdkError = require('./errors/AuthSdkError'); var cookies = require('./cookies'); var tokenStorageBuilder = require('./tokenStorageBuilder'); +var Q = require('q'); // Provides webStorage-like interface for cookies var cookieStorage = { @@ -14,8 +15,11 @@ var cookieStorage = { function add(storage, key, token) { var tokenStorage = storage.getTokenStorage(); - if (!util.isObject(token)) { - throw new AuthSdkError('Token must be an Object'); + if (!util.isObject(token) || + !token.scopes || + (!token.expiresAt && token.expiresAt !== 0) || + (!token.idToken && !token.accessToken)) { + throw new AuthSdkError('Token must be an Object with scopes, expiresAt, and an idToken or accessToken properties'); } tokenStorage[key] = token; storage.setTokenStorage(tokenStorage); @@ -32,7 +36,40 @@ function remove(storage, key) { storage.setTokenStorage(tokenStorage); } -function TokenManager(options) { +function refresh(sdk, storage, key) { + try { + var token = get(storage, key); + if (!token) { + throw new AuthSdkError('The tokenManager has no token for the key: ' + key); + } + } catch (e) { + return Q.reject(e); + } + + var responseType; + if (token.accessToken) { + responseType = 'token'; + } else { + responseType = 'id_token'; + } + + return sdk.token.getWithoutPrompt({ + responseType: responseType, + scopes: token.scopes + }) + .then(function(freshToken) { + add(storage, key, freshToken); + return freshToken; + }) + .fail(function(err) { + if (err.name === 'OAuthError') { + remove(storage, key); + } + throw err; + }); +} + +function TokenManager(sdk, options) { options = options || {}; options.storage = options.storage || 'localStorage'; @@ -54,6 +91,7 @@ function TokenManager(options) { this.add = util.bind(add, this, storage); this.get = util.bind(get, this, storage); this.remove = util.bind(remove, this, storage); + this.refresh = util.bind(refresh, this, sdk, storage); } module.exports = TokenManager; diff --git a/lib/clientBuilder.js b/lib/clientBuilder.js index e329dede2..56b857921 100644 --- a/lib/clientBuilder.js +++ b/lib/clientBuilder.js @@ -110,7 +110,7 @@ function OktaAuthBuilder(args) { return window.location.hash; }; - sdk.tokenManager = new TokenManager(args.tokenManager); + sdk.tokenManager = new TokenManager(sdk, args.tokenManager); } var proto = OktaAuthBuilder.prototype; diff --git a/lib/token.js b/lib/token.js index 6e840181b..d5b06060b 100644 --- a/lib/token.js +++ b/lib/token.js @@ -261,7 +261,7 @@ function handleOAuthResponse(sdk, oauthParams, res) { } var tokenTypes = oauthParams.responseType; - var scopes = util.clone(oauthParams.scope); + var scopes = util.clone(oauthParams.scopes); var tokenDict = {}; if (res['id_token']) { @@ -321,6 +321,13 @@ function handleOAuthResponse(sdk, oauthParams, res) { function getDefaultOAuthParams(sdk, oauthOptions) { oauthOptions = util.clone(oauthOptions) || {}; + + if (oauthOptions.scope) { + util.deprecate('The param "scope" is equivalent to "scopes". Use "scopes" instead.'); + oauthOptions.scopes = oauthOptions.scope; + delete oauthOptions.scope; + } + var defaults = { clientId: sdk.options.clientId, redirectUri: sdk.options.redirectUri || window.location.href, @@ -328,7 +335,7 @@ function getDefaultOAuthParams(sdk, oauthOptions) { responseMode: 'okta_post_message', state: util.genRandomString(64), nonce: util.genRandomString(64), - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }; util.extend(defaults, oauthOptions); return defaults; @@ -364,10 +371,10 @@ function convertOAuthParamsToQueryParams(oauthParams) { } if (oauthParams.responseType.indexOf('id_token') !== -1 && - oauthParams.scope.indexOf('openid') === -1) { - throw new AuthSdkError('openid scope must be specified in the scope argument when requesting an id_token'); + oauthParams.scopes.indexOf('openid') === -1) { + throw new AuthSdkError('openid scope must be specified in the scopes argument when requesting an id_token'); } else { - oauthQueryParams.scope = oauthParams.scope.join(' '); + oauthQueryParams.scope = oauthParams.scopes.join(' '); } return oauthQueryParams; @@ -586,7 +593,7 @@ function getWithRedirect(sdk, oauthOptions, options) { responseType: oauthParams.responseType, state: oauthParams.state, nonce: oauthParams.nonce, - scope: oauthParams.scope + scopes: oauthParams.scopes })); sdk.token.getWithRedirect._setLocation(requestUrl); diff --git a/lib/util.js b/lib/util.js index c32cf5b4c..0321da459 100644 --- a/lib/util.js +++ b/lib/util.js @@ -189,6 +189,12 @@ function getLink(obj, linkName, altName) { } } +function deprecate(text) { + /* eslint-disable no-console */ + console.log('[okta-auth-sdk] DEPRECATION: ' + text); + /* eslint-enable */ +} + module.exports = { base64UrlToBase64: base64UrlToBase64, base64UrlToString: base64UrlToString, @@ -207,5 +213,6 @@ module.exports = { clone: clone, omit: omit, find: find, - getLink: getLink + getLink: getLink, + deprecate: deprecate }; diff --git a/test/spec/general.js b/test/spec/general.js index 1fd134122..c258b41ce 100644 --- a/test/spec/general.js +++ b/test/spec/general.js @@ -87,7 +87,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, expectations: function (test, res) { expect(res).toEqual(true); } @@ -120,7 +120,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken due to expiration', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, execute: function(test, opts) { util.warpToDistantPast(); return test.oa.idToken.verify(opts.idToken, opts.verifyOpts) @@ -137,7 +137,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies an idToken that would be invalid, except ' + 'we\'re using the expirationTime option', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { expirationTime: 9999999999 }, @@ -156,7 +156,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using single audience option', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { audience: 'NPSfOkH5eZrTy8PMDlvx' }, @@ -167,7 +167,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken using single audience option', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { audience: 'invalid' }, @@ -178,7 +178,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using multiple audience option (all valid)', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { audience: ['NPSfOkH5eZrTy8PMDlvx', 'NPSfOkH5eZrTy8PMDlvx'] }, @@ -189,7 +189,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using multiple audience option (valid and invalid)', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { audience: ['NPSfOkH5eZrTy8PMDlvx', 'invalid2'] }, @@ -200,7 +200,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken using multiple audience option (all invalid)', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { audience: ['invalid1', 'invalid2'] }, @@ -211,9 +211,9 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'verifies a valid idToken using issuer option', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { - issuer: 'https://auth-js-test.okta.com' + issuer: 'https://lboyette.trexcloud.com' }, expectations: function (test, res) { expect(res).toEqual(true); @@ -222,7 +222,7 @@ define(function(require) { setupVerifyIdTokenTest({ title: 'rejects an invalid idToken using issuer option', - idToken: tokens.standardIdToken, + idToken: tokens.verifiableIdToken, verifyOpts: { issuer: 'invalid' }, diff --git a/test/spec/oauth.js b/test/spec/oauth.js index 9ea4d2524..91d9b27aa 100644 --- a/test/spec/oauth.js +++ b/test/spec/oauth.js @@ -179,7 +179,7 @@ define(function(require) { authorizeArgs: { clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://auth-js-test.okta.com/redirect', - scope: ['openid', 'testscope'], + scopes: ['openid', 'testscope'], sessionToken: 'testToken' }, postMessageSrc: { @@ -237,15 +237,15 @@ define(function(require) { authorizeArgs: { clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://auth-js-test.okta.com/redirect', - scope: ['notopenid'], + scopes: ['notopenid'], sessionToken: 'testToken' } }, { name: 'AuthSdkError', - message: 'openid scope must be specified in the scope argument when requesting an id_token', + message: 'openid scope must be specified in the scopes argument when requesting an id_token', errorCode: 'INTERNAL', - errorSummary: 'openid scope must be specified in the scope argument when requesting an id_token', + errorSummary: 'openid scope must be specified in the scopes argument when requesting an id_token', errorLink: 'INTERNAL', errorId: 'INTERNAL', errorCauses: [] diff --git a/test/spec/token.js b/test/spec/token.js index 0d96a6118..6447ba46f 100644 --- a/test/spec/token.js +++ b/test/spec/token.js @@ -74,7 +74,7 @@ define(function(require) { responseMode: 'okta_post_message', state: 'bbbbbb', nonce: 'cccccc', - scope: ['openid', 'custom'], + scopes: ['openid', 'custom'], maxAge: 1469481630, display: 'page' // will be forced to undefined }, @@ -500,7 +500,7 @@ define(function(require) { responseType: 'id_token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';', expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -518,14 +518,14 @@ define(function(require) { oauthUtil.setupRedirect({ getWithRedirectArgs: { responseType: 'token', - scope: ['email'], + scopes: ['email'], sessionToken: 'testToken' }, expectedCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ responseType: 'token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['email'] + scopes: ['email'] }) + ';', expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -549,7 +549,7 @@ define(function(require) { responseType: ['token', 'id_token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';', expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -573,7 +573,7 @@ define(function(require) { responseType: 'id_token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';', expectedResp: { idToken: tokens.standardIdToken, @@ -598,7 +598,7 @@ define(function(require) { responseType: 'token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';', expectedResp: { accessToken: tokens.standardAccessToken, @@ -624,7 +624,7 @@ define(function(require) { responseType: ['id_token', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';', expectedResp: [{ idToken: tokens.standardIdToken, @@ -651,7 +651,7 @@ define(function(require) { responseType: ['id_token', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';' }, { @@ -698,7 +698,7 @@ define(function(require) { responseType: ['id_token', 'token'], state: 'mismatchedState', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';' }, { @@ -724,7 +724,7 @@ define(function(require) { responseType: ['id_token', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'mismatchedNonce', - scope: ['openid', 'email'] + scopes: ['openid', 'email'] }) + ';' }, { diff --git a/test/spec/tokenManager.js b/test/spec/tokenManager.js index 51d53bc17..562ae9e6c 100644 --- a/test/spec/tokenManager.js +++ b/test/spec/tokenManager.js @@ -2,11 +2,14 @@ define(function(require) { var OktaAuth = require('OktaAuth'); var tokens = require('../util/tokens'); var util = require('../util/util'); + var oauthUtil = require('../util/oauthUtil'); function setupSync(options) { options = options || {}; return new OktaAuth({ url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect', tokenManager: { storage: options.type } @@ -23,11 +26,13 @@ define(function(require) { it('defaults to localStorage', function() { var client = setupSync(); client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); - expect(localStorage.getItem('okta-token-storage')).toEqual(JSON.stringify({ + oauthUtil.expectTokenStorageToEqual(localStorage, { 'test-idToken': tokens.standardIdTokenParsed - })); + }); }); + }); + describe('add', function() { it('throws an error when attempting to add a non-token', function() { var client = setupSync(); try { @@ -41,14 +46,231 @@ define(function(require) { } catch (e) { util.expectErrorToEqual(e, { name: 'AuthSdkError', - message: 'Token must be an Object', + message: 'Token must be an Object with scopes, expiresAt, and an idToken or accessToken properties', + errorCode: 'INTERNAL', + errorSummary: 'Token must be an Object with scopes, expiresAt, and an idToken or accessToken properties', + errorLink: 'INTERNAL', + errorId: 'INTERNAL', + errorCauses: [] + }); + } + }); + }); + + describe('refresh', function() { + it('allows refreshing an idToken', function(done) { + return oauthUtil.setupFrame({ + oktaAuthArgs: { + url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect' + }, + tokenManagerAddKeys: { + 'test-idToken': { + idToken: 'testInitialToken', + claims: {'fake': 'claims'}, + expiresAt: 0, + scopes: ['openid', 'email'] + } + }, + tokenManagerRefreshArgs: ['test-idToken'], + postMessageSrc: { + baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + queryParams: { + 'client_id': 'NPSfOkH5eZrTy8PMDlvx', + 'redirect_uri': 'https://auth-js-test.okta.com/redirect', + 'response_type': 'id_token', + 'response_mode': 'okta_post_message', + 'state': oauthUtil.mockedState, + 'nonce': oauthUtil.mockedNonce, + 'scope': 'openid email', + 'prompt': 'none' + } + }, + postMessageResp: { + 'id_token': tokens.standardIdToken, + state: oauthUtil.mockedState + }, + expectedResp: tokens.standardIdTokenParsed + }) + .then(function() { + oauthUtil.expectTokenStorageToEqual(localStorage, { + 'test-idToken': tokens.standardIdTokenParsed + }); + }) + .fin(done); + }); + + it('allows refreshing an accessToken', function(done) { + return oauthUtil.setupFrame({ + oktaAuthArgs: { + url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect' + }, + tokenManagerAddKeys: { + 'test-accessToken': { + accessToken: 'testInitialToken', + expiresAt: 1449703529, + scopes: ['openid', 'email'], + tokenType: 'Bearer' + } + }, + tokenManagerRefreshArgs: ['test-accessToken'], + postMessageSrc: { + baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + queryParams: { + 'client_id': 'NPSfOkH5eZrTy8PMDlvx', + 'redirect_uri': 'https://auth-js-test.okta.com/redirect', + 'response_type': 'token', + 'response_mode': 'okta_post_message', + 'state': oauthUtil.mockedState, + 'nonce': oauthUtil.mockedNonce, + 'scope': 'openid email', + 'prompt': 'none' + } + }, + postMessageResp: { + 'access_token': tokens.standardAccessToken, + 'token_type': 'Bearer', + 'expires_in': 3600, + 'state': oauthUtil.mockedState + }, + expectedResp: tokens.standardAccessTokenParsed + }) + .then(function() { + oauthUtil.expectTokenStorageToEqual(localStorage, { + 'test-accessToken': tokens.standardAccessTokenParsed + }); + }) + .fin(done); + }); + + oauthUtil.itpErrorsCorrectly('throws an errors when a token doesn\'t exist', + { + oktaAuthArgs: { + url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect' + }, + tokenManagerRefreshArgs: ['test-accessToken'] + }, + { + name: 'AuthSdkError', + message: 'The tokenManager has no token for the key: test-accessToken', + errorCode: 'INTERNAL', + errorSummary: 'The tokenManager has no token for the key: test-accessToken', + errorLink: 'INTERNAL', + errorId: 'INTERNAL', + errorCauses: [] + } + ); + + it('throws an errors when the token is mangled', function(done) { + localStorage.setItem('okta-token-storage', '#unparseableJson#'); + return oauthUtil.setupFrame({ + willFail: true, + oktaAuthArgs: { + url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect' + }, + tokenManagerRefreshArgs: ['test-accessToken'] + }) + .then(function() { + expect(true).toEqual(false); + }) + .fail(function(err) { + util.expectErrorToEqual(err, { + name: 'AuthSdkError', + message: 'Unable to parse token storage string', errorCode: 'INTERNAL', - errorSummary: 'Token must be an Object', + errorSummary: 'Unable to parse token storage string', errorLink: 'INTERNAL', errorId: 'INTERNAL', errorCauses: [] }); + }) + .fin(done); + }); + + oauthUtil.itpErrorsCorrectly('throws an error if there\'s an issue refreshing', + { + oktaAuthArgs: { + url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect' + }, + tokenManagerAddKeys: { + 'test-accessToken': { + accessToken: 'testInitialToken', + expiresAt: 1449703529, + scopes: ['openid', 'email'], + tokenType: 'Bearer' + } + }, + tokenManagerRefreshArgs: ['test-accessToken'], + postMessageSrc: { + baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + queryParams: { + 'client_id': 'NPSfOkH5eZrTy8PMDlvx', + 'redirect_uri': 'https://auth-js-test.okta.com/redirect', + 'response_type': 'token', + 'response_mode': 'okta_post_message', + 'state': oauthUtil.mockedState, + 'nonce': oauthUtil.mockedNonce, + 'scope': 'openid email', + 'prompt': 'none' + } + }, + postMessageResp: { + 'access_token': tokens.standardAccessToken, + 'token_type': 'Bearer', + 'expires_in': 3600, + 'state': 'fakeState' + } + }, + { + name: 'AuthSdkError', + message: 'OAuth flow response state doesn\'t match request state', + errorCode: 'INTERNAL', + errorSummary: 'OAuth flow response state doesn\'t match request state', + errorLink: 'INTERNAL', + errorId: 'INTERNAL', + errorCauses: [] } + ); + + it('removes token if an OAuthError is thrown while refreshing', function(done) { + return oauthUtil.setupFrame({ + willFail: true, + oktaAuthArgs: { + url: 'https://auth-js-test.okta.com', + clientId: 'NPSfOkH5eZrTy8PMDlvx', + redirectUri: 'https://auth-js-test.okta.com/redirect' + }, + tokenManagerAddKeys: { + 'test-accessToken': tokens.standardAccessTokenParsed, + 'test-idToken': tokens.standardIdTokenParsed + }, + tokenManagerRefreshArgs: ['test-accessToken'], + postMessageResp: { + error: 'sampleErrorCode', + 'error_description': 'something went wrong' + } + }) + .fail(function(e) { + util.expectErrorToEqual(e, { + name: 'OAuthError', + message: 'something went wrong', + errorCode: 'sampleErrorCode', + errorSummary: 'something went wrong' + }); + oauthUtil.expectTokenStorageToEqual(localStorage, { + 'test-idToken': tokens.standardIdTokenParsed + }); + }) + .fin(done); }); }); @@ -64,9 +286,9 @@ define(function(require) { it('adds a token', function() { var client = localStorageSetup(); client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); - expect(localStorage.getItem('okta-token-storage')).toEqual(JSON.stringify({ + oauthUtil.expectTokenStorageToEqual(localStorage, { 'test-idToken': tokens.standardIdTokenParsed - })); + }); }); }); @@ -89,9 +311,9 @@ define(function(require) { anotherKey: tokens.standardIdTokenParsed })); client.tokenManager.remove('test-idToken'); - expect(localStorage.getItem('okta-token-storage')).toEqual(JSON.stringify({ + oauthUtil.expectTokenStorageToEqual(localStorage, { anotherKey: tokens.standardIdTokenParsed - })); + }); }); }); }); @@ -108,9 +330,9 @@ define(function(require) { it('adds a token', function() { var client = sessionStorageSetup(); client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); - expect(sessionStorage.getItem('okta-token-storage')).toEqual(JSON.stringify({ + oauthUtil.expectTokenStorageToEqual(sessionStorage, { 'test-idToken': tokens.standardIdTokenParsed - })); + }); }); }); @@ -133,9 +355,9 @@ define(function(require) { anotherKey: tokens.standardIdTokenParsed })); client.tokenManager.remove('test-idToken'); - expect(sessionStorage.getItem('okta-token-storage')).toEqual(JSON.stringify({ + oauthUtil.expectTokenStorageToEqual(sessionStorage, { anotherKey: tokens.standardIdTokenParsed - })); + }); }); }); }); diff --git a/test/util/oauthUtil.js b/test/util/oauthUtil.js index d9c0002ab..5ea7b3e3e 100644 --- a/test/util/oauthUtil.js +++ b/test/util/oauthUtil.js @@ -64,6 +64,7 @@ define(function(require) { (opts.authorizeArgs && opts.authorizeArgs.responseMode !== 'fragment') || opts.getWithoutPromptArgs || opts.getWithPopupArgs || + opts.tokenManagerRefreshArgs || opts.refreshArgs) { // Simulate the postMessage between the window and the popup or iframe spyOn(window, 'addEventListener').and.callFake(function(eventName, fn) { @@ -97,6 +98,16 @@ define(function(require) { util.mockGetWindowLocation(authClient, opts.hrefMock); } + if (opts.tokenManagerAddKeys) { + for (var key in opts.tokenManagerAddKeys) { + if (!opts.tokenManagerAddKeys.hasOwnProperty(key)) { + continue; + } + var token = opts.tokenManagerAddKeys[key]; + authClient.tokenManager.add(key, token); + } + } + var promise; if (opts.refreshArgs) { promise = authClient.idToken.refresh(opts.refreshArgs); @@ -104,6 +115,8 @@ define(function(require) { promise = authClient.token.getWithoutPrompt(opts.getWithoutPromptArgs); } else if (opts.getWithPopupArgs) { promise = authClient.token.getWithPopup(opts.getWithPopupArgs); + } else if (opts.tokenManagerRefreshArgs) { + promise = authClient.tokenManager.refresh.apply(this, opts.tokenManagerRefreshArgs); } else { promise = authClient.idToken.authorize(opts.authorizeArgs); } @@ -289,6 +302,10 @@ define(function(require) { }); }; + oauthUtil.expectTokenStorageToEqual = function(storage, obj) { + var parsed = JSON.parse(storage.getItem('okta-token-storage')); + expect(parsed).toEqual(obj); + }; return oauthUtil; }); diff --git a/test/util/tokens.js b/test/util/tokens.js index 5cd6fb608..a89959998 100644 --- a/test/util/tokens.js +++ b/test/util/tokens.js @@ -24,6 +24,25 @@ define(function() { signature: 'TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ' }; + tokens.verifiableIdToken = 'eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMHUxcGNsYTVxWUlSRU' + + 'RMV0NRViIsIm5hbWUiOiJMZW4gQm95ZXR0ZSIsImdpdmVuX25hb' + + 'WUiOiJMZW4iLCJmYW1pbHlfbmFtZSI6IkJveWV0dGUiLCJ1cGRh' + + 'dGVkX2F0IjoxNDQ2MTUzNDAxLCJlbWFpbCI6Imxib3lldHRlQG9' + + 'rdGEuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInZlciI6MS' + + 'wiaXNzIjoiaHR0cHM6Ly9sYm95ZXR0ZS50cmV4Y2xvdWQuY29tI' + + 'iwibG9naW4iOiJhZG1pbkBva3RhLmNvbSIsImF1ZCI6Ik5QU2ZP' + + 'a0g1ZVpyVHk4UE1EbHZ4IiwiaWF0IjoxNDQ5Njk2MzMwLCJleHA' + + 'iOjE0NDk2OTk5MzAsImFtciI6WyJrYmEiLCJtZmEiLCJwd2QiXS' + + 'wianRpIjoiVFJaVDdSQ2lTeW1UczVXN1J5aDMiLCJhdXRoX3Rpb' + + 'WUiOjE0NDk2OTYzMzB9.YWCNE3ZvT-8ceKnAbTkmSxYE-jIPpfh' + + '2s8f_hTagUUxrfdKgyWzBb9iN3GOPaQ2K6jqOFx90RI2GBzAWec' + + 'pel3sAxG-wvLqiy0d8g0CUb7XTHdhXOLRrXvlpbULxdNnMbBcc6' + + 'uOLDalBjrumOiDMLzti-Bx6uQQ0EjUwuC-Dhv7I3wMsVxyEKejv' + + 'jMLbfWJ6iu4-UUx1r8_ZZUjDDXSB3OFXJQ3nPwRVFXZuRNhGScL' + + 'nftXz7mypRGxrapIQusym1K8hk9uy8_KYL2H2QNbyIqK9Vh9JhY' + + '1rtkQNpv3ZerCUXEVGRiEXDqR_OHu4vUi1-FkONZZe2ov8dQ1mX' + + 'iHHdw'; + tokens.standardIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi' + 'IwMHUxcGNsYTVxWUlSRURMV0NRViIsIm5hbWUiOiJTYW1sI' + 'EphY2tzb24iLCJnaXZlbl9uYW1lIjoiU2FtbCIsImZhbWls' + @@ -135,6 +154,13 @@ define(function() { 'JOEhR6vs11qVmIgbwZ4--MqUIRU3WoFEsr0muLl039QrUa1' + 'EQ9-Ua9rPOMaO0pFC6h2lfB_HfzGifXATKsN-wLdxk6cgA'; + tokens.standardAccessTokenParsed = { + accessToken: tokens.standardAccessToken, + expiresAt: 1449703529, // assuming time = 1449699929 + scopes: ['openid', 'email'], + tokenType: 'Bearer' + }; + /* { 'sub': '00u1pcla5qYIREDLWCQV',