Skip to content

Commit

Permalink
Added caching to well-known configuration (#48)
Browse files Browse the repository at this point in the history
Resolves: OKTA-100724
  • Loading branch information
lboyette-okta authored Oct 20, 2016
1 parent e39e6cd commit af21c3b
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 93 deletions.
23 changes: 12 additions & 11 deletions lib/TokenManager.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
var util = require('./util');
var AuthSdkError = require('./errors/AuthSdkError');
var cookies = require('./cookies');
var tokenStorageBuilder = require('./tokenStorageBuilder');
var storageBuilder = require('./storageBuilder');
var Q = require('q');
var Emitter = require('tiny-emitter');
var config = require('./config');

// Provides webStorage-like interface for cookies
var cookieStorage = {
Expand Down Expand Up @@ -58,7 +59,7 @@ function setRefreshTimeout(sdk, tokenMgmtRef, storage, key, token) {

function setRefreshTimeoutAll(sdk, tokenMgmtRef, storage) {
try {
var tokenStorage = storage.getTokenStorage();
var tokenStorage = storage.getStorage();
} catch(e) {
// Any errors thrown on instantiation will not be caught,
// because there are no listeners yet
Expand All @@ -76,20 +77,20 @@ function setRefreshTimeoutAll(sdk, tokenMgmtRef, storage) {
}

function add(sdk, tokenMgmtRef, storage, key, token) {
var tokenStorage = storage.getTokenStorage();
var tokenStorage = storage.getStorage();
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);
storage.setStorage(tokenStorage);
setRefreshTimeout(sdk, tokenMgmtRef, storage, key, token);
}

function get(storage, key) {
var tokenStorage = storage.getTokenStorage();
var tokenStorage = storage.getStorage();
return tokenStorage[key];
}

Expand All @@ -98,9 +99,9 @@ function remove(tokenMgmtRef, storage, key) {
clearRefreshTimeout(tokenMgmtRef, key);

// Remove it from storage
var tokenStorage = storage.getTokenStorage();
var tokenStorage = storage.getStorage();
delete tokenStorage[key];
storage.setTokenStorage(tokenStorage);
storage.setStorage(tokenStorage);
}

function refresh(sdk, tokenMgmtRef, storage, key) {
Expand Down Expand Up @@ -133,7 +134,7 @@ function refresh(sdk, tokenMgmtRef, storage, key) {

function clear(tokenMgmtRef, storage) {
clearRefreshTimeoutAll(tokenMgmtRef);
storage.clearTokenStorage();
storage.clearStorage();
}

function TokenManager(sdk, options) {
Expand All @@ -146,13 +147,13 @@ function TokenManager(sdk, options) {
var storage;
switch(options.storage) {
case 'localStorage':
storage = tokenStorageBuilder(localStorage);
storage = storageBuilder(localStorage, config.TOKEN_STORAGE_NAME);
break;
case 'sessionStorage':
storage = tokenStorageBuilder(sessionStorage);
storage = storageBuilder(sessionStorage, config.TOKEN_STORAGE_NAME);
break;
case 'cookie':
storage = tokenStorageBuilder(cookieStorage);
storage = storageBuilder(cookieStorage, config.TOKEN_STORAGE_NAME);
break;
default:
throw new AuthSdkError('Unrecognized storage option');
Expand Down
43 changes: 32 additions & 11 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@ var cookies = require('./cookies');
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 || {};
var url = options.url,
method = options.method,
args = options.args,
dontSaveResponse = options.dontSaveResponse,
saveAuthnState = options.saveAuthnState,
accessToken = options.accessToken;

if (options.cacheResponse) {
var cacheContents = httpCache.getStorage();
var cachedResponse = cacheContents[url];
if (cachedResponse && Date.now()/1000 < cachedResponse.expiresAt) {
return Q.resolve(cachedResponse.response);
}
}

var headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
Expand All @@ -37,7 +48,7 @@ function httpRequest(sdk, options) {
res = JSON.parse(res);
}

if (!dontSaveResponse) {
if (saveAuthnState) {
if (!res.stateToken) {
cookies.deleteCookie(config.STATE_TOKEN_COOKIE_NAME);
}
Expand All @@ -47,6 +58,13 @@ function httpRequest(sdk, options) {
cookies.setCookie(config.STATE_TOKEN_COOKIE_NAME, res.stateToken, res.expiresAt);
}

if (res && options.cacheResponse) {
httpCache.updateStorage(url, {
expiresAt: Math.floor(Date.now()/1000) + config.DEFAULT_CACHE_DURATION,
response: res
});
}

return res;
})
.fail(function(resp) {
Expand Down Expand Up @@ -79,23 +97,26 @@ function httpRequest(sdk, options) {
});
}

function get(sdk, url, saveResponse) {
function get(sdk, url, options) {
url = util.isAbsoluteUrl(url) ? url : sdk.options.url + url;
return httpRequest(sdk, {
var getOptions = {
url: url,
method: 'GET',
dontSaveResponse: !saveResponse
});
method: 'GET'
};
util.extend(getOptions, options);
return httpRequest(sdk, getOptions);
}

function post(sdk, url, args, dontSaveResponse) {
function post(sdk, url, args, options) {
url = util.isAbsoluteUrl(url) ? url : sdk.options.url + url;
return httpRequest(sdk, {
var postOptions = {
url: url,
method: 'POST',
args: args,
dontSaveResponse: dontSaveResponse
});
saveAuthnState: true
};
util.extend(postOptions, options);
return httpRequest(sdk, postOptions);
}

module.exports = {
Expand Down
4 changes: 3 additions & 1 deletion lib/oauthUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ function loadPopup(src, options) {

function getWellKnown(sdk) {
// TODO: Use the issuer when known (usually from the id_token)
return http.get(sdk, sdk.options.url + '/.well-known/openid-configuration');
return http.get(sdk, sdk.options.url + '/.well-known/openid-configuration', {
cacheResponse: true
});
}

function validateClaims(sdk, claims, aud, iss) {
Expand Down
3 changes: 1 addition & 2 deletions lib/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ function getSession(sdk) {
function closeSession(sdk) {
return http.httpRequest(sdk, {
url: sdk.options.url + '/api/v1/sessions/me',
method: 'DELETE',
dontSaveResponse: true
method: 'DELETE'
});
}

Expand Down
42 changes: 42 additions & 0 deletions lib/storageBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
var AuthSdkError = require('./errors/AuthSdkError');

// storage must have getItem and setItem
function storageBuilder(webstorage, storageName) {
function getStorage() {
var storageString = webstorage.getItem(storageName);
storageString = storageString || '{}';
try {
return JSON.parse(storageString);
} catch(e) {
throw new AuthSdkError('Unable to parse storage string: ' + storageName);
}
}

function setStorage(storage) {
try {
var storageString = JSON.stringify(storage);
webstorage.setItem(storageName, storageString);
} catch(e) {
throw new AuthSdkError('Unable to set storage: ' + storageName);
}
}

function clearStorage() {
setStorage({});
}

function updateStorage(key, value) {
var storage = getStorage();
storage[key] = value;
setStorage(storage);
}

return {
getStorage: getStorage,
setStorage: setStorage,
clearStorage: clearStorage,
updateStorage: updateStorage
};
}

module.exports = storageBuilder;
1 change: 0 additions & 1 deletion lib/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,6 @@ function getUserInfo(sdk, accessTokenObject) {
return http.httpRequest(sdk, {
url: accessTokenObject.userinfoUrl,
method: 'GET',
dontSaveResponse: true,
accessToken: accessTokenObject.accessToken
})
.fail(function(err) {
Expand Down
36 changes: 0 additions & 36 deletions lib/tokenStorageBuilder.js

This file was deleted.

4 changes: 3 additions & 1 deletion lib/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ function getPollFn(sdk, res, ref) {
if (rememberDevice) {
href += '?rememberDevice=true';
}
return http.post(sdk, href, getStateToken(res), true, true);
return http.post(sdk, href, getStateToken(res), {
saveAuthnState: false
});
}

ref.isPolling = true;
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@
"STATE_TOKEN_COOKIE_NAME": "oktaStateToken",
"DEFAULT_POLLING_DELAY": 500,
"DEFAULT_MAX_CLOCK_SKEW": 300,
"DEFAULT_CACHE_DURATION": 86400,
"FRAME_ID": "okta-oauth-helper-frame",
"REDIRECT_OAUTH_PARAMS_COOKIE_NAME": "okta-oauth-redirect-params",
"TOKEN_STORAGE_NAME": "okta-token-storage"
"TOKEN_STORAGE_NAME": "okta-token-storage",
"CACHE_STORAGE_NAME": "okta-cache-storage"
}
}
}
Loading

0 comments on commit af21c3b

Please sign in to comment.