@okta/okta-auth-js
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -21,2 +21,4 @@ /*! | ||
var AuthSdkError = require('./errors/AuthSdkError'); | ||
var config = require('./config'); | ||
var TokenManager = require('./TokenManager'); | ||
@@ -45,2 +47,15 @@ function OktaAuthBuilder(args) { | ||
// Digital clocks will drift over time, so the server | ||
// can misalign with the time reported by the browser. | ||
// The maxClockSkew allows relaxing the time-based | ||
// validation of tokens (in seconds, not milliseconds). | ||
// It currently defaults to 300, because 5 min is the | ||
// default maximum tolerance allowed by Kerberos. | ||
// (https://technet.microsoft.com/en-us/library/cc976357.aspx) | ||
if (!args.maxClockSkew && args.maxClockSkew !== 0) { | ||
this.options.maxClockSkew = config.DEFAULT_MAX_CLOCK_SKEW; | ||
} else { | ||
this.options.maxClockSkew = args.maxClockSkew; | ||
} | ||
// Remove trailing forward slash from url | ||
@@ -71,3 +86,3 @@ if (this.options.url.slice(-1) === '/') { | ||
sdk.idToken = { | ||
authorize: util.bind(token.getIdToken, sdk, sdk), // deprecated for sessionToken | ||
authorize: util.bind(token.getToken, sdk, sdk), // deprecated for sessionToken and idp flows | ||
verify: util.bind(token.verifyIdToken, sdk, sdk), | ||
@@ -85,4 +100,19 @@ refresh: util.bind(token.refreshIdToken, sdk, sdk), | ||
getWithoutPrompt: util.bind(token.getWithoutPrompt, sdk, sdk), | ||
getWithPopup: util.bind(token.getWithPopup, sdk, sdk), | ||
getWithRedirect: util.bind(token.getWithRedirect, sdk, sdk), | ||
parseFromUrl: util.bind(token.parseFromUrl, sdk, sdk), | ||
decode: util.bind(token.decodeToken, sdk) | ||
}; | ||
// This is exposed so we can set window.location in our tests | ||
sdk.token.getWithRedirect._setLocation = function(url) { | ||
window.location = url; | ||
}; | ||
// This is exposed so we can mock window.location.hash in our tests | ||
sdk.token.parseFromUrl._getLocationHash = function(url) { | ||
return window.location.hash; | ||
}; | ||
sdk.tokenManager = new TokenManager(sdk, args.tokenManager); | ||
} | ||
@@ -89,0 +119,0 @@ |
@@ -10,3 +10,3 @@ var util = require('./util'); | ||
var cookieText = name + '=' + value + ';' + expiresText; | ||
document.cookie = cookieText; | ||
setCookie._setDocumentCookie(cookieText); | ||
@@ -16,5 +16,10 @@ return cookieText; | ||
// Exposed for testing | ||
setCookie._setDocumentCookie = function(cookieText) { | ||
document.cookie = cookieText; | ||
}; | ||
function getCookie(name) { | ||
var pattern = new RegExp(name + '=([^;]*)'), | ||
matched = document.cookie.match(pattern); | ||
matched = getCookie._getDocumentCookie().match(pattern); | ||
@@ -27,2 +32,7 @@ if (matched) { | ||
// Exposed for testing | ||
getCookie._getDocumentCookie = function() { | ||
return document.cookie; | ||
}; | ||
function deleteCookie(name) { | ||
@@ -29,0 +39,0 @@ setCookie(name, '', '1970-01-01T00:00:00Z'); |
@@ -6,3 +6,3 @@ /* eslint-disable complexity */ | ||
var AuthApiError = require('./errors/AuthApiError'); | ||
var config = require('./config.json'); | ||
var config = require('./config'); | ||
@@ -13,3 +13,3 @@ function httpRequest(sdk, url, method, args, dontSaveResponse) { | ||
'Content-Type': 'application/json', | ||
'X-Okta-SDK': 'okta-auth-js-' + config.SDK_VERSION | ||
'X-Okta-User-Agent-Extended': 'okta-auth-js-' + config.SDK_VERSION | ||
}; | ||
@@ -16,0 +16,0 @@ util.extend(headers, sdk.options.headers || {}); |
326
lib/token.js
@@ -8,3 +8,4 @@ /* eslint-disable complexity, max-statements */ | ||
var OAuthError = require('./errors/OAuthError'); | ||
var config = require('./config.json'); | ||
var config = require('./config'); | ||
var cookies = require('./cookies'); | ||
@@ -15,3 +16,5 @@ function getWellKnown(sdk) { | ||
function validateClaims(claims, iss, aud) { | ||
function validateClaims(sdk, claims, aud) { | ||
var iss = sdk.options.url; | ||
if (!claims || !iss || !aud) { | ||
@@ -37,7 +40,7 @@ throw new AuthSdkError('The jwt, iss, and aud arguments are all required'); | ||
if (now > claims.exp) { | ||
if ((now - sdk.options.maxClockSkew) > claims.exp) { | ||
throw new AuthSdkError('The JWT expired and is no longer valid'); | ||
} | ||
if (claims.iat > now) { | ||
if (claims.iat > (now + sdk.options.maxClockSkew)) { | ||
throw new AuthSdkError('The JWT was issued in the future'); | ||
@@ -134,3 +137,3 @@ } | ||
opts.prompt = 'none'; | ||
return getIdToken(sdk, opts); | ||
return getToken(sdk, opts); | ||
} | ||
@@ -197,5 +200,3 @@ | ||
function addFragmentListener(sdk, windowEl, timeout) { | ||
var deferred = Q.defer(); | ||
function hashToObject(hash) { | ||
// Predefine regexs for parsing hash | ||
@@ -205,27 +206,29 @@ var plus2space = /\+/g; | ||
function hashToObject(hash) { | ||
// Remove the leading hash | ||
var fragment = hash.substring(1); | ||
// Remove the leading hash | ||
var fragment = hash.substring(1); | ||
var obj = {}; | ||
var obj = {}; | ||
// Loop until we have no more params | ||
var param; | ||
while (true) { // eslint-disable-line no-constant-condition | ||
param = paramSplit.exec(fragment); | ||
if (!param) { break; } | ||
// Loop until we have no more params | ||
var param; | ||
while (true) { // eslint-disable-line no-constant-condition | ||
param = paramSplit.exec(fragment); | ||
if (!param) { break; } | ||
var key = param[1]; | ||
var value = param[2]; | ||
var key = param[1]; | ||
var value = param[2]; | ||
// id_token should remain base64url encoded | ||
if (key === 'id_token') { | ||
obj[key] = value; | ||
} else { | ||
obj[key] = decodeURIComponent(value.replace(plus2space, ' ')); | ||
} | ||
// id_token should remain base64url encoded | ||
if (key === 'id_token' || key === 'access_token') { | ||
obj[key] = value; | ||
} else { | ||
obj[key] = decodeURIComponent(value.replace(plus2space, ' ')); | ||
} | ||
return obj; | ||
} | ||
return obj; | ||
} | ||
function addFragmentListener(sdk, windowEl, timeout) { | ||
var deferred = Q.defer(); | ||
function hashChangeHandler() { | ||
@@ -257,2 +260,136 @@ /* | ||
function handleOAuthResponse(sdk, oauthParams, res) { | ||
if (res['error'] || res['error_description']) { | ||
throw new OAuthError(res['error'], res['error_description']); | ||
} | ||
if (res.state !== oauthParams.state) { | ||
throw new AuthSdkError('OAuth flow response state doesn\'t match request state'); | ||
} | ||
var tokenTypes = oauthParams.responseType; | ||
var scopes = util.clone(oauthParams.scopes); | ||
var tokenDict = {}; | ||
if (res['id_token']) { | ||
var jwt = sdk.idToken.decode(res['id_token']); | ||
if (jwt.payload.nonce !== oauthParams.nonce) { | ||
throw new AuthSdkError('OAuth flow response nonce doesn\'t match request nonce'); | ||
} | ||
var clientId = oauthParams.clientId || sdk.options.clientId; | ||
validateClaims(sdk, jwt.payload, clientId); | ||
var idToken = { | ||
idToken: res['id_token'], | ||
claims: jwt.payload, | ||
expiresAt: jwt.payload.exp, | ||
scopes: scopes | ||
}; | ||
if (Array.isArray(tokenTypes)) { | ||
tokenDict['id_token'] = idToken; | ||
} else { | ||
return idToken; | ||
} | ||
} | ||
if (res['access_token']) { | ||
var accessToken = { | ||
accessToken: res['access_token'], | ||
expiresAt: Number(res['expires_in']) + Math.floor(Date.now()/1000), | ||
tokenType: res['token_type'], | ||
scopes: scopes | ||
}; | ||
if (Array.isArray(tokenTypes)) { | ||
tokenDict['token'] = accessToken; | ||
} else { | ||
return accessToken; | ||
} | ||
} | ||
if (!tokenDict['token'] && !tokenDict['id_token']) { | ||
throw new AuthSdkError('Unable to parse OAuth flow response'); | ||
} | ||
var tokens = []; | ||
// Create token array in the order of the responseType array | ||
for (var t = 0, tl = tokenTypes.length; t < tl; t++) { | ||
var tokenType = tokenTypes[t]; | ||
if (tokenDict[tokenType]) { | ||
tokens.push(tokenDict[tokenType]); | ||
} | ||
} | ||
return tokens; | ||
} | ||
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, | ||
responseType: 'id_token', | ||
responseMode: 'okta_post_message', | ||
state: util.genRandomString(64), | ||
nonce: util.genRandomString(64), | ||
scopes: ['openid', 'email'] | ||
}; | ||
util.extend(defaults, oauthOptions); | ||
return defaults; | ||
} | ||
function convertOAuthParamsToQueryParams(oauthParams) { | ||
// Quick validation | ||
if (!oauthParams.clientId) { | ||
throw new AuthSdkError('A clientId must be specified in the OktaAuth constructor to get a token'); | ||
} | ||
if (util.isString(oauthParams.responseType) && oauthParams.responseType.indexOf(' ') !== -1) { | ||
throw new AuthSdkError('Multiple OAuth responseTypes must be defined as an array'); | ||
} | ||
// Convert our params to their actual OAuth equivalents | ||
var oauthQueryParams = util.removeNils({ | ||
'client_id': oauthParams.clientId, | ||
'redirect_uri': oauthParams.redirectUri, | ||
'response_type': oauthParams.responseType, | ||
'response_mode': oauthParams.responseMode, | ||
'state': oauthParams.state, | ||
'nonce': oauthParams.nonce, | ||
'prompt': oauthParams.prompt, | ||
'display': oauthParams.display, | ||
'sessionToken': oauthParams.sessionToken, | ||
'idp': oauthParams.idp, | ||
'max_age': oauthParams.maxAge | ||
}); | ||
if (Array.isArray(oauthQueryParams['response_type'])) { | ||
oauthQueryParams['response_type'] = oauthQueryParams['response_type'].join(' '); | ||
} | ||
if (oauthParams.responseType.indexOf('id_token') !== -1 && | ||
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.scopes.join(' '); | ||
} | ||
return oauthQueryParams; | ||
} | ||
function buildAuthorizeUrl(sdk, oauthParams) { | ||
var oauthQueryParams = convertOAuthParamsToQueryParams(oauthParams); | ||
return sdk.options.url + '/oauth2/v1/authorize' + util.toQueryParams(oauthQueryParams); | ||
} | ||
/* | ||
@@ -266,3 +403,3 @@ * Retrieve an idToken from an Okta or a third party idp | ||
* Required: | ||
* clientId: passed via the OktaAuth constructor or into getIdToken | ||
* clientId: passed via the OktaAuth constructor or into getToken | ||
* sessionToken: 'yourtoken' | ||
@@ -282,3 +419,3 @@ * | ||
* Required: | ||
* clientId: passed via the OktaAuth constructor or into getIdToken | ||
* clientId: passed via the OktaAuth constructor or into getToken | ||
* | ||
@@ -310,3 +447,3 @@ * Optional: | ||
*/ | ||
function getIdToken(sdk, oauthOptions, options) { | ||
function getToken(sdk, oauthOptions, options) { | ||
if (!oauthOptions) { | ||
@@ -321,15 +458,4 @@ oauthOptions = {}; | ||
// Default OAuth query params | ||
var oauthParams = { | ||
clientId: sdk.options.clientId, | ||
redirectUri: sdk.options.redirectUri || window.location.href, | ||
responseType: 'id_token', | ||
responseMode: 'okta_post_message', | ||
state: util.genRandomString(64), | ||
nonce: util.genRandomString(64), | ||
scope: ['openid', 'email'] | ||
}; | ||
var oauthParams = getDefaultOAuthParams(sdk, oauthOptions); | ||
// Add user-provided options | ||
util.extend(oauthParams, oauthOptions); | ||
// Start overriding any options that don't make sense | ||
@@ -352,29 +478,4 @@ var sessionTokenOverrides = { | ||
// Quick validation | ||
if (!oauthParams.clientId) { | ||
throw new AuthSdkError('A clientId must be specified in the OktaAuth constructor to get an idToken'); | ||
} | ||
// Convert our params to their actual OAuth equivalents | ||
var oauthQueryHash = util.removeNils({ | ||
'client_id': oauthParams.clientId, | ||
'redirect_uri': oauthParams.redirectUri, | ||
'response_type': oauthParams.responseType, | ||
'response_mode': oauthParams.responseMode, | ||
'state': oauthParams.state, | ||
'nonce': oauthParams.nonce, | ||
'prompt': oauthParams.prompt, | ||
'display': oauthParams.display, | ||
'sessionToken': oauthParams.sessionToken, | ||
'idp': oauthParams.idp | ||
}); | ||
if (oauthParams.scope.indexOf('openid') !== -1) { | ||
oauthQueryHash.scope = oauthParams.scope.join(' '); | ||
} else { | ||
throw new AuthSdkError('openid scope must be specified in the scope argument'); | ||
} | ||
// Use the query params to build the authorize url | ||
var requestUrl = sdk.options.url + '/oauth2/v1/authorize' + util.toQueryParams(oauthQueryHash); | ||
var requestUrl = buildAuthorizeUrl(sdk, oauthParams); | ||
@@ -391,27 +492,2 @@ // Determine the flow type | ||
function handleOAuthResponse(res) { | ||
if (res['error'] || res['error_description']) { | ||
throw new OAuthError(res['error'], res['error_description']); | ||
} else if (res['id_token']) { | ||
if (res.state !== oauthParams.state) { | ||
throw new AuthSdkError('OAuth flow response state doesn\'t match request state'); | ||
} | ||
var jwt = sdk.idToken.decode(res['id_token']); | ||
if (jwt.payload.nonce !== oauthParams.nonce) { | ||
throw new AuthSdkError('OAuth flow response nonce doesn\'t match request nonce'); | ||
} | ||
validateClaims(jwt.payload, sdk.options.url, oauthParams.clientId); | ||
return { | ||
idToken: res['id_token'], | ||
claims: jwt.payload | ||
}; | ||
} else { | ||
throw new AuthSdkError('Unable to parse OAuth flow response'); | ||
} | ||
} | ||
function getOrigin(url) { | ||
@@ -428,3 +504,5 @@ var originRegex = /^(https?\:\/\/)?([^:\/?#]*(?:\:[0-9]+)?)/; | ||
return iframePromise | ||
.then(handleOAuthResponse) | ||
.then(function(res) { | ||
return handleOAuthResponse(sdk, oauthParams, res); | ||
}) | ||
.fin(function() { | ||
@@ -486,3 +564,5 @@ if (document.body.contains(iframeEl)) { | ||
return popupDeferred.promise | ||
.then(handleOAuthResponse) | ||
.then(function(res) { | ||
return handleOAuthResponse(sdk, oauthParams, res); | ||
}) | ||
.fin(function() { | ||
@@ -507,8 +587,62 @@ if (!windowEl.closed) { | ||
}); | ||
return getIdToken(sdk, oauthParams, options); | ||
return getToken(sdk, oauthParams, options); | ||
} | ||
function getWithPopup(sdk, oauthOptions, options) { | ||
var oauthParams = util.clone(oauthOptions) || {}; | ||
util.extend(oauthParams, { | ||
display: 'popup' | ||
}); | ||
return getToken(sdk, oauthParams, options); | ||
} | ||
function getWithRedirect(sdk, oauthOptions, options) { | ||
oauthOptions = util.clone(oauthOptions) || {}; | ||
var oauthParams = getDefaultOAuthParams(sdk, oauthOptions); | ||
util.extend(oauthParams, { | ||
responseMode: 'fragment' | ||
}); | ||
var requestUrl = buildAuthorizeUrl(sdk, oauthParams); | ||
// Set session cookie to store the oauthParams | ||
cookies.setCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME, JSON.stringify({ | ||
responseType: oauthParams.responseType, | ||
state: oauthParams.state, | ||
nonce: oauthParams.nonce, | ||
scopes: oauthParams.scopes | ||
})); | ||
sdk.token.getWithRedirect._setLocation(requestUrl); | ||
} | ||
function parseFromUrl(sdk, url) { | ||
var hash = sdk.token.parseFromUrl._getLocationHash(); | ||
if (url) { | ||
hash = url.substring(url.indexOf('#')); | ||
} | ||
var oauthParamsCookie = cookies.getCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME); | ||
if (!hash || !oauthParamsCookie) { | ||
return Q.reject(new AuthSdkError('Unable to parse a token from the url')); | ||
} | ||
try { | ||
var oauthParams = JSON.parse(oauthParamsCookie); | ||
cookies.deleteCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME); | ||
} catch(e) { | ||
return Q.reject(new AuthSdkError('Unable to parse the ' + | ||
config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME + ' cookie: ' + e.message)); | ||
} | ||
return Q.resolve(hashToObject(hash)) | ||
.then(function(res) { | ||
return handleOAuthResponse(sdk, oauthParams, res); | ||
}); | ||
} | ||
module.exports = { | ||
getIdToken: getIdToken, | ||
getToken: getToken, | ||
getWithoutPrompt: getWithoutPrompt, | ||
getWithPopup: getWithPopup, | ||
getWithRedirect: getWithRedirect, | ||
parseFromUrl: parseFromUrl, | ||
refreshIdToken: refreshIdToken, | ||
@@ -515,0 +649,0 @@ decodeToken: decodeToken, |
@@ -7,3 +7,3 @@ /* eslint-disable complexity */ | ||
var AuthPollStopError = require('./errors/AuthPollStopError'); | ||
var config = require('./config.json'); | ||
var config = require('./config'); | ||
@@ -10,0 +10,0 @@ function addStateToken(res, options) { |
@@ -192,2 +192,8 @@ /*! | ||
function deprecate(text) { | ||
/* eslint-disable no-console */ | ||
console.log('[okta-auth-sdk] DEPRECATION: ' + text); | ||
/* eslint-enable */ | ||
} | ||
module.exports = { | ||
@@ -211,3 +217,4 @@ base64UrlToBase64: base64UrlToBase64, | ||
find: find, | ||
getLink: getLink | ||
getLink: getLink, | ||
deprecate: deprecate | ||
}; |
{ | ||
"name": "@okta/okta-auth-js", | ||
"description": "The Okta Auth SDK", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"homepage": "https://github.com/okta/okta-auth-js", | ||
@@ -24,3 +24,2 @@ "license": "Apache-2.0", | ||
"build": "webpack --config webpack.config.js", | ||
"build:AMDJqueryQ": "webpack --config webpack.AMDJqueryQ.config.js", | ||
"build:tests": "webpack --config webpack.test.config.js", | ||
@@ -43,2 +42,3 @@ "package": "grunt", | ||
"reqwest": "2.0.5", | ||
"tiny-emitter": "1.1.0", | ||
"xhr2": "0.1.3" | ||
@@ -66,4 +66,7 @@ }, | ||
"DEFAULT_POLLING_DELAY": 500, | ||
"FRAME_ID": "okta-oauth-helper-frame" | ||
"DEFAULT_MAX_CLOCK_SKEW": 300, | ||
"FRAME_ID": "okta-oauth-helper-frame", | ||
"REDIRECT_OAUTH_PARAMS_COOKIE_NAME": "okta-oauth-redirect-params", | ||
"TOKEN_STORAGE_NAME": "okta-token-storage" | ||
} | ||
} |
@@ -16,5 +16,5 @@ /* globals __dirname */ | ||
var configDest = __dirname + '/lib/config.json'; | ||
var configDest = __dirname + '/lib/config.js'; | ||
console.log('Writing config to', configDest); | ||
fs.writeFileSync(configDest, JSON.stringify(oktaAuthConfig, null, 2)); | ||
fs.writeFileSync(configDest, 'module.exports = ' + JSON.stringify(oktaAuthConfig, null, 2) + ';'); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
91863
30
1795
0
5
+ Addedtiny-emitter@1.1.0
+ Addedtiny-emitter@1.1.0(transitive)