From 81775017587d772f0034d81f5cad66caec0d5439 Mon Sep 17 00:00:00 2001 From: Len Boyette Date: Tue, 20 Dec 2016 15:57:26 -0500 Subject: [PATCH] :bug: Checks for IE11 localStorage (#54) Related-to: OKTA-105205 --- lib/TokenManager.js | 26 ++++++++-------- lib/http.js | 7 ++--- lib/oauthUtil.js | 5 ++-- lib/storageUtil.js | 63 +++++++++++++++++++++++++++++++++++++++ lib/util.js | 7 +++++ test/spec/oauthUtil.js | 62 ++++++++++++++++++++++++++++++++++++++ test/spec/tokenManager.js | 18 +++++++++++ test/util/oauthUtil.js | 13 ++++++++ test/util/util.js | 3 ++ 9 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 lib/storageUtil.js diff --git a/lib/TokenManager.js b/lib/TokenManager.js index e26523b6b..65eea529e 100644 --- a/lib/TokenManager.js +++ b/lib/TokenManager.js @@ -1,19 +1,11 @@ +/* eslint complexity:[0,8] max-statements:[0,21] */ var util = require('./util'); var AuthSdkError = require('./errors/AuthSdkError'); -var cookies = require('./cookies'); -var storageBuilder = require('./storageBuilder'); +var storageUtil = require('./storageUtil'); var Q = require('q'); var Emitter = require('tiny-emitter'); var config = require('./config'); - -// Provides webStorage-like interface for cookies -var cookieStorage = { - getItem: cookies.getCookie, - setItem: function(key, value) { - // Cookie shouldn't expire - cookies.setCookie(key, value, '2038-01-19T03:14:07.000Z'); - } -}; +var storageBuilder = require('./storageBuilder'); function emitExpired(tokenMgmtRef, key, token) { tokenMgmtRef.emitter.emit('expired', key, token); @@ -144,6 +136,16 @@ function TokenManager(sdk, options) { options.autoRefresh = true; } + if (options.storage === 'localStorage' && !storageUtil.browserHasLocalStorage()) { + util.warn('This browser doesn\'t support localStorage. Switching to sessionStorage.'); + options.storage = 'sessionStorage'; + } + + if (options.storage === 'sessionStorage' && !storageUtil.browserHasSessionStorage()) { + util.warn('This browser doesn\'t support sessionStorage. Switching to cookie-based storage.'); + options.storage = 'cookie'; + } + var storage; switch(options.storage) { case 'localStorage': @@ -153,7 +155,7 @@ function TokenManager(sdk, options) { storage = storageBuilder(sessionStorage, config.TOKEN_STORAGE_NAME); break; case 'cookie': - storage = storageBuilder(cookieStorage, config.TOKEN_STORAGE_NAME); + storage = storageBuilder(storageUtil.getCookieStorage(), config.TOKEN_STORAGE_NAME); break; default: throw new AuthSdkError('Unrecognized storage option'); diff --git a/lib/http.js b/lib/http.js index f09c20d1c..f598f47c9 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1,12 +1,10 @@ /* eslint-disable complexity */ var util = require('./util'); var cookies = require('./cookies'); +var storageUtil = require('./storageUtil'); var Q = require('q'); var AuthApiError = require('./errors/AuthApiError'); var config = require('./config'); -var storageBuilder = require('./storageBuilder'); - -var httpCache = storageBuilder(localStorage, config.CACHE_STORAGE_NAME); function httpRequest(sdk, options) { options = options || {}; @@ -14,7 +12,8 @@ function httpRequest(sdk, options) { method = options.method, args = options.args, saveAuthnState = options.saveAuthnState, - accessToken = options.accessToken; + accessToken = options.accessToken, + httpCache = storageUtil.getHttpCache(); if (options.cacheResponse) { var cacheContents = httpCache.getStorage(); diff --git a/lib/oauthUtil.js b/lib/oauthUtil.js index efafb1ff8..0884387f8 100644 --- a/lib/oauthUtil.js +++ b/lib/oauthUtil.js @@ -1,11 +1,10 @@ /* eslint-disable complexity, max-statements */ var http = require('./http'); var util = require('./util'); +var storageUtil = require('./storageUtil'); var AuthSdkError = require('./errors/AuthSdkError'); -var config = require('./config'); -var storageBuilder = require('./storageBuilder'); -var httpCache = storageBuilder(localStorage, config.CACHE_STORAGE_NAME); +var httpCache = storageUtil.getHttpCache(); function isToken(obj) { if (obj && diff --git a/lib/storageUtil.js b/lib/storageUtil.js new file mode 100644 index 000000000..c23986206 --- /dev/null +++ b/lib/storageUtil.js @@ -0,0 +1,63 @@ +var cookies = require('./cookies'); +var storageBuilder = require('./storageBuilder'); +var config = require('./config'); + +// Building this as an object allows us to mock the functions in our tests +var storageUtil = {}; + +// IE11 bug that Microsoft doesn't plan to fix +// https://connect.microsoft.com/IE/Feedback/Details/1496040 +storageUtil.browserHasLocalStorage = function() { + try { + if (storageUtil.getLocalStorage()) { + return true; + } else { + return false; + } + } catch (e) { + return false; + } +}; + +storageUtil.browserHasSessionStorage = function() { + try { + if (storageUtil.getSessionStorage()) { + return true; + } else { + return false; + } + } catch (e) { + return false; + } +}; + +storageUtil.getHttpCache = function() { + if (storageUtil.browserHasLocalStorage()) { + return storageBuilder(storageUtil.getLocalStorage(), config.CACHE_STORAGE_NAME); + } else if (storageUtil.browserHasSessionStorage()) { + return storageBuilder(storageUtil.getSessionStorage(), config.CACHE_STORAGE_NAME); + } else { + return storageBuilder(storageUtil.getCookieStorage(), config.CACHE_STORAGE_NAME); + } +}; + +storageUtil.getLocalStorage = function() { + return localStorage; +}; + +storageUtil.getSessionStorage = function() { + return sessionStorage; +}; + +// Provides webStorage-like interface for cookies +storageUtil.getCookieStorage = function() { + return { + getItem: cookies.getCookie, + setItem: function(key, value) { + // Cookie shouldn't expire + cookies.setCookie(key, value, '2038-01-19T03:14:07.000Z'); + } + }; +}; + +module.exports = storageUtil; diff --git a/lib/util.js b/lib/util.js index 357195df9..6fb63935c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -193,6 +193,12 @@ function getLink(obj, linkName, altName) { } } +function warn(text) { + /* eslint-disable no-console */ + console.log('[okta-auth-sdk] WARN: ' + text); + /* eslint-enable */ +} + function deprecate(text) { /* eslint-disable no-console */ console.log('[okta-auth-sdk] DEPRECATION: ' + text); @@ -236,6 +242,7 @@ module.exports = { omit: omit, find: find, getLink: getLink, + warn: warn, deprecate: deprecate, deprecateWrap: deprecateWrap, removeTrailingSlash: removeTrailingSlash diff --git a/test/spec/oauthUtil.js b/test/spec/oauthUtil.js index 7d6a300e6..cd7c27206 100644 --- a/test/spec/oauthUtil.js +++ b/test/spec/oauthUtil.js @@ -96,6 +96,68 @@ define(function(require) { })); } }); + util.itMakesCorrectRequestResponse({ + title: 'caches response in sessionStorage if localStorage isn\'t available', + setup: { + beforeClient: function() { + oauthUtilHelpers.mockLocalStorageError(); + }, + calls: [ + { + request: { + method: 'get', + uri: '/.well-known/openid-configuration' + }, + response: 'well-known' + } + ], + time: 1449699929 + }, + execute: function(test) { + sessionStorage.clear(); + return oauthUtil.getWellKnown(test.oa); + }, + expectations: function() { + var cache = sessionStorage.getItem('okta-cache-storage'); + expect(cache).toEqual(JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + } + })); + } + }); + util.itMakesCorrectRequestResponse({ + title: 'caches response in cookie if localStorage and sessionStorage are not available', + setup: { + beforeClient: function() { + oauthUtilHelpers.mockLocalStorageError(); + oauthUtilHelpers.mockSessionStorageError(); + }, + calls: [ + { + request: { + method: 'get', + uri: '/.well-known/openid-configuration' + }, + response: 'well-known' + } + ], + time: 1449699929 + }, + execute: function(test) { + test.setCookieMock = util.mockSetCookie(); + return oauthUtil.getWellKnown(test.oa); + }, + expectations: function(test) { + expect(test.setCookieMock).toHaveBeenCalledWith('okta-cache-storage=' + JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + } + }) + '; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT;'); + } + }); }); describe('getKey', function() { diff --git a/test/spec/tokenManager.js b/test/spec/tokenManager.js index b22f987c7..87b278433 100644 --- a/test/spec/tokenManager.js +++ b/test/spec/tokenManager.js @@ -31,6 +31,24 @@ define(function(require) { 'test-idToken': tokens.standardIdTokenParsed }); }); + it('defaults to sessionStorage if localStorage isn\'t available', function() { + oauthUtil.mockLocalStorageError(); + var client = setupSync(); + client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); + oauthUtil.expectTokenStorageToEqual(sessionStorage, { + 'test-idToken': tokens.standardIdTokenParsed + }); + }); + it('defaults to cookie-based storage if localStorage and sessionStorage are not available', function() { + oauthUtil.mockLocalStorageError(); + oauthUtil.mockSessionStorageError(); + var client = setupSync(); + var setCookieMock = util.mockSetCookie(); + client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); + expect(setCookieMock).toHaveBeenCalledWith('okta-token-storage=' + JSON.stringify({ + 'test-idToken': tokens.standardIdTokenParsed + }) + '; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT;'); + }); }); describe('add', function() { diff --git a/test/util/oauthUtil.js b/test/util/oauthUtil.js index c37332823..e938c01e0 100644 --- a/test/util/oauthUtil.js +++ b/test/util/oauthUtil.js @@ -9,6 +9,7 @@ define(function(require) { var wellKnown = require('../xhr/well-known'); var wellKnownSharedResource = require('../xhr/well-known-shared-resource'); var keys = require('../xhr/keys'); + var storageUtil = require('../../lib/storageUtil'); var oauthUtil = {}; @@ -26,6 +27,18 @@ define(function(require) { }); }; + oauthUtil.mockLocalStorageError = function() { + spyOn(storageUtil, 'getLocalStorage').and.callFake(function() { + throw 'This function is not supported on this system.'; + }); + }; + + oauthUtil.mockSessionStorageError = function() { + spyOn(storageUtil, 'getSessionStorage').and.callFake(function() { + throw 'This function is not supported on this system.'; + }); + }; + oauthUtil.loadWellKnownCache = function() { localStorage.setItem('okta-cache-storage', JSON.stringify({ 'https://auth-js-test.okta.com/.well-known/openid-configuration': { diff --git a/test/util/util.js b/test/util/util.js index 20afdd15a..11308f571 100644 --- a/test/util/util.js +++ b/test/util/util.js @@ -170,6 +170,9 @@ define(function(require) { } }) .then(function() { + if (options.beforeClient) { + options.beforeClient(); + } // 2. Setup OktaAuth oa = new OktaAuth({