From ff1dec7d5b4d3c35778ae438bbd1fce0462ede95 Mon Sep 17 00:00:00 2001 From: Jordan Melberg Date: Tue, 14 Aug 2018 15:53:27 -0700 Subject: [PATCH] 2.0.0 (#130) --- CHANGELOG.md | 33 + README.md | 140 +- lib/TokenManager.js | 141 +- lib/clientBuilder.js | 38 +- lib/cookies.js | 43 +- lib/oauthUtil.js | 6 +- lib/token.js | 137 +- lib/util.js | 10 +- package-lock.json | 3776 +++++++++++++++++++++++++++++++++++++ package.json | 3 +- test/spec/cookies.js | 48 + test/spec/errors.js | 18 +- test/spec/general.js | 228 --- test/spec/oauth.js | 562 ------ test/spec/oauthUtil.js | 54 +- test/spec/token.js | 656 ++++--- test/spec/tokenManager.js | 409 ++-- test/spec/util.js | 32 + test/util/oauthUtil.js | 80 +- test/util/util.js | 18 +- 20 files changed, 4946 insertions(+), 1486 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 package-lock.json create mode 100644 test/spec/cookies.js delete mode 100644 test/spec/oauth.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..71966d1b4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +## 2.0.0 + +### Breaking Changes + +* Token retrieval is now asyncronous to account for automatic token renewal. + + ```javascript + // ES2016+ + const accessToken = await authClient.tokenManager.get('accessToken'); + + // Handle as a promise + authClient.tokenManager.get('accessToken') + .then(function(accessToken) { + console.log(accessToken); + }); + ``` + +* Removed the following deprecated methods: + * `idToken.authorize` + * `idToken.verify` + * `idToken.refresh` + * `idToken.decode` + +## Features + +* Clears whitespace around URLs when instantiating the client. +* Infer the `url` from the `issuer` to simplify client setup. + +## Other + +* Renames all `refresh` methods on the `token` and `tokenManager` objects to `renew`. diff --git a/README.md b/README.md index 177ed7b2d..4575a96df 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,13 @@ You can learn more on the [Okta + JavaScript][lang-landing] page in our document This library uses semantic versioning and follows Okta's [library version policy](https://developer.okta.com/code/library-versions/). -:heavy_check_mark: The current stable major version series is: `1.x` +:heavy_check_mark: The current stable major version series is: `2.x` -| Version | Status | -| ------- | ------------------------- | -| `1.x` | :heavy_check_mark: Stable | -| `0.x` | :x: Retired | +| Version | Status | +| ------- | -------------------------------- | +| `2.x` | :heavy_check_mark: Stable | +| `1.x` | :warning: Retiring on 2019-05-31 | +| `0.x` | :x: Retired | The latest release can always be found on the [releases page][github-releases]. @@ -41,24 +42,12 @@ If you run into problems using the SDK, you can: ## Getting started -Installing the Okta JavaScript SDK in your project is simple. You can include Okta Auth JS in your project either directly from the Okta CDN, or by packaging it with your app via our npm package, [@okta/okta-auth-js](https://www.npmjs.com/package/@okta/okta-auth-js). +Installing the Authentication SDK is simple. You can include it in your project via our npm package, [@okta/okta-auth-js](https://www.npmjs.com/package/@okta/okta-auth-js). You'll also need: * An Okta account, called an _organization_ (sign up for a free [developer organization](https://developer.okta.com/signup) if you need one) -### Using the Okta CDN - -Loading our assets directly from the CDN is a good choice if you want an easy way to get started with okta-auth-js, and don't already have an existing build process that leverages [npm](https://www.npmjs.com/) for external dependencies. - -```html - - -``` - ### Using the npm module Using our npm module is a good choice if: @@ -101,7 +90,17 @@ You can also browse the full [API reference documentation](#api-reference). ## Configuration reference -The only required configuration option is `url`. All others are optional. +If you are using this SDK to implement an OIDC flow, the only required configuration option is `issuer`: + +```javascript +var config = { + issuer: 'https://{yourOktaDomain}/oauth2/default' +}; + +var authClient = new OktaAuth(config); +``` + +If you’re using this SDK only for communicating with the [Authentication API](https://developer.okta.com/docs/api/resources/authn), you instead need to set the `url` for your Okta Domain: ```javascript var config = { @@ -134,11 +133,11 @@ var config = { var authClient = new OktaAuth(config); ``` -The `tokenManager` will **automatically refresh tokens** for you when they expire. To disable this feature, set `autoRefresh` to false. +By default, the `tokenManager` will attempt to renew expired tokens. When an expired token is requested by the `tokenManager.get()` method, a renewal request is executed to update the token. If you wish to manually control token renewal, set `autoRenew` to false to disable this feature. You can listen to [`expired`](#tokenmanageronevent-callback-context) events to know when the token has expired. ```javascript tokenManager: { - autoRefresh: false + autoRenew: false } ``` @@ -151,6 +150,8 @@ tokenManager: { | `redirectUri` | The url that is redirected to when using `token.getWithRedirect`. This must be pre-registered as part of client registration. If no `redirectUri` is provided, defaults to the current origin. | | `authorizeUrl` | Specify a custom authorizeUrl to perform the OIDC flow. Defaults to the issuer plus "/v1/authorize". | | `userinfoUrl` | Specify a custom userinfoUrl. Defaults to the issuer plus "/v1/userinfo". | +| `ignoreSignature` | ID token signatures are validated by default when `token.getWithoutPrompt`, `token.getWithPopup`, `token.getWithRedirect`, and `token.verify` are called. To disable ID token signature validation for these methods, set this value to `true`. | +| | This option should be used only for browser support and testing purposes. | ##### Example Client @@ -238,16 +239,16 @@ var config = { * [token.getWithRedirect](#tokengetwithredirectoptions) * [token.parseFromUrl](#tokenparsefromurloptions) * [token.decode](#tokendecodeidtokenstring) - * [token.refresh](#tokenrefreshtokentorefresh) + * [token.renew](#tokenrenewtokentorenew) * [token.getUserInfo](#tokengetuserinfoaccesstokenobject) * [token.verify](#tokenverifyidtokenobject) -* [tokenManager](#tokenManager) +* [tokenManager](#tokenmanager) * [tokenManager.add](#tokenmanageraddkey-token) * [tokenManager.get](#tokenmanagergetkey) * [tokenManager.remove](#tokenmanagerremovekey) * [tokenManager.clear](#tokenmanagerclear) - * [tokenManager.refresh](#tokenmanagerrefreshkey) - * [tokenManager.on](#tokenmanagerontokenevent-callback-context) + * [tokenManager.renew](#tokenmanagerrenewkey) + * [tokenManager.on](#tokenmanageronevent-callback-context) * [tokenManager.off](#tokenmanageroffevent-callback) ------ @@ -295,9 +296,9 @@ authClient.signOut() Starts a [new password recovery transaction](https://developer.okta.com/docs/api/resources/authn#forgot-password) for a given user and issues a recovery token that can be used to reset a user’s password. - - `username` - User’s non-qualified short-name (e.g. dade.murphy) or unique fully-qualified login (e.g dade.murphy@example.com) - - `factorType` - Recovery factor to use for primary authentication. Supported options are `SMS`, `EMAIL`, or `CALL` - - `relayState` - Optional state value that is persisted for the lifetime of the recovery transaction +* `username` - User’s non-qualified short-name (e.g. dade.murphy) or unique fully-qualified login (e.g dade.murphy@example.com) +* `factorType` - Recovery factor to use for primary authentication. Supported options are `SMS`, `EMAIL`, or `CALL` +* `relayState` - Optional state value that is persisted for the lifetime of the recovery transaction ```javascript authClient.forgotPassword({ @@ -1426,15 +1427,15 @@ Decode a raw ID Token authClient.token.decode('YOUR_ID_TOKEN_JWT'); ``` -#### `token.refresh(tokenToRefresh)` +#### `token.renew(tokenToRenew)` Returns a new token if the Okta [session](https://developer.okta.com/docs/api/resources/sessions#example) is still valid. -* `tokenToRefresh` - an access token or ID token previously provided by Okta. note: this is not the raw JWT +* `tokenToRenew` - an access token or ID token previously provided by Okta. note: this is not the raw JWT ```javascript // this token is provided by Okta via getWithoutPrompt, getWithPopup, and parseFromUrl -var tokenToRefresh = { +var tokenToRenew = { idToken: 'YOUR_ID_TOKEN_JWT', claims: { /* token claims */ }, expiresAt: 1449699930, @@ -1444,7 +1445,7 @@ var tokenToRefresh = { clientId: 'NPSfOkH5eZrTy8PMDlvx' }; -authClient.token.refresh(tokenToRefresh) +authClient.token.renew(tokenToRenew) .then(function(freshToken) { // manage freshToken }) @@ -1471,9 +1472,14 @@ authClient.token.getUserInfo(accessTokenObject) Verify the validity of an ID token's claims and check the signature on browsers that support web cryptography. * `idTokenObject` - an ID token returned by this library. note: this is not the raw ID token JWT +* `validationOptions` - Optional object to assert ID token claim values. Defaults to the configuration passed in during client instantiation. ```javascript -authClient.token.verify(idTokenObject) +var validationOptions = { + issuer: 'https://{yourOktaDomain}/oauth2/{authorizationServerId}' +} + +authClient.token.verify(idTokenObject, validationOptions) .then(function() { // the idToken is valid }) @@ -1486,9 +1492,9 @@ authClient.token.verify(idTokenObject) #### `tokenManager.add(key, token)` -After receiving an `access_token` or `id_token`, add it to the `tokenManager` to manage token expiration and refresh operations. When a token is added to the `tokenManager`, it is automatically refreshed when it expires. +After receiving an `access_token` or `id_token`, add it to the `tokenManager` to manage token expiration and renew operations. When a token is added to the `tokenManager`, it is automatically renewed when it expires. -* `key` - Unique key to store the token in the `tokenManager`. This is used later when you want to get, delete, or refresh the token. +* `key` - Unique key to store the token in the `tokenManager`. This is used later when you want to get, delete, or renew the token. * `token` - Token object that will be added ```javascript @@ -1500,12 +1506,24 @@ authClient.token.getWithPopup() #### `tokenManager.get(key)` -Get a token that you have previously added to the `tokenManager` with the given `key`. +Get a token that you have previously added to the `tokenManager` with the given `key`. The token object will be returned if it has not expired. * `key` - Key for the token you want to get ```javascript -var token = authClient.tokenManager.get('idToken'); +authClient.tokenManager.get('idToken') +.then(function(token) { + if (token) { + // Token is valid + console.log(token); + } else { + // Token has expired + } +}) +.catch(function(err) { + // OAuth Error + console.error(err); +}); ``` #### `tokenManager.remove(key)` @@ -1526,50 +1544,56 @@ Remove all tokens from the `tokenManager`. authClient.tokenManager.clear(); ``` -#### `tokenManager.refresh(key)` +#### `tokenManager.renew(key)` -Manually refresh a token before it expires. +Manually renew a token before it expires. -* `key` - Key for the token you want to refresh +* `key` - Key for the token you want to renew ```javascript -// Because the refresh() method is async, you can wait for it to complete +// Because the renew() method is async, you can wait for it to complete // by using the returned Promise: -authClient.tokenManager.refresh('idToken') +authClient.tokenManager.renew('idToken') .then(function (newToken) { - // doSomethingWith(newToken); + console.log(newToken); }); -// Alternatively, you can subscribe to the 'refreshed' event: -authClient.tokenManager.on('refreshed', function (key, newToken, oldToken) { - // doSomethingWith(newToken); +// Alternatively, you can subscribe to the 'renewed' event: +authClient.tokenManager.on('renewed', function (key, newToken, oldToken) { + console.log(newToken); }); -authClient.tokenManager.refresh('idToken'); +authClient.tokenManager.renew('idToken'); ``` #### `tokenManager.on(event, callback[, context])` Subscribe to an event published by the `tokenManager`. -* `event` - Event to subscribe to. Possible events are `expired`, `error`, and `refreshed`. +* `event` - Event to subscribe to. Possible events are `expired`, `error`, and `renewed`. * `callback` - Function to call when the event is triggered * `context` - Optional context to bind the callback to ```javascript +// Triggered when the token has expired authClient.tokenManager.on('expired', function (key, expiredToken) { console.log('Token with key', key, ' has expired:'); console.log(expiredToken); }); -authClient.tokenManager.on('error', function (err) { - console.log('TokenManager error:', err); -}); - -authClient.tokenManager.on('refreshed', function (key, newToken, oldToken) { - console.log('Token with key', key, 'has been refreshed'); +authClient.tokenManager.on('renewed', function (key, newToken, oldToken) { + console.log('Token with key', key, 'has been renewed'); console.log('Old token:', oldToken); console.log('New token:', newToken); }); + +// Triggered when an OAuthError is returned via the API +authClient.tokenManager.on('error', function (err) { + console.log('TokenManager error:', err.message); + // err.name + // err.message + // err.errorCode + // err.errorSummary +}); ``` #### `tokenManager.off(event[, callback])` @@ -1580,8 +1604,8 @@ Unsubscribe from `tokenManager` events. If no callback is provided, unsubscribes * `callback` - Optional callback that was used to subscribe to the event ```javascript -authClient.tokenManager.off('refreshed'); -authClient.tokenManager.off('refreshed', myRefreshedCallback); +authClient.tokenManager.off('renewed'); +authClient.tokenManager.off('renewed', myRenewedCallback); ``` ## Building the SDK @@ -1614,5 +1638,5 @@ We're happy to accept contributions and PRs! Please see the [contribution guide] [devforum]: https://devforum.okta.com/ [lang-landing]: https://developer.okta.com/code/javascript -[github-issues]: /issues -[github-releases]: /releases \ No newline at end of file +[github-issues]: https://github.com/okta/okta-auth-js/issues +[github-releases]: https://github.com/okta/okta-auth-js/releases diff --git a/lib/TokenManager.js b/lib/TokenManager.js index 0eb016e93..6acedadab 100644 --- a/lib/TokenManager.js +++ b/lib/TokenManager.js @@ -24,51 +24,50 @@ function emitExpired(tokenMgmtRef, key, token) { tokenMgmtRef.emitter.emit('expired', key, token); } -function clearRefreshTimeout(tokenMgmtRef, key) { - clearTimeout(tokenMgmtRef.refreshTimeouts[key]); - delete tokenMgmtRef.refreshTimeouts[key]; +function emitError(tokenMgmtRef, error) { + tokenMgmtRef.emitter.emit('error', error); } -function clearRefreshTimeoutAll(tokenMgmtRef) { - var refreshTimeouts = tokenMgmtRef.refreshTimeouts; - for(var key in refreshTimeouts) { - if (!refreshTimeouts.hasOwnProperty(key)) { +function clearExpireEventTimeout(tokenMgmtRef, key) { + clearTimeout(tokenMgmtRef.expireTimeouts[key]); + delete tokenMgmtRef.expireTimeouts[key]; + + // Remove the renew promise (if it exists) + delete tokenMgmtRef.renewPromise[key]; +} + +function clearExpireEventTimeoutAll(tokenMgmtRef) { + var expireTimeouts = tokenMgmtRef.expireTimeouts; + for (var key in expireTimeouts) { + if (!expireTimeouts.hasOwnProperty(key)) { continue; } - clearRefreshTimeout(tokenMgmtRef, key); + clearExpireEventTimeout(tokenMgmtRef, key); } - tokenMgmtRef.refreshTimeouts = {}; } -function setRefreshTimeout(sdk, tokenMgmtRef, storage, key, token) { - var refreshWait = (token.expiresAt * 1000) - Date.now(); - if (refreshWait < 0) { - // Already expired - refreshWait = 0; - } - var refreshTimeout = setTimeout(function() { - if (tokenMgmtRef.autoRefresh) { - return refresh(sdk, tokenMgmtRef, storage, key); - } else if (token.expiresAt * 1000 <= Date.now()) { - remove(tokenMgmtRef, storage, key); - emitExpired(tokenMgmtRef, key, token); - } - }, refreshWait); +function setExpireEventTimeout(sdk, tokenMgmtRef, key, token) { + var clockSkew = sdk.options.maxClockSkew * 1000; + var expireEventWait = (token.expiresAt * 1000) - (Date.now() - clockSkew); // Clear any existing timeout - clearRefreshTimeout(tokenMgmtRef, key); + clearExpireEventTimeout(tokenMgmtRef, key); + + var expireEventTimeout = setTimeout(function() { + emitExpired(tokenMgmtRef, key, token); + }, expireEventWait); // Add a new timeout - tokenMgmtRef.refreshTimeouts[key] = refreshTimeout; + tokenMgmtRef.expireTimeouts[key] = expireEventTimeout; } -function setRefreshTimeoutAll(sdk, tokenMgmtRef, storage) { +function setExpireEventTimeoutAll(sdk, tokenMgmtRef, storage) { try { var tokenStorage = storage.getStorage(); } catch(e) { // Any errors thrown on instantiation will not be caught, // because there are no listeners yet - tokenMgmtRef.emitter.emit('error', e); + emitError(tokenMgmtRef, e); return; } @@ -77,7 +76,7 @@ function setRefreshTimeoutAll(sdk, tokenMgmtRef, storage) { continue; } var token = tokenStorage[key]; - setRefreshTimeout(sdk, tokenMgmtRef, storage, key, token); + setExpireEventTimeout(sdk, tokenMgmtRef, key, token); } } @@ -91,7 +90,7 @@ function add(sdk, tokenMgmtRef, storage, key, token) { } tokenStorage[key] = token; storage.setStorage(tokenStorage); - setRefreshTimeout(sdk, tokenMgmtRef, storage, key, token); + setExpireEventTimeout(sdk, tokenMgmtRef, key, token); } function get(storage, key) { @@ -99,9 +98,25 @@ function get(storage, key) { return tokenStorage[key]; } +function getAsync(sdk, tokenMgmtRef, storage, key) { + return Q.Promise(function(resolve) { + var token = get(storage, key); + var clockSkew = sdk.options.maxClockSkew * 1000; + if (!token || (token.expiresAt * 1000 - clockSkew) > Date.now()) { + resolve(token); + } + + var tokenPromise = tokenMgmtRef.autoRenew + ? renew(sdk, tokenMgmtRef, storage, key) + : remove(tokenMgmtRef, storage, key); + + resolve(tokenPromise); + }); +} + function remove(tokenMgmtRef, storage, key) { // Clear any listener for this token - clearRefreshTimeout(tokenMgmtRef, key); + clearExpireEventTimeout(tokenMgmtRef, key); // Remove it from storage var tokenStorage = storage.getStorage(); @@ -109,7 +124,7 @@ function remove(tokenMgmtRef, storage, key) { storage.setStorage(tokenStorage); } -function refresh(sdk, tokenMgmtRef, storage, key) { +function renew(sdk, tokenMgmtRef, storage, key) { try { var token = get(storage, key); if (!token) { @@ -119,34 +134,47 @@ function refresh(sdk, tokenMgmtRef, storage, key) { return Q.reject(e); } - // Remove existing autoRefresh timeout for this key - clearRefreshTimeout(tokenMgmtRef, key); - - return sdk.token.refresh(token) - .then(function(freshToken) { - add(sdk, tokenMgmtRef, storage, key, freshToken); - tokenMgmtRef.emitter.emit('refreshed', key, freshToken, token); - return freshToken; - }) - .fail(function(err) { - if (err.name === 'OAuthError') { - remove(tokenMgmtRef, storage, key); - emitExpired(tokenMgmtRef, key, token); - } - throw err; - }); + // Remove existing autoRenew timeout for this key + clearExpireEventTimeout(tokenMgmtRef, key); + + // Store the renew promise state, to avoid renewing again + if (!tokenMgmtRef.renewPromise[key]) { + tokenMgmtRef.renewPromise[key] = sdk.token.renew(token) + .then(function(freshToken) { + if (!get(storage, key)) { + // It is possible to enter a state where the tokens have been cleared + // after a renewal request was triggered. To ensure we do not store a + // renewed token, we verify the promise key doesn't exist and return. + return; + } + add(sdk, tokenMgmtRef, storage, key, freshToken); + tokenMgmtRef.emitter.emit('renewed', key, freshToken, token); + // Remove existing promise key + delete tokenMgmtRef.renewPromise[key]; + return freshToken; + }) + .fail(function(err) { + if (err.name === 'OAuthError') { + remove(tokenMgmtRef, storage, key); + emitError(tokenMgmtRef, err); + } + + throw err; + }); + } + return tokenMgmtRef.renewPromise[key]; } function clear(tokenMgmtRef, storage) { - clearRefreshTimeoutAll(tokenMgmtRef); + clearExpireEventTimeoutAll(tokenMgmtRef); storage.clearStorage(); } function TokenManager(sdk, options) { options = options || {}; options.storage = options.storage || 'localStorage'; - if (!options.autoRefresh && options.autoRefresh !== false) { - options.autoRefresh = true; + if (!options.autoRenew && options.autoRenew !== false) { + options.autoRenew = true; } if (options.storage === 'localStorage' && !storageUtil.browserHasLocalStorage()) { @@ -176,19 +204,20 @@ function TokenManager(sdk, options) { var tokenMgmtRef = { emitter: new Emitter(), - autoRefresh: options.autoRefresh, - refreshTimeouts: {} + autoRenew: options.autoRenew, + expireTimeouts: {}, + renewPromise: {} }; this.add = util.bind(add, this, sdk, tokenMgmtRef, storage); - this.get = util.bind(get, this, storage); + this.get = util.bind(getAsync, this, sdk, tokenMgmtRef, storage); this.remove = util.bind(remove, this, tokenMgmtRef, storage); this.clear = util.bind(clear, this, tokenMgmtRef, storage); - this.refresh = util.bind(refresh, this, sdk, tokenMgmtRef, storage); + this.renew = util.bind(renew, this, sdk, tokenMgmtRef, storage); this.on = util.bind(tokenMgmtRef.emitter.on, tokenMgmtRef.emitter); this.off = util.bind(tokenMgmtRef.emitter.off, tokenMgmtRef.emitter); - - setRefreshTimeoutAll(sdk, tokenMgmtRef, storage); + + setExpireEventTimeoutAll(sdk, tokenMgmtRef, storage); } module.exports = TokenManager; diff --git a/lib/clientBuilder.js b/lib/clientBuilder.js index 0e50c95e2..92fd67c3a 100644 --- a/lib/clientBuilder.js +++ b/lib/clientBuilder.js @@ -34,18 +34,25 @@ function OktaAuthBuilder(args) { 'Required usage: new OktaAuth(args)'); } - if (!args.url) { - throw new AuthSdkError('No url passed to constructor. ' + + var url = args.url; + if (!url) { + var isUrlRegex = new RegExp('^http?s?://.+'); + if (args.issuer && isUrlRegex.test(args.issuer)) { + // Infer the URL from the issuer URL, omitting the /oauth2/{authServerId} + url = args.issuer.split('/oauth2/')[0]; + } else { + throw new AuthSdkError('No url passed to constructor. ' + 'Required usage: new OktaAuth({url: "https://sample.okta.com"})'); + } } - if (args.url.indexOf('-admin.') !== -1) { + if (url.indexOf('-admin.') !== -1) { throw new AuthSdkError('URL passed to constructor contains "-admin" in subdomain. ' + 'Required usage: new OktaAuth({url: "https://dev-12345.okta.com})'); } this.options = { - url: util.removeTrailingSlash(args.url), + url: util.removeTrailingSlash(url), clientId: args.clientId, issuer: util.removeTrailingSlash(args.issuer), authorizeUrl: util.removeTrailingSlash(args.authorizeUrl), @@ -71,6 +78,10 @@ function OktaAuthBuilder(args) { this.options.maxClockSkew = args.maxClockSkew; } + // Give the developer the ability to disable token signature + // validation. + this.options.ignoreSignature = !!args.ignoreSignature; + sdk.session = { close: util.bind(session.closeSession, null, sdk), exists: util.bind(session.sessionExists, null, sdk), @@ -90,18 +101,13 @@ function OktaAuthBuilder(args) { return cookies.getCookie(name); }; - sdk.idToken = { - authorize: util.deprecateWrap('Use token.getWithoutPrompt, token.getWithPopup, or token.getWithRedirect ' + - 'instead of idToken.authorize.', util.bind(token.getToken, null, sdk)), - verify: util.deprecateWrap('Use token.verify instead of idToken.verify', util.bind(token.verifyIdToken, null, sdk)), - refresh: util.deprecateWrap('Use token.refresh instead of idToken.refresh', - util.bind(token.refreshIdToken, null, sdk)), - decode: util.deprecateWrap('Use token.decode instead of idToken.decode', token.decodeToken) - }; - // This is exposed so we can mock window.location.href in our tests - sdk.idToken.authorize._getLocationHref = function() { - return window.location.href; + sdk.idToken = { + authorize: { + _getLocationHref: function() { + return window.location.href; + } + } }; sdk.token = { @@ -110,7 +116,7 @@ function OktaAuthBuilder(args) { getWithRedirect: util.bind(token.getWithRedirect, null, sdk), parseFromUrl: util.bind(token.parseFromUrl, null, sdk), decode: token.decodeToken, - refresh: util.bind(token.refreshToken, null, sdk), + renew: util.bind(token.renewToken, null, sdk), getUserInfo: util.bind(token.getUserInfo, null, sdk), verify: util.bind(token.verifyToken, null, sdk) }; diff --git a/lib/cookies.js b/lib/cookies.js index 38f8b8ec3..2f729274a 100644 --- a/lib/cookies.js +++ b/lib/cookies.js @@ -10,43 +10,32 @@ * See the License for the specific language governing permissions and limitations under the License. * */ - -var util = require('./util'); +var Cookies = require('js-cookie'); function setCookie(name, value, expiresAt) { - var expiresText = ''; - if (expiresAt) { - expiresText = ' expires=' + util.isoToUTCString(expiresAt) + ';'; + var cookieOptions = { + path: '/' + }; + + // eslint-disable-next-line no-extra-boolean-cast + if (!!(Date.parse(expiresAt))) { + // Expires value can be converted to a Date object. + // + // If the 'expiresAt' value is not provided, or the value cannot be + // parsed as a Date object, the cookie will set as a session cookie. + cookieOptions.expires = new Date(expiresAt); } - var cookieText = name + '=' + value + '; path=/;' + expiresText; - setCookie._setDocumentCookie(cookieText); - - return cookieText; + Cookies.set(name, value, cookieOptions); + return getCookie(name); } -// Exposed for testing -setCookie._setDocumentCookie = function(cookieText) { - document.cookie = cookieText; -}; - function getCookie(name) { - var pattern = new RegExp(name + '=([^;]*)'), - matched = getCookie._getDocumentCookie().match(pattern); - - if (matched) { - var cookie = matched[1]; - return cookie; - } + return Cookies.get(name); } -// Exposed for testing -getCookie._getDocumentCookie = function() { - return document.cookie; -}; - function deleteCookie(name) { - setCookie(name, '', '1970-01-01T00:00:00Z'); + return Cookies.remove(name, { path: '/' }); } module.exports = { diff --git a/lib/oauthUtil.js b/lib/oauthUtil.js index 94b9f35bf..426b42e1e 100644 --- a/lib/oauthUtil.js +++ b/lib/oauthUtil.js @@ -114,7 +114,11 @@ function getKey(sdk, issuer, kid) { }); } -function validateClaims(sdk, claims, aud, iss, nonce) { +function validateClaims(sdk, claims, validationParams) { + var aud = validationParams.clientId; + var iss = validationParams.issuer; + var nonce = validationParams.nonce; + if (!claims || !iss || !aud) { throw new AuthSdkError('The jwt, iss, and aud arguments are all required'); } diff --git a/lib/token.js b/lib/token.js index 4c01d4366..ccbe06ee4 100644 --- a/lib/token.js +++ b/lib/token.js @@ -39,73 +39,7 @@ function decodeToken(token) { return decodedToken; } -function verifyIdToken(sdk, idToken, options) { - options = options || {}; - - if (!sdk.features.isTokenVerifySupported()) { - return Q.reject(new AuthSdkError('This browser doesn\'t support crypto.subtle')); - } - - function isExpired(jwtExp) { - var expirationTime; - if (options.expirationTime || options.expirationTime === 0) { - expirationTime = options.expirationTime; - } else { - expirationTime = Math.floor(Date.now()/1000); - } - if (jwtExp && - jwtExp > expirationTime) { - return true; - } - } - - function hasAudience(jwtAudience) { - if (!options.audience) { - return true; - } - var audiences = Array.isArray(options.audience) ? options.audience : [options.audience]; - var jwtAudiences = Array.isArray(jwtAudience) ? jwtAudience : [jwtAudience]; - var ai = audiences.length; - while (ai--) { - var aud = audiences[ai]; - if (jwtAudiences.indexOf(aud) !== -1) { - return true; - } - } - } - - return oauthUtil.getWellKnown(sdk) - .then(function(res) { - return http.get(sdk, res['jwks_uri']); - }) - .then(function(res) { - var key = res.keys[0]; - return sdkCrypto.verifyToken(idToken, key); - }) - .then(function(res) { - if (!res) { - return false; - } - var jwt = sdk.token.decode(idToken); - - if (isExpired(jwt.payload.exp)) { - return false; - } - - if (!hasAudience(jwt.payload.aud)) { - return false; - } - - if (options.issuer && - options.issuer !== jwt.payload.iss) { - return false; - } - - return true; - }); -} - -function verifyToken(sdk, token, nonce, ignoreSignature) { +function verifyToken(sdk, token, validationParams) { return new Q() .then(function() { if (!token || !token.idToken) { @@ -114,12 +48,20 @@ function verifyToken(sdk, token, nonce, ignoreSignature) { var jwt = decodeToken(token.idToken); + var validationOptions = { + clientId: sdk.options.clientId, + issuer: sdk.options.issuer || sdk.options.url, + ignoreSignature: sdk.options.ignoreSignature + }; + + util.extend(validationOptions, validationParams); + // Standard claim validation - oauthUtil.validateClaims(sdk, jwt.payload, token.clientId, token.issuer, nonce); + oauthUtil.validateClaims(sdk, jwt.payload, validationOptions); - // If the browser doesn't support native crypto or we choose not + // If the browser doesn't support native crypto or we choose not // to verify the signature, bail early - if (ignoreSignature || !sdk.features.isTokenVerifySupported()) { + if (validationOptions.ignoreSignature == true || !sdk.features.isTokenVerifySupported()) { return token; } @@ -136,13 +78,6 @@ function verifyToken(sdk, token, nonce, ignoreSignature) { }); } -function refreshIdToken(sdk, options) { - options = options || {}; - options.display = null; - options.prompt = 'none'; - return getToken(sdk, options); -} - function addPostMessageListener(sdk, timeout, state) { var deferred = Q.defer(); @@ -171,7 +106,7 @@ function addFragmentListener(sdk, windowEl, timeout) { We are only able to access window.location.hash on a window that has the same domain. A try/catch is necessary because there's no other way to determine that the popup is in - another domain. When we try to access a window on another + another domain. When we try to access a window on another domain, an error is thrown. */ try { @@ -210,7 +145,7 @@ function handleOAuthResponse(sdk, oauthParams, res, urls) { } var tokenDict = {}; - + if (res['access_token']) { tokenDict['token'] = { accessToken: res['access_token'], @@ -241,7 +176,14 @@ function handleOAuthResponse(sdk, oauthParams, res, urls) { clientId: clientId }; - return verifyToken(sdk, idToken, oauthParams.nonce, true) + var validationParams = { + clientId: clientId, + issuer: urls.issuer, + nonce: oauthParams.nonce, + ignoreSignature: oauthParams.ignoreSignature + }; + + return verifyToken(sdk, idToken, validationParams) .then(function() { tokenDict['id_token'] = idToken; return tokenDict; @@ -269,12 +211,6 @@ function handleOAuthResponse(sdk, oauthParams, res, urls) { 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, @@ -282,7 +218,8 @@ function getDefaultOAuthParams(sdk, oauthOptions) { responseMode: 'okta_post_message', state: util.genRandomString(64), nonce: util.genRandomString(64), - scopes: ['openid', 'email'] + scopes: ['openid', 'email'], + ignoreSignature: sdk.options.ignoreSignature }; util.extend(defaults, oauthOptions); return defaults; @@ -334,11 +271,11 @@ function buildAuthorizeParams(oauthParams) { /* * Retrieve an idToken from an Okta or a third party idp - * + * * Two main flows: * * 1) Exchange a sessionToken for a token - * + * * Required: * clientId: passed via the OktaAuth constructor or into getToken * sessionToken: 'yourtoken' @@ -550,7 +487,7 @@ function getWithRedirect(sdk, oauthOptions, options) { }); } } - + var urls = oauthUtil.getOAuthUrls(sdk, oauthParams, options); var requestUrl = urls.authorizeUrl + buildAuthorizeParams(oauthParams); @@ -560,6 +497,7 @@ function getWithRedirect(sdk, oauthOptions, options) { state: oauthParams.state, nonce: oauthParams.nonce, scopes: oauthParams.scopes, + clientId: oauthParams.clientId, urls: urls })); @@ -572,9 +510,9 @@ function getWithRedirect(sdk, oauthOptions, options) { sdk.token.getWithRedirect._setLocation(requestUrl); } -function refreshToken(sdk, token) { +function renewToken(sdk, token) { if (!oauthUtil.isToken(token)) { - return Q.reject(new AuthSdkError('Refresh must be passed a token with ' + + return Q.reject(new AuthSdkError('Renew must be passed a token with ' + 'an array of scopes and an accessToken or idToken')); } @@ -612,17 +550,22 @@ function parseFromUrl(sdk, url) { hash = url.substring(url.indexOf('#')); } - var oauthParamsCookie = cookies.getCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME); - if (!hash || !oauthParamsCookie) { + if (!hash) { return Q.reject(new AuthSdkError('Unable to parse a token from the url')); } + + var oauthParamsCookie = cookies.getCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME); + if (!oauthParamsCookie) { + return Q.reject(new AuthSdkError('Unable to retrieve OAuth redirect params cookie')); + } + try { var oauthParams = JSON.parse(oauthParamsCookie); var urls = oauthParams.urls; delete oauthParams.urls; cookies.deleteCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME); } catch(e) { - return Q.reject(new AuthSdkError('Unable to parse the ' + + return Q.reject(new AuthSdkError('Unable to parse the ' + config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME + ' cookie: ' + e.message)); } @@ -669,10 +612,8 @@ module.exports = { getWithPopup: getWithPopup, getWithRedirect: getWithRedirect, parseFromUrl: parseFromUrl, - refreshIdToken: refreshIdToken, decodeToken: decodeToken, - verifyIdToken: verifyIdToken, - refreshToken: refreshToken, + renewToken: renewToken, getUserInfo: getUserInfo, verifyToken: verifyToken }; diff --git a/lib/util.js b/lib/util.js index a11dda0d5..a2eb72d54 100644 --- a/lib/util.js +++ b/lib/util.js @@ -185,7 +185,7 @@ util.getLink = function(obj, linkName, altName) { if (!obj || !obj._links) { return; } - + var link = util.clone(obj._links[linkName]); // If a link has a name and we have an altName, return if they match @@ -235,10 +235,12 @@ util.removeTrailingSlash = function(path) { if (!path) { return; } - if (path.slice(-1) === '/') { - return path.slice(0, -1); + // Remove any whitespace before or after string + var trimmed = path.replace(/^\s+|\s+$/gm,''); + if (trimmed.slice(-1) === '/') { + return trimmed.slice(0, -1); } - return path; + return trimmed; }; util.isIE11OrLess = function() { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..0f7b793ba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3776 @@ +{ + "name": "@okta/okta-auth-js", + "version": "2.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "Base64": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.3.0.tgz", + "integrity": "sha1-baJhpOgNT6D1xoQlTlvM0Wu9zp8=" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "0.2.9" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "1.3.0", + "ieee754": "1.1.12", + "isarray": "1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.2.4", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "coffee-script": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", + "dev": true + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "0.1.4" + } + }, + "constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "crypto-browserify": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.2.8.tgz", + "integrity": "sha1-ubEdvm2WUd2IKgHmzEZ99xjs8Yk=", + "dev": true, + "requires": { + "pbkdf2-compat": "2.0.1", + "ripemd160": "0.2.0", + "sha.js": "2.2.6" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.45" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.2.0", + "tapable": "0.1.10" + }, + "dependencies": { + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + } + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "1.0.1" + } + }, + "es5-ext": { + "version": "0.10.45", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", + "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.10.2.tgz", + "integrity": "sha1-sjCUgv7wQ9MgM2WjIShebM4Bw9c=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "debug": "2.6.9", + "doctrine": "1.5.0", + "es6-map": "0.1.5", + "escope": "3.6.0", + "espree": "3.1.4", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "1.3.1", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.10", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.17.2", + "is-resolvable": "1.1.0", + "js-yaml": "3.12.0", + "json-stable-stringify": "1.0.1", + "lodash": "4.10.0", + "mkdirp": "0.5.1", + "optionator": "0.8.2", + "path-is-absolute": "1.0.1", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.6.1", + "strip-json-comments": "1.0.4", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + } + }, + "espree": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.1.4.tgz", + "integrity": "sha1-BybXrIOvl6fISY2ps2OjYJ0qaKE=", + "dev": true, + "requires": { + "acorn": "3.3.0", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45" + } + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.4" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "file-entry-cache": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", + "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.0.0", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "requires": { + "glob": "3.2.11", + "lodash": "2.4.2" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimatch": "0.3.0" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + } + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.19" + } + }, + "formatio": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "dev": true, + "requires": { + "samsam": "1.1.2" + } + }, + "fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "2.4.0", + "klaw": "1.3.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.2.4" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.1.1", + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.2.4" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "0.1.22", + "coffee-script": "1.3.3", + "colors": "0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "0.4.14", + "exit": "0.1.2", + "findup-sync": "0.1.3", + "getobject": "0.1.0", + "glob": "3.1.21", + "grunt-legacy-log": "0.1.3", + "grunt-legacy-util": "0.2.0", + "hooker": "0.2.3", + "iconv-lite": "0.2.11", + "js-yaml": "2.0.5", + "lodash": "0.9.2", + "minimatch": "0.2.14", + "nopt": "1.0.10", + "rimraf": "2.2.8", + "underscore.string": "2.2.1", + "which": "1.0.9" + }, + "dependencies": { + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "1.7.0", + "underscore.string": "2.4.0" + }, + "dependencies": { + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + } + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "0.1.16", + "esprima": "1.0.4" + } + }, + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "grunt-cli": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", + "dev": true, + "requires": { + "findup-sync": "0.3.0", + "grunt-known-options": "1.1.0", + "nopt": "3.0.6", + "resolve": "1.1.7" + }, + "dependencies": { + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "requires": { + "glob": "5.0.15" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.1.1" + } + } + } + }, + "grunt-contrib-jasmine": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-jasmine/-/grunt-contrib-jasmine-1.1.0.tgz", + "integrity": "sha1-9oL3dX2il3Wf4+G0xl1GcMw66kk=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "grunt-lib-phantomjs": "1.1.0", + "jasmine-core": "2.4.1", + "lodash": "2.4.2", + "rimraf": "2.6.2", + "sprintf-js": "1.0.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + } + } + }, + "grunt-known-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", + "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", + "dev": true + }, + "grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "0.6.2", + "grunt-legacy-log-utils": "0.1.1", + "hooker": "0.2.3", + "lodash": "2.4.2", + "underscore.string": "2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "0.6.2", + "lodash": "2.4.2", + "underscore.string": "2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "0.1.22", + "exit": "0.1.2", + "getobject": "0.1.0", + "hooker": "0.2.3", + "lodash": "0.9.2", + "underscore.string": "2.2.1", + "which": "1.0.9" + }, + "dependencies": { + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + } + } + }, + "grunt-lib-phantomjs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-lib-phantomjs/-/grunt-lib-phantomjs-1.1.0.tgz", + "integrity": "sha1-np7c3Z/S3UDgwYHJQ3HVcqpe6tI=", + "dev": true, + "requires": { + "eventemitter2": "0.4.14", + "phantomjs-prebuilt": "2.1.16", + "rimraf": "2.6.2", + "semver": "5.1.0", + "temporary": "0.0.8" + } + }, + "grunt-shell": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-1.3.0.tgz", + "integrity": "sha1-3lYGCpNN+OzuZAdLYcYwSQDXEVg=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "npm-run-path": "1.0.0", + "object-assign": "4.1.1" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", + "dev": true, + "requires": { + "is-stream": "1.1.0", + "pinkie-promise": "2.0.1" + } + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", + "dev": true, + "requires": { + "Base64": "0.2.1", + "inherits": "2.0.3" + }, + "dependencies": { + "Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", + "dev": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" + } + }, + "https-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.0.tgz", + "integrity": "sha1-s//f5zSyo9Sp79WOhlTJH86G6v0=", + "dev": true + }, + "iconv-lite": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.10.0", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "interpret": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz", + "integrity": "sha1-/s16GOfOXKar+5U+H4YhOknxYls=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.11.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jasmine-core": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.4.1.tgz", + "integrity": "sha1-b4OrOg8WlRcizgfSBsdz1XzIOL4=", + "dev": true + }, + "jquery": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.1.4.tgz", + "integrity": "sha1-IoveaYoMYUMdwmMKahVPFYkNIxc=", + "dev": true + }, + "js-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz", + "integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=" + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-loader": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.4.tgz", + "integrity": "sha1-i6oTZaYy9Yo8RtIBdfxgAsluN94=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + }, + "lodash": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.10.0.tgz", + "integrity": "sha1-PY86x6WpBP2wHiz+FAGHm/llLGo=", + "dev": true + }, + "lolex": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.0.tgz", + "integrity": "sha1-Iy3TsN5sZhzm9m4nOxSYhgcNg+8=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", + "dev": true, + "requires": { + "errno": "0.1.7", + "readable-stream": "2.3.6" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mime-db": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "dev": true, + "requires": { + "mime-db": "1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "moment": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.12.0.tgz", + "integrity": "sha1-3CVg0Zg41sBzGxpq+gRnUmTTYNY=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "dev": true, + "optional": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node-libs-browser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.6.0.tgz", + "integrity": "sha1-JEgG1E0xngSLyGB7XMTq+aKdLjw=", + "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.1.4", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "0.0.1", + "crypto-browserify": "3.2.8", + "domain-browser": "1.2.0", + "events": "1.1.1", + "http-browserify": "1.7.0", + "https-browserify": "0.0.0", + "os-browserify": "0.1.2", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "1.1.14", + "stream-browserify": "1.0.0", + "string_decoder": "0.10.31", + "timers-browserify": "1.4.2", + "tty-browserify": "0.0.0", + "url": "0.10.3", + "util": "0.10.4", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "normalize-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.4.1.tgz", + "integrity": "sha1-swB/JZOx+NAVtNWlpijOIr88bxM=", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "prepend-http": "1.0.4", + "query-string": "3.0.3", + "sort-keys": "1.1.2" + } + }, + "npm-run-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", + "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", + "dev": true, + "requires": { + "path-key": "1.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package/-/package-1.0.1.tgz", + "integrity": "sha1-0lofmeJQbcsn1nBLg9yooxLk7cw=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", + "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=", + "dev": true + }, + "pbkdf2-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz", + "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", + "dev": true, + "requires": { + "es6-promise": "4.2.4", + "extract-zip": "1.6.7", + "fs-extra": "1.0.0", + "hasha": "2.2.0", + "kew": "0.7.0", + "progress": "1.1.8", + "request": "2.87.0", + "request-progress": "2.0.1", + "which": "1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + } + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-3.0.3.tgz", + "integrity": "sha1-ri4UtNBQcdTpuetIc8NbDc1C5jg=", + "dev": true, + "requires": { + "strict-uri-encode": "1.1.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randomatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "dev": true, + "requires": { + "is-number": "4.0.0", + "kind-of": "6.0.2", + "math-random": "1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.6", + "set-immediate-shim": "1.0.1" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "dev": true, + "requires": { + "throttleit": "1.0.0" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "reqwest": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/reqwest/-/reqwest-2.0.5.tgz", + "integrity": "sha1-APsVrEkYxBnKgrQ/JMeIguZgOaE=" + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "ripemd160": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz", + "integrity": "sha1-K/GYveFnys+lHAqSjoS2i74XH84=", + "dev": true + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "samsam": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", + "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "dev": true + }, + "semver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "sha.js": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.tgz", + "integrity": "sha1-F93t3F9yL7ZlAWWIlUYZd4ZzFbo=", + "dev": true + }, + "shelljs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", + "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "sinon": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.16.0.tgz", + "integrity": "sha1-GF+FZcQVioTQm93GDdaqQ62VffA=", + "dev": true, + "requires": { + "formatio": "1.1.1", + "lolex": "1.3.0", + "samsam": "1.1.2", + "util": "0.11.0" + } + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "1.1.0" + } + }, + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "dev": true, + "requires": { + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + } + }, + "stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.10.0", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + }, + "temporary": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/temporary/-/temporary-0.0.8.tgz", + "integrity": "sha1-oYqYHSi6jKNgJ/s8MFOMPst0CsA=", + "dev": true, + "requires": { + "package": "1.0.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "0.11.10" + } + }, + "tiny-emitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-1.1.0.tgz", + "integrity": "sha1-q0BaIf/tgUp2wZc5ZICT1wZU/ss=" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.4.tgz", + "integrity": "sha1-ZeovswWck5RpLxX+2HwrNsFrmt8=", + "dev": true, + "requires": { + "async": "0.2.10", + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "util": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.0.tgz", + "integrity": "sha512-5n12uMzKCjvB2HPFHnbQSjaqAa98L5iIXmHrZCLavuZVe0qe/SJGbDGWlpaHk5lnBkWRDO+dRu1/PgmUYKPPTw==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "watchpack": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.9.tgz", + "integrity": "sha1-Yuqkq15bo1/fwBgnVibjwPXj+ws=", + "dev": true, + "requires": { + "async": "0.9.2", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + } + } + }, + "webpack": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.13.0.tgz", + "integrity": "sha1-qtPTTwtyAt1VsXpqbOv0275yJyo=", + "dev": true, + "requires": { + "acorn": "3.3.0", + "async": "1.5.2", + "clone": "1.0.4", + "enhanced-resolve": "0.9.1", + "interpret": "0.6.6", + "loader-utils": "0.2.17", + "memory-fs": "0.3.0", + "mkdirp": "0.5.1", + "node-libs-browser": "0.6.0", + "optimist": "0.6.1", + "supports-color": "3.2.3", + "tapable": "0.1.10", + "uglify-js": "2.6.4", + "watchpack": "0.2.9", + "webpack-core": "0.6.9" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "0.1.8", + "source-map": "0.4.4" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "xhr2": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.3.tgz", + "integrity": "sha1-y/xHWaabSoiOeM9PILBRA4dXvRE=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "1.0.1" + } + } + } +} diff --git a/package.json b/package.json index 2a791bc71..d0152c28b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@okta/okta-auth-js", "description": "The Okta Auth SDK", - "version": "1.18.0", + "version": "2.0.0", "homepage": "https://github.com/okta/okta-auth-js", "license": "Apache-2.0", "main": "lib/index.js", @@ -30,6 +30,7 @@ ], "dependencies": { "Base64": "0.3.0", + "js-cookie": "2.2.0", "q": "1.4.1", "reqwest": "2.0.5", "tiny-emitter": "1.1.0", diff --git a/test/spec/cookies.js b/test/spec/cookies.js new file mode 100644 index 000000000..2486d6e35 --- /dev/null +++ b/test/spec/cookies.js @@ -0,0 +1,48 @@ +var Cookies = require('../../lib/cookies'); +var JsCookie = require('js-cookie'); + +describe('cookie', function () { + beforeEach(function () { + spyOn(JsCookie, 'get'); + spyOn(JsCookie, 'set'); + spyOn(JsCookie, 'remove'); + }); + + describe('setCookie', function () { + it('proxies JsCookie.set', function () { + Cookies.setCookie('foo', 'bar'); + expect(JsCookie.set).toHaveBeenCalledWith('foo', 'bar', { + path: '/' + }); + }); + + it('proxies JsCookie.set with an expiry time', function () { + Cookies.setCookie('foo', 'bar', '2038-01-19T03:14:07.000Z'); + expect(JsCookie.set).toHaveBeenCalledWith('foo', 'bar', { + path: '/', + expires: new Date('2038-01-19T03:14:07.000Z') + }); + }); + + it('proxies JsCookie.set with an invalid expiry time', function () { + Cookies.setCookie('foo', 'bar', 'not a valid date'); + expect(JsCookie.set).toHaveBeenCalledWith('foo', 'bar', { + path: '/' + }); + }); + }); + + describe('getCookie', function () { + it('proxies Cookie.getCookie', function () { + Cookies.getCookie('foo'); + expect(JsCookie.get).toHaveBeenCalledWith('foo'); + }); + }); + + describe('deleteCookie', function () { + it('proxies JsCookie.remove', function () { + Cookies.deleteCookie('foo'); + expect(JsCookie.remove).toHaveBeenCalledWith('foo', { path: '/' }); + }); + }); +}); diff --git a/test/spec/errors.js b/test/spec/errors.js index d3b7cca9a..1eb13b2ec 100644 --- a/test/spec/errors.js +++ b/test/spec/errors.js @@ -34,8 +34,8 @@ define(function(require) { expectations: function (test, err) { var expected = _.cloneDeep(test.responseBody); expected.errorSummary = 'Unknown error'; - - // We explicitly defined the fields to compare, + + // We explicitly defined the fields to compare, // because we don't want to compare all the xhr fields expect(err.xhr.status).toEqual(test.resReply.status); @@ -83,7 +83,7 @@ define(function(require) { expect(err.errorSummary).toEqual('No arguments passed to constructor. Required usage: new OktaAuth(args)'); }); - it('throw an error if no url is passed to the constructor', function () { + it('throw an error if no url and no issuer are passed to the constructor', function () { var err; try { new OktaAuth({}); // eslint-disable-line no-new @@ -95,6 +95,18 @@ define(function(require) { 'Required usage: new OktaAuth({url: "https://sample.okta.com"})'); }); + it('throw an error if issuer is not a url and url is omitted when passed to the constructor', function () { + var err; + try { + new OktaAuth({issuer: 'default'}); // eslint-disable-line no-new + } catch (e) { + err = e; + } + expect(err.name).toEqual('AuthSdkError'); + expect(err.errorSummary).toEqual('No url passed to constructor. ' + + 'Required usage: new OktaAuth({url: "https://sample.okta.com"})'); + }); + it('throw an error if url contains "-admin" when passed to the constructor', function () { var err; try { diff --git a/test/spec/general.js b/test/spec/general.js index e59db2c4f..22b3f55dd 100644 --- a/test/spec/general.js +++ b/test/spec/general.js @@ -1,237 +1,9 @@ define(function(require) { - var OktaAuth = require('OktaAuth'); - var Q = require('q'); var util = require('../util/util'); var _ = require('lodash'); var packageJson = require('../../package.json'); - var tokens = require('../util/tokens'); describe('General Methods', function () { - - describe('idToken.decode', function () { - - function setup() { - return Q.resolve(new OktaAuth({ - url: 'http://example.okta.com' - })); - } - - it('correctly decodes an idToken', function (done) { - return setup() - .then(function (oa) { - var decodedToken = oa.idToken.decode(tokens.unicodeToken); - expect(decodedToken).toDeepEqual(tokens.unicodeDecoded); - done(); - }); - }); - - it('throws an error for a malformed idToken', function (done) { - return setup() - .then(function (oa) { - return oa.idToken.decode('malformedIdToken'); - }) - .then(function (res) { - // Should never hit this - expect(res).toBeNull(); - done(); - }) - .fail(function (err) { - expect(err.name).toEqual('AuthSdkError'); - expect(err.errorSummary).toBeDefined(); - done(); - }); - }); - }); - - // Run if browser supports crypto - var describeType = OktaAuth.prototype.features.isTokenVerifySupported() ? describe : xdescribe; - describeType('idToken.verify', function () { - function setupVerifyIdTokenTest(opts) { - util.itMakesCorrectRequestResponse({ - title: opts.title, - setup: { - calls: [ - { - request: { - method: 'get', - uri: '/.well-known/openid-configuration' - }, - response: 'well-known' - }, - { - request: { - method: 'get', - uri: '/oauth2/v1/keys' - }, - response: 'keys' - } - ] - }, - execute: function (test) { - if (opts.execute) { - return opts.execute(test, opts); - } - - // By default, we want to be in the future because the tokens - // would be expired otherwise - util.warpToDistantFuture(); - return test.oa.idToken.verify(opts.idToken, opts.verifyOpts) - .then(function(res) { - util.returnToPresent(); - return res; - }); - }, - expectations: opts.expectations - }); - } - - setupVerifyIdTokenTest({ - title: 'verifies a valid idToken', - idToken: tokens.standardIdToken, - expectations: function (test, res) { - expect(res).toEqual(true); - } - }); - - setupVerifyIdTokenTest({ - title: 'rejects an invalid idToken', - idToken: 'fyJhbGciOiJSUzI1NiJ9.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', - expectations: function (test, res) { - expect(res).toEqual(false); - } - }); - - setupVerifyIdTokenTest({ - title: 'rejects an invalid idToken due to expiration', - idToken: tokens.standardIdToken, - execute: function(test, opts) { - util.warpToDistantPast(); - return test.oa.idToken.verify(opts.idToken, opts.verifyOpts) - .then(function(res) { - util.returnToPresent(); - return res; - }); - }, - expectations: function (test, res) { - expect(res).toEqual(false); - } - }); - - setupVerifyIdTokenTest({ - title: 'verifies an idToken that would be invalid, except ' + - 'we\'re using the expirationTime option', - idToken: tokens.standardIdToken, - verifyOpts: { - expirationTime: 9999999999 - }, - execute: function(test, opts) { - util.warpToDistantPast(); - return test.oa.idToken.verify(opts.idToken, opts.verifyOpts) - .then(function(res) { - util.returnToPresent(); - return res; - }); - }, - expectations: function (test, res) { - expect(res).toEqual(true); - } - }); - - setupVerifyIdTokenTest({ - title: 'verifies a valid idToken using single audience option', - idToken: tokens.standardIdToken, - verifyOpts: { - audience: 'NPSfOkH5eZrTy8PMDlvx' - }, - expectations: function (test, res) { - expect(res).toEqual(true); - } - }); - - setupVerifyIdTokenTest({ - title: 'rejects an invalid idToken using single audience option', - idToken: tokens.standardIdToken, - verifyOpts: { - audience: 'invalid' - }, - expectations: function (test, res) { - expect(res).toEqual(false); - } - }); - - setupVerifyIdTokenTest({ - title: 'verifies a valid idToken using multiple audience option (all valid)', - idToken: tokens.standardIdToken, - verifyOpts: { - audience: ['NPSfOkH5eZrTy8PMDlvx', 'NPSfOkH5eZrTy8PMDlvx'] - }, - expectations: function (test, res) { - expect(res).toEqual(true); - } - }); - - setupVerifyIdTokenTest({ - title: 'verifies a valid idToken using multiple audience option (valid and invalid)', - idToken: tokens.standardIdToken, - verifyOpts: { - audience: ['NPSfOkH5eZrTy8PMDlvx', 'invalid2'] - }, - expectations: function (test, res) { - expect(res).toEqual(true); - } - }); - - setupVerifyIdTokenTest({ - title: 'rejects an invalid idToken using multiple audience option (all invalid)', - idToken: tokens.standardIdToken, - verifyOpts: { - audience: ['invalid1', 'invalid2'] - }, - expectations: function (test, res) { - expect(res).toEqual(false); - } - }); - - setupVerifyIdTokenTest({ - title: 'verifies a valid idToken using issuer option', - idToken: tokens.standardIdToken, - verifyOpts: { - issuer: 'https://auth-js-test.okta.com' - }, - expectations: function (test, res) { - expect(res).toEqual(true); - } - }); - - setupVerifyIdTokenTest({ - title: 'rejects an invalid idToken using issuer option', - idToken: tokens.standardIdToken, - verifyOpts: { - issuer: 'invalid' - }, - expectations: function (test, res) { - expect(res).toEqual(false); - } - }); - }); - describe('trans.cancel', function () { util.itMakesCorrectRequestResponse({ title: 'returns empty state if called', diff --git a/test/spec/oauth.js b/test/spec/oauth.js deleted file mode 100644 index c9ebed10f..000000000 --- a/test/spec/oauth.js +++ /dev/null @@ -1,562 +0,0 @@ -/* eslint-disable complexity, max-statements */ -define(function(require) { - var oauthUtil = require('../util/oauthUtil'); - var tokens = require('../util/tokens'); - - describe('OAuth Methods', function () { - - describe('idToken.refresh', function () { - it('returns new id_token using old id_token', function (done) { - return oauthUtil.setupFrame({ - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, - refreshArgs: {}, - postMessageSrc: { - baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - queryParams: { - 'client_id': 'NPSfOkH5eZrTy8PMDlvx', - 'redirect_uri': 'https://example.com/redirect', - 'response_type': 'id_token', - 'response_mode': 'okta_post_message', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid email', - 'prompt': 'none' - } - } - }) - .fin(function() { - done(); - }); - }); - }); - - describe('idToken.authorize', function () { - - it('returns id_token using sessionToken', function (done) { - return oauthUtil.setupFrame({ - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testSessionToken' - }, - postMessageSrc: { - baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - queryParams: { - 'client_id': 'NPSfOkH5eZrTy8PMDlvx', - 'redirect_uri': 'https://example.com/redirect', - 'response_type': 'id_token', - 'response_mode': 'okta_post_message', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid email', - 'prompt': 'none', - 'sessionToken': 'testSessionToken' - } - } - }) - .fin(function() { - done(); - }); - }); - - it('returns id_token using idp', function (done) { - return oauthUtil.setupPopup({ - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - idp: 'testIdp' - }, - postMessageSrc: { - baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - queryParams: { - 'client_id': 'NPSfOkH5eZrTy8PMDlvx', - 'redirect_uri': 'https://example.com/redirect', - 'response_type': 'id_token', - 'response_mode': 'okta_post_message', - 'display': 'popup', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid email', - 'idp': 'testIdp' - } - } - }) - .fin(function() { - done(); - }); - }); - - it('returns id_token using iframe', function (done) { - return oauthUtil.setupFrame({ - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - } - }) - .fin(function() { - done(); - }); - }); - - it('returns id_token using popup fragment', function (done) { - return oauthUtil.setupPopup({ - hrefMock: 'https://auth-js-test.okta.com', - changeToHash: '#id_token=' + tokens.standardIdToken + '&state=' + oauthUtil.mockedState, - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://auth-js-test.okta.com/redirect', - idp: 'testIdp', - responseMode: 'fragment' - }, - 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': 'fragment', - 'display': 'popup', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid email', - 'idp': 'testIdp' - } - } - }) - .fin(function() { - done(); - }); - }); - - it('doesn\'t throw an error when redirectUri and clientId are passed via OktaAuth', function (done) { - return oauthUtil.setupFrame({ - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, - authorizeArgs: { - sessionToken: 'testToken' - } - }) - .fin(function() { - done(); - }); - }); - - describe('scope', function () { - it('includes default scope in the uri', function (done) { - return oauthUtil.setupFrame({ - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - }, - postMessageSrc: { - baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - queryParams: { - 'client_id': 'NPSfOkH5eZrTy8PMDlvx', - 'redirect_uri': 'https://example.com/redirect', - 'response_type': 'id_token', - 'response_mode': 'okta_post_message', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid email', - 'prompt': 'none', - 'sessionToken': 'testToken' - } - } - }) - .fin(done); - }); - it('includes specified scope in the uri', function (done) { - return oauthUtil.setupFrame({ - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - scopes: ['openid', 'testscope'], - sessionToken: 'testToken' - }, - postMessageSrc: { - baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - queryParams: { - 'client_id': 'NPSfOkH5eZrTy8PMDlvx', - 'redirect_uri': 'https://example.com/redirect', - 'response_type': 'id_token', - 'response_mode': 'okta_post_message', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid testscope', - 'prompt': 'none', - 'sessionToken': 'testToken' - } - }, - expectedResp: { - idToken: tokens.standardIdToken, - claims: tokens.standardIdTokenClaims, - expiresAt: 1449699930, - scopes: ['openid', 'testscope'] - } - }) - .fin(done); - }); - - it('uses the current href as the redirect_uri when it\'s not provided', function (done) { - return oauthUtil.setupFrame({ - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - sessionToken: 'testToken' - }, - postMessageSrc: { - baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - queryParams: { - 'client_id': 'NPSfOkH5eZrTy8PMDlvx', - 'redirect_uri': window.location.href, - 'response_type': 'id_token', - 'response_mode': 'okta_post_message', - 'state': oauthUtil.mockedState, - 'nonce': oauthUtil.mockedNonce, - 'scope': 'openid email', - 'prompt': 'none', - 'sessionToken': 'testToken' - } - } - }) - .fin(done); - }); - }); - - describe('errors', function() { - oauthUtil.itpErrorsCorrectly('throws an error when openid scope isn\'t specified', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - scopes: ['notopenid'], - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - 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 scopes argument when requesting an id_token', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an error when clientId isn\'t specified', - { - authorizeArgs: { - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - message: 'A clientId must be specified in the OktaAuth constructor to get a token', - errorCode: 'INTERNAL', - errorSummary: 'A clientId must be specified in the OktaAuth constructor to get a token', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an oauth error on issue', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - }, - postMessageResp: { - error: 'sampleErrorCode', - 'error_description': 'something went wrong', - state: oauthUtil.mockedState - } - }, - { - name: 'OAuthError', - message: 'something went wrong', - errorCode: 'sampleErrorCode', - errorSummary: 'something went wrong' - } - ); - oauthUtil.itpErrorsCorrectly('throws an oauth error on issue with a fragment response', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://auth-js-test.okta.com/redirect', - idp: 'testIdp', - responseMode: 'fragment' - }, - hrefMock: 'https://auth-js-test.okta.com', - changeToHash: '#error=invalid_scope&error_description=' + - 'The+requested+scope+is+invalid%2C+unknown%2C+or+malformed.' - }, - { - name: 'OAuthError', - message: 'The requested scope is invalid, unknown, or malformed.', - errorCode: 'invalid_scope', - errorSummary: 'The requested scope is invalid, unknown, or malformed.' - } - ); - oauthUtil.itpErrorsCorrectly('throws an error when window is closed manually with a fragment', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://auth-js-test.okta.com/redirect', - idp: 'testIdp', - responseMode: 'fragment' - }, - hrefMock: 'https://auth-js-test.okta.com', - closePopup: true - }, - { - name: 'AuthSdkError', - message: 'Unable to parse OAuth flow response', - errorCode: 'INTERNAL', - errorSummary: 'Unable to parse OAuth flow response', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an error when window is closed manually with a postMessage', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - idp: 'testIdp' - }, - closePopup: true - }, - { - name: 'AuthSdkError', - message: 'Unable to parse OAuth flow response', - errorCode: 'INTERNAL', - errorSummary: 'Unable to parse OAuth flow response', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - }); - - describe('validation', function() { - oauthUtil.itpErrorsCorrectly('throws an sdk error when state doesn\'t match', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://auth-js-test.okta.com/redirect', - idp: 'testIdp', - responseMode: 'fragment' - }, - hrefMock: 'https://auth-js-test.okta.com', - changeToHash: '#id_token=' + tokens.standardIdToken + '&state=mismatchedState' - }, - { - 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: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an sdk error when nonce doesn\'t match', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken', - nonce: 'mismatchedNonce' - } - }, - { - name: 'AuthSdkError', - message: 'OAuth flow response nonce doesn\'t match request nonce', - errorCode: 'INTERNAL', - errorSummary: 'OAuth flow response nonce doesn\'t match request nonce', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an sdk error when issuer doesn\'t match', - { - oktaAuthArgs: { - url: 'https://different.issuer.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, - authorizeArgs: { - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - message: 'The issuer [https://auth-js-test.okta.com] does not match [https://different.issuer.com]', - errorCode: 'INTERNAL', - errorSummary: 'The issuer [https://auth-js-test.okta.com] does not match [https://different.issuer.com]', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an sdk error when audience doesn\'t match', - { - authorizeArgs: { - clientId: 'differentAudience', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - message: 'The audience [NPSfOkH5eZrTy8PMDlvx] does not match [differentAudience]', - errorCode: 'INTERNAL', - errorSummary: 'The audience [NPSfOkH5eZrTy8PMDlvx] does not match [differentAudience]', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an sdk error when token expired before it was issued', - { - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - }, - postMessageResp: { - 'id_token': tokens.expiredBeforeIssuedIdToken, - state: oauthUtil.mockedState - } - }, - { - name: 'AuthSdkError', - message: 'The JWT expired before it was issued', - errorCode: 'INTERNAL', - errorSummary: 'The JWT expired before it was issued', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - oauthUtil.itpErrorsCorrectly('throws an sdk error when token is expired', - { - time: 9999999999, - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - message: 'The JWT expired and is no longer valid', - errorCode: 'INTERNAL', - errorSummary: 'The JWT expired and is no longer valid', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - it('doesn\'t throw an error when expired if within acceptable clock skew', function(done) { - return oauthUtil.setupFrame({ - time: tokens.standardIdTokenClaims.exp + 499, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - maxClockSkew: 500 - }, - authorizeArgs: { - sessionToken: 'testToken' - } - }) - .fin(done); - }); - oauthUtil.itpErrorsCorrectly('throws an sdk error when token is issued in the future', - { - time: 0, - authorizeArgs: { - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - message: 'The JWT was issued in the future', - errorCode: 'INTERNAL', - errorSummary: 'The JWT was issued in the future', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - it('doesn\'t throw an error when issued in future if within acceptable clock skew', function(done) { - return oauthUtil.setupFrame({ - time: tokens.standardIdTokenClaims.iat - 499, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - maxClockSkew: 500 - }, - authorizeArgs: { - sessionToken: 'testToken' - } - }) - .fin(done); - }); - it('uses a default clock skew', function(done) { - return oauthUtil.setupFrame({ - time: tokens.standardIdTokenClaims.exp + 299, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, - authorizeArgs: { - sessionToken: 'testToken' - } - }) - .fin(done); - }); - oauthUtil.itpErrorsCorrectly('accepts 0 as a clock skew', - { - time: tokens.standardIdTokenClaims.exp + 1, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - maxClockSkew: 0 - }, - authorizeArgs: { - sessionToken: 'testToken' - } - }, - { - name: 'AuthSdkError', - message: 'The JWT expired and is no longer valid', - errorCode: 'INTERNAL', - errorSummary: 'The JWT expired and is no longer valid', - errorLink: 'INTERNAL', - errorId: 'INTERNAL', - errorCauses: [] - } - ); - }); - }); - }); -}); diff --git a/test/spec/oauthUtil.js b/test/spec/oauthUtil.js index e2d0fc7a2..79fe375b0 100644 --- a/test/spec/oauthUtil.js +++ b/test/spec/oauthUtil.js @@ -151,12 +151,16 @@ define(function(require) { 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;'); + expect(test.setCookieMock).toHaveBeenCalledWith( + 'okta-cache-storage', + JSON.stringify({ + 'https://auth-js-test.okta.com/.well-known/openid-configuration': { + expiresAt: 1449786329, + response: wellKnown.response + } + }), + '2038-01-19T03:14:07.000Z' + ); } }); }); @@ -340,7 +344,7 @@ define(function(require) { expect(error.errorCauses).toEqual([]); } } - + it('defaults all urls using global defaults', function() { setupOAuthUrls({ expectedResult: { @@ -555,4 +559,40 @@ define(function(require) { }); + describe('validateClaims', function () { + var sdk = new OktaAuth({ + url: 'https://auth-js-test.okta.com', + clientId: 'foo', + ignoreSignature: false + }); + + var validationOptions = { + clientId: 'foo', + issuer: 'https://auth-js-test.okta.com' + }; + + it('throws an AuthSdkError when no jwt is provided', function () { + var fn = function () { oauthUtil.validateClaims(sdk, undefined, validationOptions); }; + expect(fn).toThrowError('The jwt, iss, and aud arguments are all required'); + }); + + it('throws an AuthSdkError when no clientId is provided', function () { + var fn = function () { + oauthUtil.validateClaims(sdk, undefined, { + issuer: 'https://auth-js-test.okta.com' + }); + }; + expect(fn).toThrowError('The jwt, iss, and aud arguments are all required'); + }); + + it('throws an AuthSdkError when no issuer is provided', function () { + var fn = function () { + oauthUtil.validateClaims(sdk, undefined, { + clientId: 'foo' + }); + }; + expect(fn).toThrowError('The jwt, iss, and aud arguments are all required'); + }); + }); + }); diff --git a/test/spec/token.js b/test/spec/token.js index 7a1188ada..8c5e85c60 100644 --- a/test/spec/token.js +++ b/test/spec/token.js @@ -8,7 +8,7 @@ define(function(require) { var Q = require('q'); function setupSync() { - return new OktaAuth({ url: 'http://example.okta.com' }); + return new OktaAuth({ issuer: 'http://example.okta.com' }); } describe('token.decode', function () { @@ -36,7 +36,7 @@ define(function(require) { it('returns id_token using sessionToken', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -66,10 +66,9 @@ define(function(require) { it('returns id_token using sessionToken with issuer', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithoutPromptArgs: { sessionToken: 'testSessionToken' @@ -138,10 +137,9 @@ define(function(require) { it('allows passing issuer through getWithoutPrompt, which takes precedence', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID' + redirectUri: 'https://example.com/redirect' }, getWithoutPromptArgs: [{ sessionToken: 'testSessionToken' @@ -176,10 +174,9 @@ define(function(require) { it('allows passing issuer as an id through getWithoutPrompt, which takes precedence', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID' + redirectUri: 'https://example.com/redirect' }, getWithoutPromptArgs: [{ sessionToken: 'testSessionToken' @@ -214,7 +211,7 @@ define(function(require) { it('returns id_token overriding all possible oauth params', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -324,7 +321,7 @@ define(function(require) { it('returns access_token using sessionToken', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -363,10 +360,9 @@ define(function(require) { it('returns access_token using sessionToken with authorization server', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithoutPromptArgs: { responseType: 'token', @@ -403,10 +399,9 @@ define(function(require) { it('returns access_token and id_token with an authorization server', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithoutPromptArgs: { responseType: ['id_token', 'token'], @@ -444,7 +439,7 @@ define(function(require) { it('returns id_token and access_token (in that order) using an array of responseTypes', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -484,7 +479,7 @@ define(function(require) { it('returns access_token and id_token (in that order) using an array of responseTypes', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -524,7 +519,7 @@ define(function(require) { it('returns a single token using an array with a single responseType', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -556,7 +551,7 @@ define(function(require) { oauthUtil.itpErrorsCorrectly('throws an error if multiple responseTypes are sent as a string', { oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -581,7 +576,7 @@ define(function(require) { it('returns id_token using idp', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -611,10 +606,9 @@ define(function(require) { it('returns id_token using idp with authorization server', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithPopupArgs: { idp: 'testIdp' @@ -647,10 +641,9 @@ define(function(require) { it('allows passing issuer through getWithPopup, which takes precedence', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID' + redirectUri: 'https://example.com/redirect' }, getWithPopupArgs: [{ idp: 'testIdp' @@ -746,7 +739,7 @@ define(function(require) { it('returns access_token using sessionToken', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -785,10 +778,9 @@ define(function(require) { it('returns access_token using idp with authorization server', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithPopupArgs: { responseType: 'token', @@ -825,10 +817,9 @@ define(function(require) { it('returns access_token and id_token using idp with authorization server', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithPopupArgs: { responseType: ['token', 'id_token'], @@ -866,7 +857,7 @@ define(function(require) { it('returns access_token and id_token (in that order) using idp', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -896,7 +887,7 @@ define(function(require) { 'expires_in': 3600, 'state': oauthUtil.mockedState }, - expectedResp: [tokens.standardAccessTokenParsed, tokens.standardIdTokenParsed] + expectedResp: [tokens.standardAccessTokenParsed, tokens.standardIdTokenParsed] }) .fin(function() { done(); @@ -906,7 +897,7 @@ define(function(require) { it('returns id_token and access_token (in that order) using idp', function (done) { return oauthUtil.setupPopup({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, @@ -951,19 +942,29 @@ define(function(require) { sessionToken: 'testToken' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'id_token', - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'id_token', + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -980,28 +981,37 @@ define(function(require) { it('sets authorize url and cookie for id_token using sessionToken and authorization server', function() { oauthUtil.setupRedirect({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithRedirectArgs: { sessionToken: 'testToken' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'id_token', - state: oauthUtil.mockedState, - nonce: oauthUtil.mockedNonce, - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'id_token', + state: oauthUtil.mockedState, + nonce: oauthUtil.mockedNonce, + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1018,10 +1028,9 @@ define(function(require) { it('allows passing issuer through getWithRedirect, which takes precedence', function() { oauthUtil.setupRedirect({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/ORIGINAL_AUTH_SERVER_ID' + redirectUri: 'https://example.com/redirect' }, getWithRedirectArgs: [{ responseType: 'token', @@ -1031,19 +1040,29 @@ define(function(require) { issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' }], expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'token', - state: oauthUtil.mockedState, - nonce: oauthUtil.mockedNonce, - scopes: ['email'], - urls: { - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'token', + state: oauthUtil.mockedState, + nonce: oauthUtil.mockedNonce, + scopes: ['email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1065,19 +1084,29 @@ define(function(require) { sessionToken: 'testToken' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'token', - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'token', + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1094,10 +1123,9 @@ define(function(require) { it('sets authorize url and cookie for access_token using sessionToken and authorization server', function() { oauthUtil.setupRedirect({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithRedirectArgs: { responseType: 'token', @@ -1105,19 +1133,29 @@ define(function(require) { sessionToken: 'testToken' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'token', - state: oauthUtil.mockedState, - nonce: oauthUtil.mockedNonce, - scopes: ['email'], - urls: { - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'token', + state: oauthUtil.mockedState, + nonce: oauthUtil.mockedNonce, + scopes: ['email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1138,19 +1176,29 @@ define(function(require) { idp: 'testIdp' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: ['token', 'id_token'], - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: ['token', 'id_token'], + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1167,29 +1215,38 @@ define(function(require) { it('sets authorize url for access_token and id_token using idp and authorization server', function() { oauthUtil.setupRedirect({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithRedirectArgs: { responseType: ['token', 'id_token'], idp: 'testIdp' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: ['token', 'id_token'], - state: oauthUtil.mockedState, - nonce: oauthUtil.mockedNonce, - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: ['token', 'id_token'], + state: oauthUtil.mockedState, + nonce: oauthUtil.mockedNonce, + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1210,19 +1267,29 @@ define(function(require) { responseType: 'code' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'code', - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'code', + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1235,33 +1302,41 @@ define(function(require) { 'scope=openid%20email' }); }); - - it('sets authorize url for authorization code requests with an authorization server', function() { + it('sets authorize url for authorization code requests with an authorization server', function() { oauthUtil.setupRedirect({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7' + redirectUri: 'https://example.com/redirect' }, getWithRedirectArgs: { sessionToken: 'testToken', responseType: 'code' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'code', - state: oauthUtil.mockedState, - nonce: oauthUtil.mockedNonce, - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'code', + state: oauthUtil.mockedState, + nonce: oauthUtil.mockedNonce, + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1274,8 +1349,7 @@ define(function(require) { 'scope=openid%20email' }); }); - - it('sets authorize url for authorization code (as an array) requests, ' + + it('sets authorize url for authorization code (as an array) requests, ' + 'defaulting responseMode to query', function() { oauthUtil.setupRedirect({ getWithRedirectArgs: { @@ -1283,19 +1357,29 @@ define(function(require) { responseType: ['code'] }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: ['code'], - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: ['code'], + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1317,19 +1401,29 @@ define(function(require) { responseType: ['code', 'id_token'] }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: ['code', 'id_token'], - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: ['code', 'id_token'], + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1351,19 +1445,29 @@ define(function(require) { responseMode: 'form_post' }, expectedCookies: [ - 'okta-oauth-redirect-params=' + JSON.stringify({ - responseType: 'code', - state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - scopes: ['openid', 'email'], - urls: { - issuer: 'https://auth-js-test.okta.com', - authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', - userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' - } - }) + '; path=/;', - 'okta-oauth-nonce=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;', - 'okta-oauth-state=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; path=/;' + [ + 'okta-oauth-redirect-params', + JSON.stringify({ + responseType: 'code', + state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + scopes: ['openid', 'email'], + clientId: 'NPSfOkH5eZrTy8PMDlvx', + urls: { + issuer: 'https://auth-js-test.okta.com', + authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' + } + }) + ], + [ + 'okta-oauth-nonce', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ], + [ + 'okta-oauth-state', + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + ] ], expectedRedirectUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize?' + 'client_id=NPSfOkH5eZrTy8PMDlvx&' + @@ -1383,7 +1487,7 @@ define(function(require) { return oauthUtil.setupParseUrl({ directUrl: 'http://example.com#id_token=' + tokens.standardIdToken + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: 'id_token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1393,7 +1497,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: { idToken: tokens.standardIdToken, claims: tokens.standardIdTokenClaims, @@ -1411,7 +1515,7 @@ define(function(require) { noHistory: true, hashMock: '#id_token=' + tokens.standardIdToken + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: 'id_token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1421,7 +1525,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: { idToken: tokens.standardIdToken, claims: tokens.standardIdTokenClaims, @@ -1438,7 +1542,7 @@ define(function(require) { return oauthUtil.setupParseUrl({ hashMock: '#id_token=' + tokens.standardIdToken + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: 'id_token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1448,7 +1552,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: { idToken: tokens.standardIdToken, claims: tokens.standardIdTokenClaims, @@ -1465,7 +1569,7 @@ define(function(require) { return oauthUtil.setupParseUrl({ hashMock: '#id_token=' + tokens.authServerIdToken + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: 'id_token', state: oauthUtil.mockedState, nonce: oauthUtil.mockedNonce, @@ -1475,7 +1579,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: tokens.authServerIdTokenParsed }) .fin(function() { @@ -1490,7 +1594,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: 'token', state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1500,7 +1604,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: tokens.standardAccessTokenParsed }) .fin(function() { @@ -1515,7 +1619,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: 'token', state: oauthUtil.mockedState, nonce: oauthUtil.mockedNonce, @@ -1525,7 +1629,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: tokens.authServerAccessTokenParsed }) .fin(function() { @@ -1541,7 +1645,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1551,7 +1655,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: [tokens.standardIdTokenParsed, tokens.standardAccessTokenParsed] }) .fin(function() { @@ -1567,7 +1671,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token'], state: oauthUtil.mockedState, nonce: oauthUtil.mockedNonce, @@ -1577,7 +1681,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: [tokens.authServerIdTokenParsed, tokens.authServerTokenParsed] }) .fin(function() { @@ -1594,7 +1698,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token', 'code'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1604,7 +1708,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: [tokens.standardIdTokenParsed, tokens.standardAccessTokenParsed, { authorizationCode: tokens.standardAuthorizationCode }] @@ -1622,7 +1726,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'code', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1632,7 +1736,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: [tokens.standardIdTokenParsed, undefined, tokens.standardAccessTokenParsed] }) .fin(function() { @@ -1649,7 +1753,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token', 'code'], state: oauthUtil.mockedState, nonce: oauthUtil.mockedNonce, @@ -1659,7 +1763,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/userinfo' } - }) + '; path=/;', + }), expectedResp: [tokens.authServerIdTokenParsed, tokens.authServerAccessTokenParsed, { authorizationCode: tokens.standardAuthorizationCode }] @@ -1673,7 +1777,7 @@ define(function(require) { { setupMethod: oauthUtil.setupParseUrl, hashMock: '', - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1683,7 +1787,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;' + }) }, { name: 'AuthSdkError', @@ -1708,9 +1812,9 @@ define(function(require) { }, { name: 'AuthSdkError', - message: 'Unable to parse a token from the url', + message: 'Unable to retrieve OAuth redirect params cookie', errorCode: 'INTERNAL', - errorSummary: 'Unable to parse a token from the url', + errorSummary: 'Unable to retrieve OAuth redirect params cookie', errorLink: 'INTERNAL', errorId: 'INTERNAL', errorCauses: [] @@ -1725,7 +1829,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token'], state: 'mismatchedState', nonce: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -1735,7 +1839,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;' + }) }, { name: 'AuthSdkError', @@ -1756,7 +1860,7 @@ define(function(require) { '&expires_in=3600' + '&token_type=Bearer' + '&state=' + oauthUtil.mockedState, - oauthCookie: 'okta-oauth-redirect-params=' + JSON.stringify({ + oauthCookie: JSON.stringify({ responseType: ['id_token', 'token'], state: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', nonce: 'mismatchedNonce', @@ -1766,7 +1870,7 @@ define(function(require) { authorizeUrl: 'https://auth-js-test.okta.com/oauth2/v1/authorize', userinfoUrl: 'https://auth-js-test.okta.com/oauth2/v1/userinfo' } - }) + '; path=/;' + }) }, { name: 'AuthSdkError', @@ -1780,15 +1884,15 @@ define(function(require) { ); }); - describe('token.refresh', function () { + describe('token.renew', function () { it('returns id_token', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, - tokenRefreshArgs: [tokens.standardIdTokenParsed], + tokenRenewArgs: [tokens.standardIdTokenParsed], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', queryParams: { @@ -1811,11 +1915,11 @@ define(function(require) { it('returns id_token with authorization server', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, - tokenRefreshArgs: [tokens.authServerIdTokenParsed], + tokenRenewArgs: [tokens.authServerIdTokenParsed], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', queryParams: { @@ -1850,11 +1954,11 @@ define(function(require) { it('returns access_token', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, - tokenRefreshArgs: [tokens.standardAccessTokenParsed], + tokenRenewArgs: [tokens.standardAccessTokenParsed], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', queryParams: { @@ -1891,12 +1995,11 @@ define(function(require) { it('returns access_token with authorization server', function (done) { return oauthUtil.setupFrame({ oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com/oauth2/wontusethisone', clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - issuer: 'https://auth-js-test.okta.com/oauth2/wontusethisone' + redirectUri: 'https://example.com/redirect' }, - tokenRefreshArgs: [tokens.authServerAccessTokenParsed], + tokenRenewArgs: [tokens.authServerAccessTokenParsed], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7/v1/authorize', queryParams: { @@ -1933,17 +2036,17 @@ define(function(require) { oauthUtil.itpErrorsCorrectly('throws an error if a non-token is passed', { oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect' }, - tokenRefreshArgs: [{non:'token'}] + tokenRenewArgs: [{non:'token'}] }, { name: 'AuthSdkError', - message: 'Refresh must be passed a token with an array of scopes and an accessToken or idToken', + message: 'Renew must be passed a token with an array of scopes and an accessToken or idToken', errorCode: 'INTERNAL', - errorSummary: 'Refresh must be passed a token with an array of scopes and an accessToken or idToken', + errorSummary: 'Renew must be passed a token with an array of scopes and an accessToken or idToken', errorLink: 'INTERNAL', errorId: 'INTERNAL', errorCauses: [] @@ -2092,11 +2195,18 @@ define(function(require) { }); describe('token.verify', function() { + var validationParams = { + clientId: tokens.standardIdTokenParsed.clientId, + issuer: tokens.standardIdTokenParsed.issuer + }; + it('verifies a valid idToken with nonce', function(done) { var client = setupSync(); util.warpToUnixTime(1449699929); oauthUtil.loadWellKnownAndKeysCache(); - client.token.verify(tokens.standardIdTokenParsed, oauthUtil.mockedNonce) + var alteredParams = _.clone(validationParams); + alteredParams.nonce = tokens.standardIdTokenParsed.nonce; + client.token.verify(tokens.standardIdTokenParsed, validationParams) .then(function(res) { expect(res).toEqual(tokens.standardIdTokenParsed); }) @@ -2109,7 +2219,7 @@ define(function(require) { var client = setupSync(); util.warpToUnixTime(1449699929); oauthUtil.loadWellKnownAndKeysCache(); - client.token.verify(tokens.standardIdTokenParsed) + client.token.verify(tokens.standardIdTokenParsed, validationParams) .then(function(res) { expect(res).toEqual(tokens.standardIdTokenParsed); }) @@ -2139,37 +2249,39 @@ define(function(require) { }); it('issued in the future', function(done) { util.warpToDistantPast(); - expectError([tokens.standardIdTokenParsed], + expectError([tokens.standardIdTokenParsed, validationParams], 'The JWT was issued in the future') .fin(done); }); it('expired', function(done) { util.warpToDistantFuture(); - expectError([tokens.standardIdTokenParsed], + expectError([tokens.standardIdTokenParsed, validationParams], 'The JWT expired and is no longer valid') .fin(done); }); it('invalid nonce', function(done) { - expectError([tokens.standardIdTokenParsed, 'invalidNonce'], + var alteredParams = _.clone(validationParams); + alteredParams.nonce = 'invalidNonce'; + expectError([tokens.standardIdToken2Parsed, alteredParams], 'OAuth flow response nonce doesn\'t match request nonce') .fin(done); }); it('invalid audience', function(done) { - var idToken = _.clone(tokens.standardIdTokenParsed); - idToken.clientId = 'invalidAudience'; - expectError([idToken], + var alteredParams = _.clone(validationParams); + alteredParams.clientId = 'invalidAudience'; + expectError([tokens.standardIdTokenParsed, alteredParams], 'The audience [NPSfOkH5eZrTy8PMDlvx] does not match [invalidAudience]') .fin(done); }); it('invalid issuer', function(done) { - var idToken = _.clone(tokens.standardIdTokenParsed); - idToken.issuer = 'http://invalidissuer.example.com'; - expectError([idToken], + var alteredParams = _.clone(validationParams); + alteredParams.issuer = 'http://invalidissuer.example.com'; + expectError([tokens.standardIdTokenParsed, alteredParams], 'The issuer [https://auth-js-test.okta.com] does not match [http://invalidissuer.example.com]') .fin(done); }); it('expired before issued', function(done) { - expectError([tokens.expiredBeforeIssuedIdTokenParsed], + expectError([tokens.expiredBeforeIssuedIdTokenParsed, validationParams], 'The JWT expired before it was issued') .fin(done); }); diff --git a/test/spec/tokenManager.js b/test/spec/tokenManager.js index 87b278433..0c1d147e3 100644 --- a/test/spec/tokenManager.js +++ b/test/spec/tokenManager.js @@ -5,14 +5,15 @@ define(function(require) { var oauthUtil = require('../util/oauthUtil'); function setupSync(options) { - options = options || {}; + options = options || { tokenManager: {} }; return new OktaAuth({ - url: 'https://auth-js-test.okta.com', + issuer: 'https://auth-js-test.okta.com', clientId: 'NPSfOkH5eZrTy8PMDlvx', redirectUri: 'https://example.com/redirect', + maxClockSkew: options.maxClockSkew || 1, // set default to 1 second tokenManager: { - storage: options.type, - autoRefresh: options.autoRefresh || false + storage: options.tokenManager.type, + autoRenew: options.tokenManager.autoRenew || false } }); } @@ -32,22 +33,34 @@ define(function(require) { }); }); it('defaults to sessionStorage if localStorage isn\'t available', function() { + spyOn(window.console, 'log'); oauthUtil.mockLocalStorageError(); var client = setupSync(); + expect(window.console.log).toHaveBeenCalledWith( + '[okta-auth-sdk] WARN: This browser doesn\'t ' + + 'support localStorage. Switching to sessionStorage.' + ); 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() { + spyOn(window.console, 'log'); oauthUtil.mockLocalStorageError(); oauthUtil.mockSessionStorageError(); var client = setupSync(); + expect(window.console.log).toHaveBeenCalledWith( + '[okta-auth-sdk] WARN: This browser doesn\'t ' + + 'support sessionStorage. Switching to cookie-based storage.' + ); 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;'); + expect(setCookieMock).toHaveBeenCalledWith( + 'okta-token-storage', + JSON.stringify({'test-idToken': tokens.standardIdTokenParsed}), + '2038-01-19T03:14:07.000Z' + ); }); }); @@ -76,17 +89,10 @@ define(function(require) { }); }); - describe('refresh', function() { - it('allows refreshing an idToken', function(done) { + describe('renew', function() { + it('allows renewing an idToken', function(done) { return oauthUtil.setupFrame({ - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - tokenManager: { - autoRefresh: false - } - }, + authClient: setupSync(), tokenManagerAddKeys: { 'test-idToken': { idToken: 'testInitialToken', @@ -95,7 +101,7 @@ define(function(require) { scopes: ['openid', 'email'] } }, - tokenManagerRefreshArgs: ['test-idToken'], + tokenManagerRenewArgs: ['test-idToken'], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', queryParams: { @@ -123,16 +129,9 @@ define(function(require) { .fin(done); }); - it('allows refreshing an accessToken', function(done) { + it('allows renewing an accessToken', function(done) { return oauthUtil.setupFrame({ - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect', - tokenManager: { - autoRefresh: false - } - }, + authClient: setupSync(), tokenManagerAddKeys: { 'test-accessToken': { accessToken: 'testInitialToken', @@ -141,7 +140,7 @@ define(function(require) { tokenType: 'Bearer' } }, - tokenManagerRefreshArgs: ['test-accessToken'], + tokenManagerRenewArgs: ['test-accessToken'], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', queryParams: { @@ -173,12 +172,8 @@ define(function(require) { oauthUtil.itpErrorsCorrectly('throws an errors when a token doesn\'t exist', { - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, - tokenManagerRefreshArgs: ['test-accessToken'] + authClient: setupSync(), + tokenManagerRenewArgs: ['test-accessToken'] }, { name: 'AuthSdkError', @@ -194,13 +189,9 @@ define(function(require) { it('throws an errors when the token is mangled', function(done) { localStorage.setItem('okta-token-storage', '#unparseableJson#'); return oauthUtil.setupFrame({ + authClient: setupSync(), willFail: true, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, - tokenManagerRefreshArgs: ['test-accessToken'] + tokenManagerRenewArgs: ['test-accessToken'] }) .then(function() { expect(true).toEqual(false); @@ -219,17 +210,13 @@ define(function(require) { .fin(done); }); - oauthUtil.itpErrorsCorrectly('throws an error if there\'s an issue refreshing', + oauthUtil.itpErrorsCorrectly('throws an error if there\'s an issue renewing', { - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, + authClient: setupSync(), tokenManagerAddKeys: { 'test-idToken': tokens.standardIdTokenParsed }, - tokenManagerRefreshArgs: ['test-idToken'], + tokenManagerRenewArgs: ['test-idToken'], postMessageSrc: { baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', queryParams: { @@ -259,19 +246,15 @@ define(function(require) { } ); - it('removes token if an OAuthError is thrown while refreshing', function(done) { + it('removes token if an OAuthError is thrown while renewing', function(done) { return oauthUtil.setupFrame({ + authClient: setupSync(), willFail: true, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' - }, tokenManagerAddKeys: { 'test-accessToken': tokens.standardAccessTokenParsed, 'test-idToken': tokens.standardIdTokenParsed }, - tokenManagerRefreshArgs: ['test-accessToken'], + tokenManagerRenewArgs: ['test-accessToken'], postMessageResp: { error: 'sampleErrorCode', 'error_description': 'something went wrong', @@ -293,7 +276,7 @@ define(function(require) { }); }); - describe('autoRefresh', function() { + describe('autoRenew', function() { beforeEach(function() { jasmine.clock().install(); }); @@ -302,16 +285,66 @@ define(function(require) { jasmine.clock().uninstall(); }); - it('automatically refreshes a token by default', function(done) { + it('automatically renews a token by default', function(done) { var expiresAt = tokens.standardIdTokenParsed.expiresAt; return oauthUtil.setupFrame({ - fastForwardToTime: expiresAt + 1, - autoRefresh: true, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' + authClient: setupSync({ + tokenManager: { + autoRenew: true + } + }), + autoRenew: true, + fastForwardToTime: true, + autoRenewTokenKey: 'test-idToken', + time: expiresAt + 1, + tokenManagerAddKeys: { + 'test-idToken': { + idToken: 'testInitialToken', + claims: {'fake': 'claims'}, + expiresAt: expiresAt, + scopes: ['openid', 'email'] + } }, + postMessageSrc: { + baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + queryParams: { + 'client_id': 'NPSfOkH5eZrTy8PMDlvx', + 'redirect_uri': 'https://example.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 + } + }) + .then(function() { + oauthUtil.expectTokenStorageToEqual(localStorage, { + 'test-idToken': tokens.standardIdTokenParsed + }); + }) + .fin(done); + }); + + it('automatically renews a token early when clock skew is considered', function(done) { + var expiresAt = tokens.standardIdTokenParsed.expiresAt; + return oauthUtil.setupFrame({ + authClient: setupSync({ + // Account for 10 min of clock skew + maxClockSkew: 600, + tokenManager: { + autoRenew: true + } + }), + autoRenew: true, + fastForwardToTime: true, + autoRenewTokenKey: 'test-idToken', + time: expiresAt - 10, tokenManagerAddKeys: { 'test-idToken': { idToken: 'testInitialToken', @@ -346,15 +379,66 @@ define(function(require) { .fin(done); }); - it('removes a token on OAuth failure', function(done) { + it('does not return the token after tokens were cleared before renew promise was resolved', function(done) { + var expiresAt = tokens.standardIdTokenParsed.expiresAt; return oauthUtil.setupFrame({ - fastForwardToTime: tokens.standardIdTokenParsed.expiresAt + 1, - autoRefresh: true, - oktaAuthArgs: { - url: 'https://auth-js-test.okta.com', - clientId: 'NPSfOkH5eZrTy8PMDlvx', - redirectUri: 'https://example.com/redirect' + authClient: setupSync({ + tokenManager: { + autoRenew: true + } + }), + autoRenew: true, + fastForwardToTime: true, + autoRenewTokenKey: 'test-idToken', + time: expiresAt + 1, + tokenManagerAddKeys: { + 'test-idToken': { + idToken: 'testInitialToken', + claims: {'fake': 'claims'}, + expiresAt: expiresAt, + scopes: ['openid', 'email'] + } }, + postMessageSrc: { + baseUri: 'https://auth-js-test.okta.com/oauth2/v1/authorize', + queryParams: { + 'client_id': 'NPSfOkH5eZrTy8PMDlvx', + 'redirect_uri': 'https://example.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 + }, + beforeCompletion: function(authClient) { + // Simulate tokens being cleared while the renew request is performed + authClient.tokenManager.clear(); + } + }) + .then(function() { + oauthUtil.expectTokenStorageToEqual(localStorage, {}); + }) + .fin(done); + }); + + it('removes a token on OAuth failure', function(done) { + return oauthUtil.setupFrame({ + authClient: setupSync({ + tokenManager: { + autoRenew: true + } + }), + autoRenew: true, + willFail: true, + fastForwardToTime: true, + autoRenewTokenKey: 'test-idToken', + time: tokens.standardIdTokenParsed.expiresAt + 1, tokenManagerAddKeys: { 'test-idToken': tokens.standardIdTokenParsed }, @@ -364,30 +448,39 @@ define(function(require) { state: oauthUtil.mockedState } }) - .then(function() { + .fail(function(err) { + util.expectErrorToEqual(err, { + name: 'OAuthError', + message: 'something went wrong', + errorCode: 'sampleErrorCode', + errorSummary: 'something went wrong' + }); oauthUtil.expectTokenStorageToEqual(localStorage, {}); }) .fin(done); }); - it('emits "expired" on existing tokens even when autoRefresh is disabled', function(done) { + it('emits "expired" on existing tokens even when autoRenew is disabled', function(done) { util.warpToUnixTime(tokens.standardIdTokenClaims.iat); localStorage.setItem('okta-token-storage', JSON.stringify({ 'test-idToken': tokens.standardIdTokenParsed })); - var client = setupSync({ autoRefresh: false }); + var client = setupSync({ tokenManager: { autoRenew: false } }); client.tokenManager.on('expired', function(key, token) { expect(key).toEqual('test-idToken'); expect(token).toEqual(tokens.standardIdTokenParsed); - expect(client.tokenManager.get('test-idToken')).toBeUndefined(); - done(); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); }); util.warpByTicksToUnixTime(tokens.standardIdTokenParsed.expiresAt + 1); }); - it('emits "expired" on new tokens even when autoRefresh is disabled', function(done) { + it('emits "expired" on new tokens even when autoRenew is disabled', function(done) { util.warpToUnixTime(tokens.standardIdTokenClaims.iat); - var client = setupSync({ autoRefresh: false }); + var client = setupSync({ tokenManager: { autoRenew: false } }); client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); client.tokenManager.on('expired', function(key, token) { expect(key).toEqual('test-idToken'); @@ -395,6 +488,46 @@ define(function(require) { done(); }); util.warpByTicksToUnixTime(tokens.standardIdTokenParsed.expiresAt + 1); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); + }); + + it('returns undefined for a token that has expired when autoRenew is disabled', function(done) { + util.warpToUnixTime(tokens.standardIdTokenClaims.iat); + localStorage.setItem('okta-token-storage', JSON.stringify({ + 'test-idToken': tokens.standardIdTokenParsed + })); + var client = setupSync({ tokenManager: { autoRenew: false } }); + util.warpByTicksToUnixTime(tokens.standardIdTokenParsed.expiresAt + 1); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); + }); + + it('returns undefined for an active token when autoRenew is disabled, accounting' + + 'for clock skew', function(done) { + util.warpToUnixTime(tokens.standardIdTokenClaims.iat); + localStorage.setItem('okta-token-storage', JSON.stringify({ + 'test-idToken': tokens.standardIdTokenParsed + })); + var client = setupSync({ + // Account for 10 min of clock skew + maxClockSkew: 600, + tokenManager: { + autoRenew: false + } + }); + util.warpByTicksToUnixTime(tokens.standardIdTokenParsed.expiresAt - 5); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); }); }); @@ -402,7 +535,9 @@ define(function(require) { function localStorageSetup() { return setupSync({ - type: 'localStorage' + tokenManager: { + type: 'localStorage' + } }); } @@ -417,13 +552,31 @@ define(function(require) { }); describe('get', function() { - it('gets a token', function() { + it('returns a token', function(done) { + var client = localStorageSetup(); + localStorage.setItem('okta-token-storage', JSON.stringify({ + 'test-idToken': tokens.standardIdTokenParsed + })); + util.warpToUnixTime(tokens.standardIdTokenClaims.iat); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toEqual(tokens.standardIdTokenParsed); + done(); + }); + // Warp back to current time + util.warpToUnixTime(Date.now()); + }); + + it('returns undefined for an expired token', function(done) { var client = localStorageSetup(); localStorage.setItem('okta-token-storage', JSON.stringify({ 'test-idToken': tokens.standardIdTokenParsed })); - var result = client.tokenManager.get('test-idToken'); - expect(result).toEqual(tokens.standardIdTokenParsed); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); }); }); @@ -458,7 +611,9 @@ define(function(require) { function sessionStorageSetup() { return setupSync({ - type: 'sessionStorage' + tokenManager: { + type: 'sessionStorage' + } }); } @@ -473,13 +628,31 @@ define(function(require) { }); describe('get', function() { - it('gets a token', function() { + it('returns a token', function(done) { var client = sessionStorageSetup(); sessionStorage.setItem('okta-token-storage', JSON.stringify({ 'test-idToken': tokens.standardIdTokenParsed })); - var result = client.tokenManager.get('test-idToken'); - expect(result).toEqual(tokens.standardIdTokenParsed); + util.warpToUnixTime(tokens.standardIdTokenClaims.iat); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toEqual(tokens.standardIdTokenParsed); + done(); + }); + // Warp back to current time + util.warpToUnixTime(Date.now()); + }); + + it('returns undefined for an expired token', function(done) { + var client = sessionStorageSetup(); + sessionStorage.setItem('okta-token-storage', JSON.stringify({ + 'test-idToken': tokens.standardIdTokenParsed + })); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); }); }); @@ -496,7 +669,7 @@ define(function(require) { }); }); }); - + describe('clear', function() { it('clears all tokens', function() { var client = sessionStorageSetup(); @@ -515,59 +688,77 @@ define(function(require) { function cookieStorageSetup() { return setupSync({ - type: 'cookie' + tokenManager: { + type: 'cookie' + } }); } describe('add', function() { it('adds a token', function() { var client = cookieStorageSetup(); - util.mockGetCookie(''); 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;'); + expect(setCookieMock).toHaveBeenCalledWith( + 'okta-token-storage', + JSON.stringify({'test-idToken': tokens.standardIdTokenParsed}), + '2038-01-19T03:14:07.000Z' + ); }); }); describe('get', function() { - it('gets a token', function() { + it('returns a token', function(done) { var client = cookieStorageSetup(); - util.mockGetCookie('okta-token-storage=' + JSON.stringify({ - 'test-idToken': tokens.standardIdTokenParsed - }) + ';'); - var result = client.tokenManager.get('test-idToken'); - expect(result).toEqual(tokens.standardIdTokenParsed); + client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); + util.warpToUnixTime(tokens.standardIdTokenClaims.iat); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toEqual(tokens.standardIdTokenParsed); + done(); + }); + // Warp back to current time + util.warpToUnixTime(Date.now()); + }); + + it('returns undefined for an expired token', function(done) { + var client = cookieStorageSetup(); + client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); + client.tokenManager.get('test-idToken') + .then(function(token) { + expect(token).toBeUndefined(); + done(); + }); }); }); describe('remove', function() { it('removes a token', function() { var client = cookieStorageSetup(); - util.mockGetCookie('okta-token-storage=' + JSON.stringify({ - 'test-idToken': tokens.standardIdTokenParsed, - anotherKey: tokens.standardIdTokenParsed - }) + ';'); + client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); + client.tokenManager.add('anotherKey', tokens.standardIdTokenParsed); var setCookieMock = util.mockSetCookie(); client.tokenManager.remove('test-idToken'); - expect(setCookieMock).toHaveBeenCalledWith('okta-token-storage=' + JSON.stringify({ - anotherKey: tokens.standardIdTokenParsed - }) + '; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT;'); + expect(setCookieMock).toHaveBeenCalledWith( + 'okta-token-storage', + JSON.stringify({anotherKey: tokens.standardIdTokenParsed}), + '2038-01-19T03:14:07.000Z' + ); }); }); describe('clear', function() { it('clears all tokens', function() { var client = cookieStorageSetup(); - util.mockGetCookie('okta-token-storage=' + JSON.stringify({ - 'test-idToken': tokens.standardIdTokenParsed, - anotherKey: tokens.standardIdTokenParsed - }) + ';'); + client.tokenManager.add('test-idToken', tokens.standardIdTokenParsed); + client.tokenManager.add('anotherKey', tokens.standardIdTokenParsed); var setCookieMock = util.mockSetCookie(); client.tokenManager.clear(); - expect(setCookieMock).toHaveBeenCalledWith('okta-token-storage={}; path=/; ' + - 'expires=Tue, 19 Jan 2038 03:14:07 GMT;'); + expect(setCookieMock).toHaveBeenCalledWith( + 'okta-token-storage', + '{}', + '2038-01-19T03:14:07.000Z' + ); }); }); }); diff --git a/test/spec/util.js b/test/spec/util.js index e683d834b..9d69fed39 100644 --- a/test/spec/util.js +++ b/test/spec/util.js @@ -90,5 +90,37 @@ define(function(require) { }); }); + describe('removeTrailingSlash', function() { + it('returns a url with a trailing slash without the trailing slash', function() { + var url = 'https://domain.com/'; + expect(util.removeTrailingSlash(url)).toEqual('https://domain.com'); + }); + + it('returns a url without a trailing slash as is', function() { + var url = 'https://domain.com'; + expect(util.removeTrailingSlash(url)).toEqual('https://domain.com'); + }); + + it('returns a url with a trailing slash and appended whitespace correctly', function() { + var url = 'https://domain.com/ '; + expect(util.removeTrailingSlash(url)).toEqual('https://domain.com'); + }); + + it('returns a url with a trailing slash and prepended whitespace correctly', function() { + var url = ' https://domain.com/'; + expect(util.removeTrailingSlash(url)).toEqual('https://domain.com'); + }); + + it('returns a url without a trailing slash and appended whitespace correctly', function() { + var url = 'https://domain.com '; + expect(util.removeTrailingSlash(url)).toEqual('https://domain.com'); + }); + + it('returns a url without a trailing slash and prepended whitespace correctly', function() { + var url = ' https://domain.com'; + expect(util.removeTrailingSlash(url)).toEqual('https://domain.com'); + }); + }); + }); }); diff --git a/test/util/oauthUtil.js b/test/util/oauthUtil.js index 60e4db5e9..2b3f54461 100644 --- a/test/util/oauthUtil.js +++ b/test/util/oauthUtil.js @@ -5,7 +5,6 @@ define(function(require) { var tokens = require('./tokens'); var Q = require('q'); var EventEmitter = require('tiny-emitter'); - var _ = require('lodash'); var wellKnown = require('../xhr/well-known'); var wellKnownSharedResource = require('../xhr/well-known-shared-resource'); var keys = require('../xhr/keys'); @@ -121,10 +120,10 @@ define(function(require) { (opts.authorizeArgs && opts.authorizeArgs.responseMode !== 'fragment') || opts.getWithoutPromptArgs || opts.getWithPopupArgs || - opts.tokenManagerRefreshArgs || - opts.refreshArgs || - opts.tokenRefreshArgs || - opts.autoRefresh) { + opts.tokenManagerRenewArgs || + opts.renewArgs || + opts.tokenRenewArgs || + opts.autoRenew) { // Simulate the postMessage between the window and the popup or iframe spyOn(window, 'addEventListener').and.callFake(function(eventName, fn) { if (eventName === 'message' && !opts.closePopup) { @@ -145,6 +144,8 @@ define(function(require) { var authClient; if (opts.oktaAuthArgs) { authClient = new OktaAuth(opts.oktaAuthArgs); + } else if (opts.authClient) { + authClient = opts.authClient; } else { authClient = new OktaAuth({ url: 'https://auth-js-test.okta.com' @@ -153,9 +154,8 @@ define(function(require) { util.warpToUnixTime(getTime(opts.time)); - if (opts.hrefMock) { - util.mockGetWindowLocation(authClient, opts.hrefMock); - } + // Mock the well-known and keys request + oauthUtil.loadWellKnownAndKeysCache(); if (opts.tokenManagerAddKeys) { for (var key in opts.tokenManagerAddKeys) { @@ -168,8 +168,8 @@ define(function(require) { } var promise; - if (opts.refreshArgs) { - promise = authClient.idToken.refresh(opts.refreshArgs); + if (opts.renewArgs) { + promise = authClient.token.renew(opts.renewArgs); } else if (opts.getWithoutPromptArgs) { if (Array.isArray(opts.getWithoutPromptArgs)) { promise = authClient.token.getWithoutPrompt.apply(null, opts.getWithoutPromptArgs); @@ -182,33 +182,37 @@ define(function(require) { } else { promise = authClient.token.getWithPopup(opts.getWithPopupArgs); } - } else if (opts.tokenManagerRefreshArgs) { - promise = authClient.tokenManager.refresh.apply(this, opts.tokenManagerRefreshArgs); - } else if (opts.tokenRefreshArgs) { - promise = authClient.token.refresh.apply(this, opts.tokenRefreshArgs); - } else if (opts.autoRefresh) { - var refreshDeferred = Q.defer(); - authClient.tokenManager.on('refreshed', function() { - refreshDeferred.resolve(); + } else if (opts.tokenManagerRenewArgs) { + promise = authClient.tokenManager.renew.apply(this, opts.tokenManagerRenewArgs); + } else if (opts.tokenRenewArgs) { + promise = authClient.token.renew.apply(this, opts.tokenRenewArgs); + } else if (opts.autoRenew) { + var renewDeferred = Q.defer(); + authClient.tokenManager.on('renewed', function() { + renewDeferred.resolve(); }); - authClient.tokenManager.on('expired', function() { - refreshDeferred.resolve(); + authClient.tokenManager.on('error', function() { + renewDeferred.resolve(); }); - promise = refreshDeferred.promise; - } else { - promise = authClient.idToken.authorize(opts.authorizeArgs); + promise = renewDeferred.promise; } if (opts.fastForwardToTime) { - util.warpByTicksToUnixTime(opts.fastForwardToTime); + // Since the token is "expired", we're going to attempt to + // retrieve it and kick-off the autoRenew and let the event listeners + // above pick up the 'renewed' and 'error' events. + promise = authClient.tokenManager.get(opts.autoRenewTokenKey); + util.warpByTicksToUnixTime(opts.time); } return promise .then(function(res) { - if (opts.autoRefresh) { + if(opts.beforeCompletion) { + opts.beforeCompletion(authClient); + } + if (opts.autoRenew) { return; } - var expectedResp = opts.expectedResp || defaultResponse; validateResponse(res, expectedResp); }) @@ -263,7 +267,7 @@ define(function(require) { // All iframes should be created and destroyed in the same test var iframes = document.getElementsByTagName('IFRAME'); expect(iframes.length).toBe(0); - + // Remove any frames that exist, so we don't taint our other tests oauthUtil.removeAllFrames(); } @@ -330,7 +334,7 @@ define(function(require) { } }); }; - + oauthUtil.setupRedirect = function(opts) { var client = new OktaAuth(opts.oktaAuthArgs || { url: 'https://auth-js-test.okta.com', @@ -338,6 +342,9 @@ define(function(require) { redirectUri: 'https://example.com/redirect' }); + // Mock the well-known and keys request + oauthUtil.loadWellKnownAndKeysCache(); + oauthUtil.mockStateAndNonce(); var windowLocationMock = util.mockSetWindowLocation(client); var setCookieMock = util.mockSetCookie(); @@ -350,9 +357,7 @@ define(function(require) { expect(windowLocationMock).toHaveBeenCalledWith(opts.expectedRedirectUrl); - _.each(opts.expectedCookies, function(cookie) { - expect(setCookieMock).toHaveBeenCalledWith(cookie); - }); + expect(setCookieMock.calls.allArgs()).toEqual(opts.expectedCookies); }; oauthUtil.setupParseUrl = function(opts) { @@ -362,6 +367,9 @@ define(function(require) { redirectUri: 'https://example.com/redirect' }); + // Mock the well-known and keys request + oauthUtil.loadWellKnownAndKeysCache(); + util.warpToUnixTime(getTime(opts.time)); // Mock location @@ -401,7 +409,7 @@ define(function(require) { } util.mockGetCookie(opts.oauthCookie); - var setCookieMock = util.mockSetCookie(); + var deleteCookieMock = util.mockDeleteCookie(); return client.token.parseFromUrl(opts.directUrl) .then(function(res) { @@ -409,8 +417,7 @@ define(function(require) { validateResponse(res, expectedResp); // The cookie should be deleted - expect(setCookieMock).toHaveBeenCalledWith('okta-oauth-redirect-params=; path=/; ' + - 'expires=Thu, 01 Jan 1970 00:00:00 GMT;'); + expect(deleteCookieMock).toHaveBeenCalledWith('okta-oauth-redirect-params'); if (opts.directUrl) { expect(setLocationHashSpy).not.toHaveBeenCalled(); @@ -431,6 +438,9 @@ define(function(require) { redirectUri: 'https://example.com/redirect' }); + // Mock the well-known and keys request + oauthUtil.loadWellKnownAndKeysCache(); + var emitter = new EventEmitter(); spyOn(window, 'addEventListener').and.callFake(function(eventName, fn) { if (eventName === 'message') { @@ -489,7 +499,7 @@ define(function(require) { .fin(done); }); }; - + oauthUtil.expectTokenStorageToEqual = function(storage, obj) { var parsed = JSON.parse(storage.getItem('okta-token-storage')); expect(parsed).toEqual(obj); diff --git a/test/util/util.js b/test/util/util.js index ff40a27dd..3853b1127 100644 --- a/test/util/util.js +++ b/test/util/util.js @@ -7,7 +7,7 @@ define(function(require) { OktaAuth = require('OktaAuth'), cookies = require('../../lib/cookies'); - + var util = {}; util.warpToDistantFuture = function () { @@ -110,7 +110,7 @@ define(function(require) { xhr.getResponseHeader = function(name) { return xhr.headers && xhr.headers[name]; }; - + if (xhr.status > 0 && xhr.status < 300) { // $.ajax send (data, textStatus, jqXHR) on success _.defer(function () { deferred.resolve(xhr.response, null, xhr); }); @@ -144,7 +144,7 @@ define(function(require) { // 1. Setup ajax mock if (options.calls) { - + // Get all the pairs and load the mock var xhrGenPromises = []; _.each(options.calls, function(call) { @@ -325,20 +325,20 @@ define(function(require) { }); }; - util.mockGetWindowLocation = function (client, href) { - spyOn(client.idToken.authorize, '_getLocationHref').and.returnValue(href); - }; - util.mockSetWindowLocation = function (client) { return spyOn(client.token.getWithRedirect, '_setLocation'); }; util.mockSetCookie = function () { - return spyOn(cookies.setCookie, '_setDocumentCookie'); + return spyOn(cookies, 'setCookie'); + }; + + util.mockDeleteCookie = function () { + return spyOn(cookies, 'deleteCookie'); }; util.mockGetCookie = function (text) { - spyOn(cookies.getCookie, '_getDocumentCookie').and.returnValue(text || ''); + spyOn(cookies, 'getCookie').and.returnValue(text || ''); }; util.mockGetHistory = function (client, mockHistory) {