diff --git a/package.json b/package.json index ab6f7922f..5ffa55466 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "clean-dist": "rimraf dist && lerna exec rimraf build2 && lerna exec rimraf dist", "clean-node-modules": "lerna exec rimraf node_modules && rimraf node_modules", "lint": "yarn workspaces run lint", + "validate": "yarn workspace @okta/okta-auth-js tsc && yarn lint", "publish": "lerna publish --skip-npm", "test": "yarn test:unit && yarn test:e2e", "test:e2e": "yarn --cwd test/e2e start", diff --git a/packages/okta-auth-js/.eslintignore b/packages/okta-auth-js/.eslintignore index da569770b..a77e1d3b7 100644 --- a/packages/okta-auth-js/.eslintignore +++ b/packages/okta-auth-js/.eslintignore @@ -7,3 +7,4 @@ node_modules *.config.js /lib/config.js +build2 \ No newline at end of file diff --git a/packages/okta-auth-js/.eslintrc.json b/packages/okta-auth-js/.eslintrc.json index 464b535c0..fdfe14446 100644 --- a/packages/okta-auth-js/.eslintrc.json +++ b/packages/okta-auth-js/.eslintrc.json @@ -1,14 +1,65 @@ { - "extends": ["plugin:compat/recommended"], + "extends": [ + "plugin:compat/recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:jsdoc/recommended" + ], + "globals": { + "require": true, + "global": true + }, "settings": { "polyfills": [ "Promise", "Array.from", "TextEncoder" - ] + ], + "jsdoc": { + "mode": "typescript", + "definedTags": [ + "type" + ] + } }, "parserOptions": { "sourceType": "module", "ecmaVersion": 2017 - } + }, + "rules": { + "no-var": 0, + "prefer-rest-params": 0, + "prefer-spread": 0, + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/camelcase": 0, + "@typescript-eslint/no-this-alias": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-use-before-define": 0, + "@typescript-eslint/ban-ts-ignore": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/interface-name-prefix": 0, + "@typescript-eslint/triple-slash-reference": 0, + "jsdoc/require-jsdoc": 0, + "jsdoc/require-param": 0, + "jsdoc/require-returns-description": 0, + "jsdoc/require-param-description": 0, + "jsdoc/require-returns": 0, + "jsdoc/no-undefined-types": 0 + }, + "overrides": [{ + "files": [ + "*.ts", + "lib/**/*" + ], + "plugins": [ + "@typescript-eslint", + "jsdoc" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "tsconfigRootDir": "./" + } + }] } diff --git a/packages/okta-auth-js/jest.browser.js b/packages/okta-auth-js/jest.browser.js index ac54da907..c466dae11 100644 --- a/packages/okta-auth-js/jest.browser.js +++ b/packages/okta-auth-js/jest.browser.js @@ -16,6 +16,9 @@ module.exports = { 'testPathIgnorePatterns': [ './test/spec/serverStorage.js' ], + 'modulePathIgnorePatterns': [ + '/build2/' + ], 'reporters': [ 'default', 'jest-junit' diff --git a/packages/okta-auth-js/jest.server.js b/packages/okta-auth-js/jest.server.js index b60bfecfc..8f261cecd 100644 --- a/packages/okta-auth-js/jest.server.js +++ b/packages/okta-auth-js/jest.server.js @@ -16,12 +16,16 @@ module.exports = { './test/spec/fingerprint.js', './test/spec/general.js', './test/spec/oauthUtil.js', + './test/spec/session.js', './test/spec/token.js', './test/spec/tokenManager.js', './test/spec/webfinger.js', './test/spec/pkce.js', './test/spec/features.js' ], + 'modulePathIgnorePatterns': [ + '/build2/' + ], 'reporters': [ 'default', 'jest-junit' diff --git a/packages/okta-auth-js/lib/TokenManager.js b/packages/okta-auth-js/lib/TokenManager.js index 68b6d6621..e15be2eca 100644 --- a/packages/okta-auth-js/lib/TokenManager.js +++ b/packages/okta-auth-js/lib/TokenManager.js @@ -10,6 +10,15 @@ * See the License for the specific language governing permissions and limitations under the License. * */ + + /** + * @typedef {OktaAuth.TokenManagerOptions} TokenManagerOptions + * @typedef {OktaAuth.TokenManagerAPI} TokenManagerAPI + * @typedef {OktaAuth.TokenManagerRef} TokenManagerRef + * @typedef {OktaAuth.Token} Token + * @typedef {OktaAuth.StorageProvider} StorageProvider + */ + /* global localStorage, sessionStorage */ /* eslint complexity:[0,8] max-statements:[0,21] */ var util = require('./util'); @@ -19,30 +28,52 @@ var constants = require('./constants'); var storageBuilder = require('./storageBuilder'); var SdkClock = require('./clock'); +/** @type {TokenManagerOptions} */ var DEFAULT_OPTIONS = { autoRenew: true, storage: 'localStorage', expireEarlySeconds: 30 }; +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {Token} token + */ function getExpireTime(tokenMgmtRef, token) { var expireTime = token.expiresAt - tokenMgmtRef.options.expireEarlySeconds; return expireTime; } +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {Token} token + */ function hasExpired(tokenMgmtRef, token) { var expireTime = getExpireTime(tokenMgmtRef, token); return expireTime <= tokenMgmtRef.clock.now(); } +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {string} key + * @param {Token} token + */ function emitExpired(tokenMgmtRef, key, token) { tokenMgmtRef.emitter.emit('expired', key, token); } +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {any} error + */ function emitError(tokenMgmtRef, error) { tokenMgmtRef.emitter.emit('error', error); } +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {string} key + */ function clearExpireEventTimeout(tokenMgmtRef, key) { clearTimeout(tokenMgmtRef.expireTimeouts[key]); delete tokenMgmtRef.expireTimeouts[key]; @@ -51,6 +82,9 @@ function clearExpireEventTimeout(tokenMgmtRef, key) { delete tokenMgmtRef.renewPromise[key]; } +/** + * @param {TokenManagerRef} tokenMgmtRef + */ function clearExpireEventTimeoutAll(tokenMgmtRef) { var expireTimeouts = tokenMgmtRef.expireTimeouts; for (var key in expireTimeouts) { @@ -61,6 +95,12 @@ function clearExpireEventTimeoutAll(tokenMgmtRef) { } } +/** + * @param {OktaAuth} sdk + * @param {TokenManagerRef} tokenMgmtRef + * @param {string} key + * @param {Token} token + */ function setExpireEventTimeout(sdk, tokenMgmtRef, key, token) { var expireTime = getExpireTime(tokenMgmtRef, token); var expireEventWait = Math.max(expireTime - tokenMgmtRef.clock.now(), 0) * 1000; @@ -76,6 +116,11 @@ function setExpireEventTimeout(sdk, tokenMgmtRef, key, token) { tokenMgmtRef.expireTimeouts[key] = expireEventTimeout; } +/** + * @param {OktaAuth} sdk + * @param {TokenManagerRef} tokenMgmtRef + * @param {StorageProvider} storage + */ function setExpireEventTimeoutAll(sdk, tokenMgmtRef, storage) { try { var tokenStorage = storage.getStorage(); @@ -90,11 +135,19 @@ function setExpireEventTimeoutAll(sdk, tokenMgmtRef, storage) { if (!tokenStorage.hasOwnProperty(key)) { continue; } + /** @type {Token} */ var token = tokenStorage[key]; setExpireEventTimeout(sdk, tokenMgmtRef, key, token); } } +/** + * @param {OktaAuth} sdk + * @param {TokenManagerRef} tokenMgmtRef + * @param {StorageProvider} storage + * @param {string} key + * @param {any} token + */ function add(sdk, tokenMgmtRef, storage, key, token) { var tokenStorage = storage.getStorage(); if (!util.isObject(token) || @@ -108,11 +161,21 @@ function add(sdk, tokenMgmtRef, storage, key, token) { setExpireEventTimeout(sdk, tokenMgmtRef, key, token); } +/** + * @param {StorageProvider} storage + * @param {string} key + */ function get(storage, key) { var tokenStorage = storage.getStorage(); return tokenStorage[key]; } +/** + * @param {OktaAuth} sdk + * @param {TokenManagerRef} tokenMgmtRef + * @param {StorageProvider} storage + * @param {string} key + */ function getAsync(sdk, tokenMgmtRef, storage, key) { return new Promise(function(resolve) { var token = get(storage, key); @@ -128,6 +191,11 @@ function getAsync(sdk, tokenMgmtRef, storage, key) { }); } +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {StorageProvider} storage + * @param {string} key + */ function remove(tokenMgmtRef, storage, key) { // Clear any listener for this token clearExpireEventTimeout(tokenMgmtRef, key); @@ -188,11 +256,19 @@ function renew(sdk, tokenMgmtRef, storage, key) { return tokenMgmtRef.renewPromise[key]; } +/** + * @param {TokenManagerRef} tokenMgmtRef + * @param {StorageProvider} storage + */ function clear(tokenMgmtRef, storage) { clearExpireEventTimeoutAll(tokenMgmtRef); storage.clearStorage(); } +/** + * @param {OktaAuth} sdk + * @param {TokenManagerOptions} options + */ function TokenManager(sdk, options) { options = util.extend({}, DEFAULT_OPTIONS, util.removeNils(options)); @@ -230,7 +306,9 @@ function TokenManager(sdk, options) { } var storageKey = options.storageKey || constants.TOKEN_STORAGE_NAME; var storage = storageBuilder(storageProvider, storageKey); - var clock = SdkClock.create(sdk, options); + var clock = SdkClock.create(); + + /** @type {TokenManagerRef} */ var tokenMgmtRef = { clock: clock, options: options, @@ -239,13 +317,15 @@ function TokenManager(sdk, options) { renewPromise: {} }; - this.add = util.bind(add, this, sdk, tokenMgmtRef, 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.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); + /** @type {TokenManagerAPI} */ + var api = this; + api.add = util.bind(add, this, sdk, tokenMgmtRef, storage); + api.get = util.bind(getAsync, this, sdk, tokenMgmtRef, storage); + api.remove = util.bind(remove, this, tokenMgmtRef, storage); + api.clear = util.bind(clear, this, tokenMgmtRef, storage); + api.renew = util.bind(renew, this, sdk, tokenMgmtRef, storage); + api.on = util.bind(tokenMgmtRef.emitter.on, tokenMgmtRef.emitter); + api.off = util.bind(tokenMgmtRef.emitter.off, tokenMgmtRef.emitter); setExpireEventTimeoutAll(sdk, tokenMgmtRef, storage); } diff --git a/packages/okta-auth-js/lib/browser/browser.js b/packages/okta-auth-js/lib/browser/browser.js index f0f47feef..1f52a97c6 100644 --- a/packages/okta-auth-js/lib/browser/browser.js +++ b/packages/okta-auth-js/lib/browser/browser.js @@ -14,6 +14,10 @@ /* SDK_VERSION is defined in webpack config */ /* global SDK_VERSION */ /* global window, navigator, document, crypto */ +/** + * @typedef {OktaAuth.OktaAuthOptions} OktaAuthOptions + * @typedef {OktaAuth.CookieOptions} CookieOptions + */ var Emitter = require('tiny-emitter'); var AuthSdkError = require('../errors/AuthSdkError'); var builderUtil = require('../builderUtil'); @@ -27,11 +31,18 @@ var TokenManager = require('../TokenManager'); var tx = require('../tx'); var util = require('../util'); +/** + * @param {OktaAuthOptions} args + */ function OktaAuthBuilder(args) { + /** @type {OktaAuth} */ var sdk = this; builderUtil.assertValidConfig(args); + /** + * @type {CookieOptions} + */ var cookieSettings = util.extend({ secure: true }, args.cookies); @@ -53,7 +64,10 @@ function OktaAuthBuilder(args) { cookieSettings.secure = false; } - this.options = { + /** + * @type {OktaAuthOptions} + */ + var options = { clientId: args.clientId, issuer: util.removeTrailingSlash(args.issuer), authorizeUrl: util.removeTrailingSlash(args.authorizeUrl), @@ -73,7 +87,9 @@ function OktaAuthBuilder(args) { cookies: cookieSettings }; - this.userAgent = builderUtil.getUserAgent(args, `okta-auth-js/${SDK_VERSION}`); + // @ts-ignore + var sdkDefaultUserAgent = `okta-auth-js/${SDK_VERSION}`; // SDK_VERSION is defined in webpack config + this.userAgent = builderUtil.getUserAgent(args, sdkDefaultUserAgent); // Digital clocks will drift over time, so the server // can misalign with the time reported by the browser. @@ -83,14 +99,16 @@ function OktaAuthBuilder(args) { // default maximum tolerance allowed by Kerberos. // (https://technet.microsoft.com/en-us/library/cc976357.aspx) if (!args.maxClockSkew && args.maxClockSkew !== 0) { - this.options.maxClockSkew = constants.DEFAULT_MAX_CLOCK_SKEW; + options.maxClockSkew = constants.DEFAULT_MAX_CLOCK_SKEW; } else { - this.options.maxClockSkew = args.maxClockSkew; + options.maxClockSkew = args.maxClockSkew; } // Give the developer the ability to disable token signature // validation. - this.options.ignoreSignature = !!args.ignoreSignature; + options.ignoreSignature = !!args.ignoreSignature; + + sdk.options = options; sdk.session = { close: util.bind(session.closeSession, null, sdk), @@ -103,15 +121,15 @@ function OktaAuthBuilder(args) { sdk.tx = { status: util.bind(tx.transactionStatus, null, sdk), resume: util.bind(tx.resumeTransaction, null, sdk), - exists: util.bind(tx.transactionExists, null, sdk), + exists: util.extend(util.bind(tx.transactionExists, null, sdk), { + // This is exposed so we can mock document.cookie in our tests + _get: function(name) { + return cookies.get(name); + } + }), introspect: util.bind(tx.introspect, null, sdk) }; - // This is exposed so we can mock document.cookie in our tests - sdk.tx.exists._get = function(name) { - return cookies.get(name); - }; - // This is exposed so we can mock window.location.href in our tests sdk.idToken = { authorize: { @@ -124,8 +142,28 @@ function OktaAuthBuilder(args) { sdk.token = { getWithoutPrompt: util.bind(token.getWithoutPrompt, null, sdk), getWithPopup: util.bind(token.getWithPopup, null, sdk), - getWithRedirect: util.bind(token.getWithRedirect, null, sdk), - parseFromUrl: util.bind(token.parseFromUrl, null, sdk), + getWithRedirect: util.extend(util.bind(token.getWithRedirect, null, sdk), { + // This is exposed so we can set window.location in our tests + _setLocation: function(url) { + window.location = url; + } + }), + parseFromUrl: util.extend(util.bind(token.parseFromUrl, null, sdk), { + // This is exposed so we can mock getting window.history in our tests + _getHistory: function() { + return window.history; + }, + + // This is exposed so we can mock getting window.location in our tests + _getLocation: function() { + return window.location; + }, + + // This is exposed so we can mock getting window.document in our tests + _getDocument: function() { + return window.document; + } + }), decode: token.decodeToken, revoke: util.bind(token.revokeToken, null, sdk), renew: util.bind(token.renewToken, null, sdk), @@ -133,30 +171,6 @@ function OktaAuthBuilder(args) { verify: util.bind(token.verifyToken, null, sdk) }; - // This is exposed so we can set window.location in our tests - sdk.token.getWithRedirect._setLocation = function(url) { - window.location = url; - }; - - // This is exposed so we can mock getting window.history in our tests - sdk.token.parseFromUrl._getHistory = function() { - return window.history; - }; - - // This is exposed so we can mock getting window.location in our tests - sdk.token.parseFromUrl._getLocation = function() { - return window.location; - }; - - // This is exposed so we can mock getting window.document in our tests - sdk.token.parseFromUrl._getDocument = function() { - return window.document; - }; - - sdk.fingerprint._getUserAgent = function() { - return navigator.userAgent; - }; - var isWindowsPhone = /windows phone|iemobile|wpdesktop/i; sdk.features.isFingerprintSupported = function() { var agent = sdk.fingerprint._getUserAgent(); @@ -168,6 +182,9 @@ function OktaAuthBuilder(args) { sdk.tokenManager.on('error', this._onTokenManagerError, this); } +/** + * @type {OktaAuth} + */ var proto = OktaAuthBuilder.prototype; proto._onTokenManagerError = function(error) { var code = error.errorCode; @@ -333,7 +350,7 @@ proto.webfinger = function (opts) { return http.get(this, url, options); }; -proto.fingerprint = function(options) { +proto.fingerprint = util.extend(function(options) { options = options || {}; var sdk = this; if (!sdk.features.isFingerprintSupported()) { @@ -385,6 +402,10 @@ proto.fingerprint = function(options) { iframe.parentElement.removeChild(iframe); } }); -}; +}, { + _getUserAgent: function() { + return navigator.userAgent; + } +}); module.exports = builderUtil.buildOktaAuth(OktaAuthBuilder); diff --git a/packages/okta-auth-js/lib/browser/browserStorage.js b/packages/okta-auth-js/lib/browser/browserStorage.js index 8740691c1..2abd81684 100644 --- a/packages/okta-auth-js/lib/browser/browserStorage.js +++ b/packages/okta-auth-js/lib/browser/browserStorage.js @@ -11,137 +11,147 @@ * */ /* global localStorage, sessionStorage */ +/** + * @typedef {OktaAuth.StorageUtil} StorageUtil + * @typedef {OktaAuth.StorageProvider} StorageProvider + */ + + var Cookies = require('js-cookie'); var storageBuilder = require('../storageBuilder'); var constants = require('../constants'); var AuthSdkError = require('../errors/AuthSdkError'); // Building this as an object allows us to mock the functions in our tests -var storageUtil = {}; - -// IE11 bug that Microsoft doesn't plan to fix -// https://connect.microsoft.com/IE/Feedback/Details/1496040 -storageUtil.browserHasLocalStorage = function() { - try { - var storage = storageUtil.getLocalStorage(); - return storageUtil.testStorage(storage); - } catch (e) { - return false; - } -}; - -storageUtil.browserHasSessionStorage = function() { - try { - var storage = storageUtil.getSessionStorage(); - return storageUtil.testStorage(storage); - } catch (e) { - return false; - } -}; - -storageUtil.getPKCEStorage = function(options) { - if (storageUtil.browserHasSessionStorage()) { - return storageBuilder(storageUtil.getSessionStorage(), constants.PKCE_STORAGE_NAME); - } else if (storageUtil.browserHasLocalStorage()) { - return storageBuilder(storageUtil.getLocalStorage(), constants.PKCE_STORAGE_NAME); - } else { - return storageBuilder(storageUtil.getCookieStorage(options), constants.PKCE_STORAGE_NAME); - } -}; - -storageUtil.getHttpCache = function(options) { - if (storageUtil.browserHasLocalStorage()) { - return storageBuilder(storageUtil.getLocalStorage(), constants.CACHE_STORAGE_NAME); - } else if (storageUtil.browserHasSessionStorage()) { - return storageBuilder(storageUtil.getSessionStorage(), constants.CACHE_STORAGE_NAME); - } else { - return storageBuilder(storageUtil.getCookieStorage(options), constants.CACHE_STORAGE_NAME); - } -}; - -storageUtil.getLocalStorage = function() { - return localStorage; -}; +/** @type {StorageUtil} */ +var storageUtil = { + // We must define all properties up front. + // https://github.com/microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files#object-literals-are-open-ended + + // IE11 bug that Microsoft doesn't plan to fix + // https://connect.microsoft.com/IE/Feedback/Details/1496040 + browserHasLocalStorage: function() { + try { + var storage = storageUtil.getLocalStorage(); + return storageUtil.testStorage(storage); + } catch (e) { + return false; + } + }, -storageUtil.getSessionStorage = function() { - return sessionStorage; -}; + browserHasSessionStorage: function() { + try { + var storage = storageUtil.getSessionStorage(); + return storageUtil.testStorage(storage); + } catch (e) { + return false; + } + }, -// Provides webStorage-like interface for cookies -storageUtil.getCookieStorage = function(options) { - const secure = options.secure; - const sameSite = options.sameSite; - if (typeof secure === 'undefined' || typeof sameSite === 'undefined') { - throw new AuthSdkError('getCookieStorage: "secure" and "sameSite" options must be provided'); - } - return { - getItem: storageUtil.storage.get, - setItem: function(key, value) { - // Cookie shouldn't expire - storageUtil.storage.set(key, value, '2200-01-01T00:00:00.000Z', { - secure: secure, - sameSite: sameSite - }); + getPKCEStorage: function(options) { + if (storageUtil.browserHasSessionStorage()) { + return storageBuilder(storageUtil.getSessionStorage(), constants.PKCE_STORAGE_NAME); + } else if (storageUtil.browserHasLocalStorage()) { + return storageBuilder(storageUtil.getLocalStorage(), constants.PKCE_STORAGE_NAME); + } else { + return storageBuilder(storageUtil.getCookieStorage(options), constants.PKCE_STORAGE_NAME); } - }; -}; + }, -// Provides an in-memory solution -storageUtil.getInMemoryStorage = function() { - var store = {}; - return { - getItem: function(key) { - return store[key]; - }, - setItem: function(key, value) { - store[key] = value; + getHttpCache: function(options) { + if (storageUtil.browserHasLocalStorage()) { + return storageBuilder(storageUtil.getLocalStorage(), constants.CACHE_STORAGE_NAME); + } else if (storageUtil.browserHasSessionStorage()) { + return storageBuilder(storageUtil.getSessionStorage(), constants.CACHE_STORAGE_NAME); + } else { + return storageBuilder(storageUtil.getCookieStorage(options), constants.CACHE_STORAGE_NAME); } - }; -}; + }, -storageUtil.testStorage = function(storage) { - var key = 'okta-test-storage'; - try { - storage.setItem(key, key); - storage.removeItem(key); - return true; - } catch (e) { - return false; - } -}; + getLocalStorage: function() { + return localStorage; + }, + + getSessionStorage: function() { + return sessionStorage; + }, -storageUtil.storage = { - set: function(name, value, expiresAt, options) { + // Provides webStorage-like interface for cookies + getCookieStorage: function(options) { const secure = options.secure; const sameSite = options.sameSite; if (typeof secure === 'undefined' || typeof sameSite === 'undefined') { - throw new AuthSdkError('storage.set: "secure" and "sameSite" options must be provided'); + throw new AuthSdkError('getCookieStorage: "secure" and "sameSite" options must be provided'); } - var cookieOptions = { - path: options.path || '/', - secure, - sameSite + return { + getItem: storageUtil.storage.get, + setItem: function(key, value) { + // Cookie shouldn't expire + storageUtil.storage.set(key, value, '2200-01-01T00:00:00.000Z', { + secure: secure, + sameSite: sameSite + }); + } }; + }, - // 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); - } - - Cookies.set(name, value, cookieOptions); - return storageUtil.storage.get(name); + // Provides an in-memory solution + getInMemoryStorage: function() { + var store = {}; + return { + getItem: function(key) { + return store[key]; + }, + setItem: function(key, value) { + store[key] = value; + } + }; }, - get: function(name) { - return Cookies.get(name); + testStorage: function(storage) { + var key = 'okta-test-storage'; + try { + storage.setItem(key, key); + storage.removeItem(key); + return true; + } catch (e) { + return false; + } }, - delete: function(name) { - return Cookies.remove(name, { path: '/' }); + storage: { + set: function(name, value, expiresAt, options) { + const secure = options.secure; + const sameSite = options.sameSite; + if (typeof secure === 'undefined' || typeof sameSite === 'undefined') { + throw new AuthSdkError('storage.set: "secure" and "sameSite" options must be provided'); + } + var cookieOptions = { + path: options.path || '/', + secure, + sameSite + }; + + // 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); + } + + Cookies.set(name, value, cookieOptions); + return storageUtil.storage.get(name); + }, + + get: function(name) { + return Cookies.get(name); + }, + + delete: function(name) { + return Cookies.remove(name, { path: '/' }); + } } }; diff --git a/packages/okta-auth-js/lib/builderUtil.js b/packages/okta-auth-js/lib/builderUtil.js index 7f2b0a6fb..d06848968 100644 --- a/packages/okta-auth-js/lib/builderUtil.js +++ b/packages/okta-auth-js/lib/builderUtil.js @@ -10,10 +10,19 @@ * See the License for the specific language governing permissions and limitations under the License. */ +/** + * @typedef {OktaAuth.OktaAuthOptions} OktaAuthOptions + * @typedef {OktaAuth.OktaAuthBuilder} OktaAuthBuilder + * @typedef {OktaAuth.OktaAuthFactory} OktaAuthFactory + */ + var AuthSdkError = require('./errors/AuthSdkError'); var tx = require('./tx'); var util = require('./util'); +/** + * @param {OktaAuthOptions} args + */ // TODO: use @okta/configuration-validation (move module to this monorepo?) // eslint-disable-next-line complexity function assertValidConfig(args) { @@ -48,6 +57,9 @@ function assertValidConfig(args) { } } +/** + * @param {OktaAuth} proto + */ function addSharedPrototypes(proto) { proto.getIssuerOrigin = function() { // Infer the URL from the issuer URL, omitting the /oauth2/{authServerId} @@ -70,8 +82,17 @@ function addSharedPrototypes(proto) { }; } +/** + * + * @returns {OktaAuthFactory} + */ function buildOktaAuth(OktaAuthBuilder) { + return function(storageUtil, httpRequestClient) { + /** + * @class + * @param {OktaAuthOptions} args + */ function OktaAuth(args) { if (!(this instanceof OktaAuth)) { return new OktaAuth(args); @@ -100,6 +121,10 @@ function buildOktaAuth(OktaAuthBuilder) { }; } +/** + * @param {OktaAuthOptions} args + * @param {string} sdkValue + */ function getUserAgent(args, sdkValue) { var userAgent = args.userAgent || {}; diff --git a/packages/okta-auth-js/lib/clock.js b/packages/okta-auth-js/lib/clock.js index f1e29122d..9ba477bf6 100644 --- a/packages/okta-auth-js/lib/clock.js +++ b/packages/okta-auth-js/lib/clock.js @@ -1,17 +1,17 @@ -var util = require('./util'); - +/** + * @class + * @param {any} localOffset + */ function SdkClock(localOffset) { // Calculated local clock offset from server time (in milliseconds). Can be positive or negative. this.localOffset = parseInt(localOffset || 0); } -util.extend(SdkClock.prototype, { - // Return the current time (in seconds) - now: function() { - var now = (Date.now() + this.localOffset) / 1000; - return now; - } -}); +// Return the current time (in seconds) +SdkClock.prototype.now = function() { + var now = (Date.now() + this.localOffset) / 1000; + return now; +}; // factory method. Create an instance of a clock from current context. SdkClock.create = function(/* sdk, options */) { diff --git a/packages/okta-auth-js/lib/crypto.js b/packages/okta-auth-js/lib/crypto.js index 9608549eb..83815f582 100644 --- a/packages/okta-auth-js/lib/crypto.js +++ b/packages/okta-auth-js/lib/crypto.js @@ -39,6 +39,7 @@ function verifyToken(idToken, key) { // It's not necessary to properly verify the jwt's signature. delete key.use; + // @ts-ignore return crypto.subtle.importKey( format, key, diff --git a/packages/okta-auth-js/lib/errors/AuthApiError.js b/packages/okta-auth-js/lib/errors/AuthApiError.js index e3aa83dcb..e3aca2249 100644 --- a/packages/okta-auth-js/lib/errors/AuthApiError.js +++ b/packages/okta-auth-js/lib/errors/AuthApiError.js @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and limitations under the License. */ -function AuthApiError(err, xhr) { +function AuthApiError(err, xhr = null) { this.name = 'AuthApiError'; this.message = err.errorSummary; this.errorSummary = err.errorSummary; diff --git a/packages/okta-auth-js/lib/errors/AuthSdkError.js b/packages/okta-auth-js/lib/errors/AuthSdkError.js index 87bbed4ea..1603d9148 100644 --- a/packages/okta-auth-js/lib/errors/AuthSdkError.js +++ b/packages/okta-auth-js/lib/errors/AuthSdkError.js @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and limitations under the License. */ -function AuthSdkError(msg, xhr) { +function AuthSdkError(msg, xhr = null) { this.name = 'AuthSdkError'; this.message = msg; diff --git a/packages/okta-auth-js/lib/fetch/fetchRequest.js b/packages/okta-auth-js/lib/fetch/fetchRequest.js index 4e10c9719..a39fe81f0 100644 --- a/packages/okta-auth-js/lib/fetch/fetchRequest.js +++ b/packages/okta-auth-js/lib/fetch/fetchRequest.js @@ -9,10 +9,11 @@ * * See the License for the specific language governing permissions and limitations under the License. */ - +/** + * @typedef {OktaAuth.HttpRequestor} HttpRequestor + */ var fetch = require('cross-fetch'); - function readData(response) { if (response.headers.get('Content-Type') && response.headers.get('Content-Type').toLowerCase().indexOf('application/json') >= 0) { @@ -43,6 +44,7 @@ function formatResult(status, data) { } /* eslint-disable complexity */ +/** @type {HttpRequestor} */ function fetchRequest(method, url, args) { var body = args.data; var headers = args.headers || {}; @@ -53,6 +55,7 @@ function fetchRequest(method, url, args) { body = JSON.stringify(body); } + // @ts-ignore var fetchPromise = fetch(url, { method: method, headers: args.headers, diff --git a/packages/okta-auth-js/lib/oauthUtil.js b/packages/okta-auth-js/lib/oauthUtil.js index 3832a8c0a..802aa3100 100644 --- a/packages/okta-auth-js/lib/oauthUtil.js +++ b/packages/okta-auth-js/lib/oauthUtil.js @@ -10,6 +10,11 @@ * See the License for the specific language governing permissions and limitations under the License. * */ + +/** + * @typedef {OktaAuth.CustomUrls} CustomUrls + */ + /* global window, document */ /* eslint-disable complexity, max-statements */ var http = require('./http'); @@ -75,13 +80,22 @@ function loadPopup(src, options) { } } -function getWellKnown(sdk, issuer) { +/** + * @param {OktaAuth} sdk + * @param {string=} issuer + */ +function getWellKnown(sdk, issuer = '') { var authServerUri = (issuer || sdk.options.issuer); return http.get(sdk, authServerUri + '/.well-known/openid-configuration', { cacheResponse: true }); } +/** + * @param {OktaAuth} sdk + * @param {string} issuer + * @param {string} kid + */ function getKey(sdk, issuer, kid) { var httpCache = storageUtil.getHttpCache(sdk.options.cookies); @@ -123,6 +137,11 @@ function getKey(sdk, issuer, kid) { }); } +/** + * @param {OktaAuth} sdk + * @param {object} claims + * @param {object} validationParams + */ function validateClaims(sdk, claims, validationParams) { var aud = validationParams.clientId; var iss = validationParams.issuer; @@ -161,7 +180,11 @@ function validateClaims(sdk, claims, validationParams) { } } -function getOAuthUrls(sdk, options) { +/** + * @param {OktaAuth} sdk + * @param {CustomUrls} options + */ +function getOAuthUrls(sdk, options = null) { if (arguments.length > 2) { throw new AuthSdkError('As of version 3.0, "getOAuthUrls" takes only a single set of options'); } diff --git a/packages/okta-auth-js/lib/pkce.js b/packages/okta-auth-js/lib/pkce.js index c3ef0e47d..59ab5670a 100644 --- a/packages/okta-auth-js/lib/pkce.js +++ b/packages/okta-auth-js/lib/pkce.js @@ -10,6 +10,12 @@ * See the License for the specific language governing permissions and limitations under the License. * */ + + /** + * @typedef {OktaAuth.OAuthParams} OAuthParams + * @typedef {OktaAuth.CustomUrls} CustomUrls + */ + /* global crypto */ /* eslint-disable complexity, max-statements */ var AuthSdkError = require('./errors/AuthSdkError'); @@ -41,21 +47,33 @@ function generateVerifier(prefix) { return encodeURIComponent(verifier).slice(0, MAX_VERIFIER_LENGTH); } +/** + * @param {OktaAuth} sdk + */ function getStorage(sdk) { return sdk.options.storageUtil.getPKCEStorage(sdk.options.cookies); } +/** + * @param {OktaAuth} sdk + */ function saveMeta(sdk, meta) { var storage = getStorage(sdk); storage.setStorage(meta); } +/** + * @param {OktaAuth} sdk + */ function loadMeta(sdk) { var storage = getStorage(sdk); var obj = storage.getStorage(); return obj; } +/** + * @param {OktaAuth} sdk + */ function clearMeta(sdk) { var storage = getStorage(sdk); storage.clearStorage(); @@ -71,7 +89,9 @@ function computeChallenge(str) { }); } - +/** + * @param {OAuthParams} oauthOptions + */ function validateOptions(oauthOptions) { // Quick validation if (!oauthOptions.clientId) { @@ -91,6 +111,9 @@ function validateOptions(oauthOptions) { } } +/** + * @param {OAuthParams} options + */ function getPostData(options) { // Convert options to OAuth params var params = util.removeNils({ @@ -105,6 +128,11 @@ function getPostData(options) { } // exchange authorization code for an access token +/** + * @param {OktaAuth} sdk + * @param {OAuthParams} oauthOptions + * @param {CustomUrls} urls + */ function getToken(sdk, oauthOptions, urls) { validateOptions(oauthOptions); var data = getPostData(oauthOptions); diff --git a/packages/okta-auth-js/lib/server/server.js b/packages/okta-auth-js/lib/server/server.js index ea035314e..d8fce41de 100644 --- a/packages/okta-auth-js/lib/server/server.js +++ b/packages/okta-auth-js/lib/server/server.js @@ -11,37 +11,55 @@ */ /* eslint-disable complexity */ /* eslint-disable max-statements */ - +/** + * @typedef {OktaAuth.OktaAuthOptions} OktaAuthOptions + */ var builderUtil = require('../builderUtil'); var SDK_VERSION = require('../../package.json').version; var storage = require('./serverStorage').storage; var tx = require('../tx'); var util = require('../util'); +/** + * @param {OktaAuthOptions} args + */ function OktaAuthBuilder(args) { + /** + * @type {OktaAuth} + */ var sdk = this; builderUtil.assertValidConfig(args); - this.options = { + + /** + * @type {OktaAuthOptions} + */ + var options = { issuer: util.removeTrailingSlash(args.issuer), httpRequestClient: args.httpRequestClient, storageUtil: args.storageUtil, headers: args.headers }; - - this.userAgent = builderUtil.getUserAgent(args, `okta-auth-js-server/${SDK_VERSION}`); + sdk.options = options; + sdk.userAgent = builderUtil.getUserAgent(args, `okta-auth-js-server/${SDK_VERSION}`); sdk.tx = { + introspect: util.bind(tx.introspect, null, sdk), status: util.bind(tx.transactionStatus, null, sdk), resume: util.bind(tx.resumeTransaction, null, sdk), - exists: util.bind(tx.transactionExists, null, sdk) + exists: util.extend(util.bind(tx.transactionExists, null, sdk), { + _get: function(name) { + return storage.get(name); + } + }) }; - sdk.tx.exists._get = function(name) { - return storage.get(name); - }; + } +/** + * @type {OktaAuth} + */ var proto = OktaAuthBuilder.prototype; // { username, password, (relayState), (context) } diff --git a/packages/okta-auth-js/lib/session.js b/packages/okta-auth-js/lib/session.js index be4f08e31..d9665e965 100644 --- a/packages/okta-auth-js/lib/session.js +++ b/packages/okta-auth-js/lib/session.js @@ -10,10 +10,20 @@ * See the License for the specific language governing permissions and limitations under the License. * */ + + +/** + * @typedef {OktaAuth.OAuthParams} OAuthParams + * @typedef {OktaAuth.SessionObject} SessionObject + */ + /* global window */ var util = require('./util'); var http = require('./http'); +/** + * @param {OktaAuth} sdk + */ function sessionExists(sdk) { return sdk.session.get() .then(function(res) { @@ -27,9 +37,14 @@ function sessionExists(sdk) { }); } +/** + * @param {OktaAuth} sdk + * @returns {Promise} + */ function getSession(sdk) { return http.get(sdk, '/api/v1/sessions/me') .then(function(session) { + /** @type {SessionObject} */ var res = util.omit(session, '_links'); res.refresh = function() { @@ -44,10 +59,13 @@ function getSession(sdk) { }) .catch(function() { // Return INACTIVE status on failure - return {status: 'INACTIVE'}; + return /** @type {SessionObject} */({status: 'INACTIVE'}); }); } +/** + * @param {OktaAuth} sdk + */ function closeSession(sdk) { return http.httpRequest(sdk, { url: sdk.getIssuerOrigin() + '/api/v1/sessions/me', @@ -55,18 +73,26 @@ function closeSession(sdk) { }); } +/** + * @param {OktaAuth} sdk + */ function refreshSession(sdk) { return http.post(sdk, '/api/v1/sessions/me/lifecycle/refresh'); } -function setCookieAndRedirect(sdk, sessionToken, redirectUrl) { +/** + * @param {OktaAuth} sdk + * @param {string=} sessionToken + * @param {string=} redirectUrl + */ +function setCookieAndRedirect(sdk, sessionToken = null, redirectUrl = null) { redirectUrl = redirectUrl || window.location.href; - window.location = sdk.getIssuerOrigin() + '/login/sessionCookieRedirect' + + window.location.assign(sdk.getIssuerOrigin() + '/login/sessionCookieRedirect' + util.toQueryParams({ checkAccountSetupComplete: true, token: sessionToken, redirectUrl: redirectUrl - }); + })); } module.exports = { diff --git a/packages/okta-auth-js/lib/storageBuilder.js b/packages/okta-auth-js/lib/storageBuilder.js index cc9112a93..c36a47cd2 100644 --- a/packages/okta-auth-js/lib/storageBuilder.js +++ b/packages/okta-auth-js/lib/storageBuilder.js @@ -11,9 +11,14 @@ * */ + /** + * @typedef {OktaAuth.StorageBuilder} StorageBuilder + */ + var AuthSdkError = require('./errors/AuthSdkError'); // storage must have getItem and setItem +/** @type {StorageBuilder} */ function storageBuilder(webstorage, storageName) { if (typeof storageName !== 'string' || !storageName.length) { throw new AuthSdkError('"storageName" is required'); diff --git a/packages/okta-auth-js/lib/token.js b/packages/okta-auth-js/lib/token.js index 0aa6e2da9..bc5cb134b 100644 --- a/packages/okta-auth-js/lib/token.js +++ b/packages/okta-auth-js/lib/token.js @@ -10,6 +10,11 @@ * See the License for the specific language governing permissions and limitations under the License. * */ + +/** + * @typedef {OktaAuth.OAuthParams} OAuthParams + */ + /* global window, document, btoa */ /* eslint-disable complexity, max-statements */ var http = require('./http'); @@ -314,7 +319,10 @@ function handleOAuthResponse(sdk, oauthParams, res, urls) { }; }); } - +/** + * @param {OktaAuth} sdk + * @returns {OAuthParams} + */ function getDefaultOAuthParams(sdk) { return { pkce: sdk.options.pkce, diff --git a/packages/okta-auth-js/lib/tx.js b/packages/okta-auth-js/lib/tx.js index 5a6c60645..be5406f6e 100644 --- a/packages/okta-auth-js/lib/tx.js +++ b/packages/okta-auth-js/lib/tx.js @@ -52,6 +52,9 @@ function resumeTransaction(sdk, args) { } return sdk.tx.status(args) .then(function(res) { + /** + * @type {AuthTransaction} + */ return new AuthTransaction(sdk, res); }); } @@ -357,7 +360,7 @@ function flattenEmbedded(sdk, res, obj, ref) { return obj; } -function AuthTransaction(sdk, res) { +function AuthTransaction(sdk, res = null) { if (res) { this.data = res; util.extend(this, flattenEmbedded(sdk, res, res, {})); @@ -369,7 +372,9 @@ function AuthTransaction(sdk, res) { // when OKTA-75434 is resolved if (res.status === 'RECOVERY_CHALLENGE' && !res._links) { this.cancel = function() { - return Promise.resolve(new AuthTransaction(sdk)); + return Promise.resolve( + new AuthTransaction(sdk) + ); }; } } diff --git a/packages/okta-auth-js/package.json b/packages/okta-auth-js/package.json index 79e3c826a..5b4e7537a 100644 --- a/packages/okta-auth-js/package.json +++ b/packages/okta-auth-js/package.json @@ -11,12 +11,14 @@ "type": "git", "url": "https://github.com/okta/okta-auth-js.git" }, + "types": "types/index.d.ts", "bugs": { "url": "https://github.com/okta/okta-auth-js/issues" }, "scripts": { - "lint": "eslint .", - "lint:report": "eslint -f checkstyle -o ../../build2/reports/lint/eslint-checkstyle-result.xml .", + "lint": "eslint . --ext .js --ext .ts", + "lint:report": "eslint -f checkstyle -o ../../build2/reports/lint/eslint-checkstyle-result.xml . --ext .js --ext .ts", + "validate": "yarn lint && yarn tsc", "test": "yarn test:karma && yarn test:browser && yarn test:server", "test:karma": "karma start --single-run", "test:browser": "jest --config ./jest.browser.js", @@ -54,11 +56,16 @@ "@babel/core": "^7.8.0", "@babel/plugin-transform-runtime": "^7.8.3", "@babel/preset-env": "^7.8.2", + "@types/jest": "^25.2.3", + "@types/node": "^14.0.3", + "@typescript-eslint/eslint-plugin": "^2.34.0", + "@typescript-eslint/parser": "^2.34.0", "babel-jest": "^24.9.0", "babel-loader": "^8.0.6", "eslint": "5.6.1", "eslint-plugin-compat": "^3.3.0", "eslint-plugin-jasmine": "^2.10.1", + "eslint-plugin-jsdoc": "^25.4.2", "istanbul-instrumenter-loader": "^3.0.1", "jasmine-ajax": "^4.0.0", "jest": "^24.9.0", @@ -73,6 +80,7 @@ "karma-webpack": "^3.0.5", "lodash": "4.17.11", "promise.allsettled": "^1.0.1", + "typescript": "^3.9.2", "webpack": "^3.0.0" }, "jest-junit": { diff --git a/packages/okta-auth-js/test/spec/browser.js b/packages/okta-auth-js/test/spec/browser.js index 530f50c2a..416a48795 100644 --- a/packages/okta-auth-js/test/spec/browser.js +++ b/packages/okta-auth-js/test/spec/browser.js @@ -11,13 +11,13 @@ describe('Browser', function() { let originalLocation; afterEach(() => { - global.window.location = originalLocation; + window.location = originalLocation; }); beforeEach(function() { - originalLocation = global.window.location; - delete global.window.location; - global.window.location = { + originalLocation = window.location; + delete window.location; + /** @type {any} */(window).location = { protocol: 'https:', hostname: 'somesite.local' }; @@ -36,7 +36,7 @@ describe('Browser', function() { jest.spyOn(OktaAuth.prototype, '_onTokenManagerError'); var auth = new OktaAuth({ issuer: 'http://localhost/fake', pkce: false }); expect(Emitter.prototype.on).toHaveBeenCalledWith('error', auth._onTokenManagerError, auth); - var emitter = Emitter.prototype.on.mock.instances[0]; + var emitter = /** @type {any} */(Emitter).prototype.on.mock.instances[0]; var error = { errorCode: 'anything'}; emitter.emit('error', error); expect(OktaAuth.prototype._onTokenManagerError).toHaveBeenCalledWith(error); @@ -46,7 +46,7 @@ describe('Browser', function() { var onSessionExpired = jest.fn(); jest.spyOn(Emitter.prototype, 'on'); new OktaAuth({ issuer: 'http://localhost/fake', pkce: false, onSessionExpired: onSessionExpired }); - var emitter = Emitter.prototype.on.mock.instances[0]; + var emitter = /** @type {any} */(Emitter).prototype.on.mock.instances[0]; expect(onSessionExpired).not.toHaveBeenCalled(); var error = { errorCode: 'login_required', accessToken: true }; emitter.emit('error', error); @@ -57,7 +57,7 @@ describe('Browser', function() { var onSessionExpired = jest.fn(); jest.spyOn(Emitter.prototype, 'on'); new OktaAuth({ issuer: 'http://localhost/fake', pkce: false, onSessionExpired: onSessionExpired }); - var emitter = Emitter.prototype.on.mock.instances[0]; + var emitter = /** @type {any} */(Emitter).prototype.on.mock.instances[0]; expect(onSessionExpired).not.toHaveBeenCalled(); var error = { errorCode: 'login_required' }; emitter.emit('error', error); @@ -68,7 +68,7 @@ describe('Browser', function() { var onSessionExpired = jest.fn(); jest.spyOn(Emitter.prototype, 'on'); new OktaAuth({ issuer: 'http://localhost/fake', pkce: false, onSessionExpired: onSessionExpired }); - var emitter = Emitter.prototype.on.mock.instances[0]; + var emitter = /** @type {any} */(Emitter).prototype.on.mock.instances[0]; expect(onSessionExpired).not.toHaveBeenCalled(); var error = { errorCode: 'unknown', accessToken: true }; emitter.emit('error', error); @@ -115,7 +115,7 @@ describe('Browser', function() { }); it('does not throw if running on HTTP and cookies.secure = false', () => { - global.window.location.protocol = 'http:'; + window.location.protocol = 'http:'; window.location.hostname = 'not-localhost'; function fn() { auth = new OktaAuth({ cookies: { secure: false }, issuer: 'http://my-okta-domain', pkce: false }); @@ -223,7 +223,7 @@ describe('Browser', function() { beforeEach(function() { origin = 'https://somesite.local'; encodedOrigin = encodeURIComponent(origin); - Object.assign(global.window.location, { + Object.assign(window.location, { origin, assign: jest.fn(), reload: jest.fn() diff --git a/packages/okta-auth-js/test/spec/browserStorage.js b/packages/okta-auth-js/test/spec/browserStorage.js index 7780b8e4d..bea13b8a3 100644 --- a/packages/okta-auth-js/test/spec/browserStorage.js +++ b/packages/okta-auth-js/test/spec/browserStorage.js @@ -1,3 +1,6 @@ +/** + * @typedef {OktaAuth.SimpleStorage} SimpleStorage + */ jest.mock('../../lib/storageBuilder'); var browserStorage = require('../../lib/browser/browserStorage'); @@ -59,7 +62,7 @@ describe('browserStorage', () => { }); it('returns false if sessionStorage does not exist', () => { - delete window.sessionStorage; + delete /** @type {any} */(window).sessionStorage; expect(browserStorage.browserHasSessionStorage()).toBe(false); }); @@ -124,11 +127,11 @@ describe('browserStorage', () => { }); it('Uses cookie storage if localStorage and sessionStorage are not available', () => { - delete global.window.localStorage; - delete global.window.sessionStorage; - const fakeStorage = { fakeStorage: true }; + delete /** @type {any} */(window).localStorage; + delete /** @type {any} */(window).sessionStorage; + const fakeStorage = { getItem: jest.fn(), setItem: jest.fn() }; jest.spyOn(browserStorage, 'getCookieStorage').mockReturnValue(fakeStorage); - const opts = { fakeOptions: true }; + const opts = { secure: true }; browserStorage.getPKCEStorage(opts); expect(storageBuilder).toHaveBeenCalledWith(fakeStorage, 'okta-pkce-storage'); expect(browserStorage.getCookieStorage).toHaveBeenCalledWith(opts); @@ -147,7 +150,7 @@ describe('browserStorage', () => { }); it('Uses sessionStorage if localStorage is not available', () => { - delete global.window.localStorage; + delete /** @type {any} */(window).localStorage; browserStorage.getHttpCache(); expect(storageBuilder).toHaveBeenCalledTimes(1); // .toHaveBeenCalledWith doesn't do a strict comparison, so an empty localStorage reads the same as an empty sessionStorage @@ -156,11 +159,12 @@ describe('browserStorage', () => { }); it('Uses cookie storage if localStorage and sessionStorage are not available', () => { - delete global.window.localStorage; - delete global.window.sessionStorage; + delete /** @type {any} */(window).localStorage; + delete /** @type {any} */(window).sessionStorage; const fakeStorage = { fakeStorage: true }; + // @ts-ignore jest.spyOn(browserStorage, 'getCookieStorage').mockReturnValue(fakeStorage); - const opts = { fakeOptions: true }; + const opts = { secure: true }; browserStorage.getHttpCache(opts); expect(storageBuilder).toHaveBeenCalledWith(fakeStorage, 'okta-cache-storage'); expect(browserStorage.getCookieStorage).toHaveBeenCalledWith(opts); @@ -199,7 +203,7 @@ describe('browserStorage', () => { }); it('getItem: will call storage.get', () => { - const retVal = { fakeCookie: true }; + const retVal = JSON.stringify({ fakeCookie: true }); jest.spyOn(browserStorage.storage, 'get').mockReturnValue(retVal); const storage = browserStorage.getCookieStorage({ secure: true, sameSite: 'strict' }); const key = 'fake-key'; @@ -209,12 +213,12 @@ describe('browserStorage', () => { it('setItem: will call storage.set, passing secure and sameSite options', () => { jest.spyOn(browserStorage.storage, 'set').mockReturnValue(null); - const storage = browserStorage.getCookieStorage({ secure: 'fakey', sameSite: 'strictly fakey' }); + const storage = browserStorage.getCookieStorage({ secure: false, sameSite: 'strictly fakey' }); const key = 'fake-key'; const val = { fakeValue: true }; storage.setItem(key, val); expect(browserStorage.storage.set).toHaveBeenCalledWith(key, val, '2200-01-01T00:00:00.000Z', { - secure: 'fakey', + secure: false, sameSite: 'strictly fakey' }); }) diff --git a/packages/okta-auth-js/test/spec/builderUtil.js b/packages/okta-auth-js/test/spec/builderUtil.js index b1e717644..95a7b3c09 100644 --- a/packages/okta-auth-js/test/spec/builderUtil.js +++ b/packages/okta-auth-js/test/spec/builderUtil.js @@ -9,7 +9,7 @@ describe('builderUtil', () => { value: 'fake userAgent' } }; - const userAgent = builderUtil.getUserAgent(args); + const userAgent = builderUtil.getUserAgent(args, 'does not matter'); expect(userAgent).toEqual('fake userAgent'); }); it('should replace "$OKTA_AUTH_JS" with current authJs user agent value if only with userAgentTemplate in args', () => { @@ -22,17 +22,17 @@ describe('builderUtil', () => { const userAgent = builderUtil.getUserAgent(args, sdkUserAgentValue); expect(userAgent).toEqual(`fake userAgent okta-auth-js/0.0.0`); }); - it('should return undefined if no userAgent object is in args', () => { + it('should return default value if no userAgent object is in args', () => { const args = {}; - const userAgent = builderUtil.getUserAgent(args); - expect(userAgent).toEqual(undefined); + const userAgent = builderUtil.getUserAgent(args, 'default'); + expect(userAgent).toEqual('default'); }); - it('should return undefined if neither with userAgent.value nor userAgent.template in args', () => { + it('should return default vaule if neither with userAgent.value nor userAgent.template in args', () => { const args = { userAgent: {} }; - const userAgent = builderUtil.getUserAgent(args); - expect(userAgent).toEqual(undefined); + const userAgent = builderUtil.getUserAgent(args, 'default'); + expect(userAgent).toEqual('default'); }); it('should return sdk defined user agent if no userAgent object is in args', () => { const args = {}; diff --git a/packages/okta-auth-js/test/spec/features.js b/packages/okta-auth-js/test/spec/features.js index 99b174dda..b8125b4fe 100644 --- a/packages/okta-auth-js/test/spec/features.js +++ b/packages/okta-auth-js/test/spec/features.js @@ -45,10 +45,10 @@ describe('features', function() { describe('isTokenVerifySupported', function() { beforeEach(function() { - window.crypto = { + /** @type {any} */(window).crypto = { subtle: true }; - window.Uint8Array = true; + /** @type {any} */(window).Uint8Array = true; }); it('can succeed', function() { @@ -56,7 +56,7 @@ describe('features', function() { }) it('fails if no crypto', function() { - window.crypto = undefined; + /** @type {any} */(window).crypto = undefined; expect(OktaAuth.features.isTokenVerifySupported()).toBe(false); }); @@ -68,7 +68,7 @@ describe('features', function() { describe('hasTextEncoder', function() { it('returns true if TextEncoder is defined', function() { - window.TextEncoder = true; + /** @type {any} */(window).TextEncoder = true; expect(OktaAuth.features.hasTextEncoder()).toBe(true); }); it('returns false if TextEncoder is undefined', function() { @@ -79,11 +79,11 @@ describe('features', function() { describe('isPKCESupported', function() { beforeEach(function() { - window.crypto = { + /** @type {any} */(window).crypto = { subtle: true }; - window.Uint8Array = true; - window.TextEncoder = true; + /** @type {any} */(window).Uint8Array = true; + /** @type {any} */(window).TextEncoder = true; }); it('can succeed', function() { @@ -91,7 +91,7 @@ describe('features', function() { }) it('fails if no crypto', function() { - window.crypto = undefined; + /** @type {any} */(window).crypto = undefined; expect(OktaAuth.features.isPKCESupported()).toBe(false); }); diff --git a/packages/okta-auth-js/test/spec/fetch-request.js b/packages/okta-auth-js/test/spec/fetch-request.js index d9052d363..5f80d921f 100644 --- a/packages/okta-auth-js/test/spec/fetch-request.js +++ b/packages/okta-auth-js/test/spec/fetch-request.js @@ -1,4 +1,7 @@ /* global Map */ +/** + * @typedef {OktaAuth.HttpRequestor} HttpRequestor + */ describe('fetchRequest', function () { let fetchSpy; @@ -18,7 +21,8 @@ describe('fetchRequest', function () { jest.setMock('cross-fetch', function() { return mockFetchObj.fetch.apply(null, arguments); }); - const fetchRequest = require('../../lib/fetch/fetchRequest'); + + var fetchRequest = /** @type {HttpRequestor} */(require('../../lib/fetch/fetchRequest')); beforeEach(function() { fetchSpy = jest.spyOn(mockFetchObj, 'fetch'); diff --git a/packages/okta-auth-js/test/spec/fingerprint.js b/packages/okta-auth-js/test/spec/fingerprint.js index 8bc2a91c4..3db717895 100644 --- a/packages/okta-auth-js/test/spec/fingerprint.js +++ b/packages/okta-auth-js/test/spec/fingerprint.js @@ -45,8 +45,8 @@ describe('fingerprint', function() { }); jest.spyOn(document, 'createElement').mockReturnValue(test.iframe); jest.spyOn(document.body, 'contains').mockReturnValue(true); - jest.spyOn(document.body, 'appendChild').mockImplementation(function() { - if (options.timeout) { return; } + jest.spyOn(document.body, 'appendChild').mockImplementation(function(newChild) { + if (options.timeout) { return newChild; } // mimic async page load with setTimeouts if (options.sendOtherMessage) { setTimeout(function() { @@ -66,6 +66,7 @@ describe('fingerprint', function() { postMessage: postMessageSpy } }); + return newChild; }); }); diff --git a/packages/okta-auth-js/test/spec/oauthUtil.js b/packages/okta-auth-js/test/spec/oauthUtil.js index 2cb1dbea2..a0d63a0fc 100644 --- a/packages/okta-auth-js/test/spec/oauthUtil.js +++ b/packages/okta-auth-js/test/spec/oauthUtil.js @@ -323,7 +323,7 @@ describe('getWellKnown', function() { setup: { beforeClient: function() { delete window.location; - window.location = { + /** @type {any} */(window).location = { protocol: 'https:' } oauthUtilHelpers.mockLocalStorageError(); @@ -683,7 +683,7 @@ describe('getOAuthUrls', function() { describe('loadPopup', function() { it('popups window with full src url directly when none-IE', function () { - var mockElem = {}; + var mockElem = /** @type {Window} */({}); jest.spyOn(libUtil, 'isIE11OrLess').mockReturnValue(false); jest.spyOn(window, 'open').mockReturnValue(mockElem); @@ -692,7 +692,7 @@ describe('loadPopup', function() { }); expect(winEl).toBe(mockElem); - expect(window.open.mock.calls.length).toBe(1); + expect(/** @type {any} */(window).open.mock.calls.length).toBe(1); expect(window.open).toHaveBeenCalledWith( '/path/to/foo', 'Hello Okta', @@ -701,14 +701,14 @@ describe('loadPopup', function() { }); it('popups window with full src url directly and default title', function () { - var mockElem = {}; + var mockElem = /** @type {Window} */({}); jest.spyOn(libUtil, 'isIE11OrLess').mockReturnValue(false); jest.spyOn(window, 'open').mockReturnValue(mockElem); var winEl = oauthUtil.loadPopup('/path/to/foo', {}); expect(winEl).toBe(mockElem); - expect(window.open.mock.calls.length).toBe(1); + expect(/** @type {any} */(window).open.mock.calls.length).toBe(1); expect(window.open).toHaveBeenCalledWith( '/path/to/foo', 'External Identity Provider User Authentication', @@ -717,11 +717,11 @@ describe('loadPopup', function() { }); it('popups window with full src url directly when IE mode', function () { - var mockElem = { + var mockElem = /** @type {Window} */({ location: { } - }; + }); jest.spyOn(libUtil, 'isIE11OrLess').mockReturnValue(true); jest.spyOn(window, 'open').mockReturnValue(mockElem); @@ -730,7 +730,7 @@ describe('loadPopup', function() { }); expect(winEl).toBe(mockElem); - expect(window.open.mock.calls.length).toBe(1); + expect(/** @type {any} */(window).open.mock.calls.length).toBe(1); expect(window.open).toHaveBeenCalledWith( '/', 'Hello Okta', diff --git a/packages/okta-auth-js/test/spec/pkce.js b/packages/okta-auth-js/test/spec/pkce.js index bfd62dc7d..e27a0163e 100644 --- a/packages/okta-auth-js/test/spec/pkce.js +++ b/packages/okta-auth-js/test/spec/pkce.js @@ -153,26 +153,25 @@ describe('pkce', function() { describe('validateOptions', function() { var authClient; var oauthOptions; - + var urls; beforeEach(function() { spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(true); authClient = new OktaAuth({ issuer: 'https://auth-js-test.okta.com' }); - oauthOptions = { clientId: CLIENT_ID, redirectUri: REDIRECT_URI, authorizationCode: authorizationCode, codeVerifier: codeVerifier, }; + urls = { + tokenUrl: 'http://superfake' + }; }); it('Does not throw if options are valid', function() { var httpRequst = jest.spyOn(http, 'httpRequest').mockImplementation(); - var urls = { - tokenUrl: 'http://superfake' - }; pkce.getToken(authClient, oauthOptions, urls); expect(httpRequst).toHaveBeenCalled(); }); @@ -180,7 +179,7 @@ describe('pkce', function() { it('Throws if no clientId', function() { oauthOptions.clientId = undefined; try { - pkce.getToken(authClient, oauthOptions); + pkce.getToken(authClient, oauthOptions, urls); } catch(e) { expect(e instanceof AuthSdkError).toBe(true); expect(e.message).toBe('A clientId must be specified in the OktaAuth constructor to get a token'); @@ -190,7 +189,7 @@ describe('pkce', function() { it('Throws if no redirectUri', function() { oauthOptions.redirectUri = undefined; try { - pkce.getToken(authClient, oauthOptions); + pkce.getToken(authClient, oauthOptions, urls); } catch(e) { expect(e instanceof AuthSdkError).toBe(true); expect(e.message).toBe('The redirectUri passed to /authorize must also be passed to /token'); @@ -200,7 +199,7 @@ describe('pkce', function() { it('Throws if no authorizationCode', function() { oauthOptions.authorizationCode = undefined; try { - pkce.getToken(authClient, oauthOptions); + pkce.getToken(authClient, oauthOptions, urls); } catch(e) { expect(e instanceof AuthSdkError).toBe(true); expect(e.message).toBe('An authorization code (returned from /authorize) must be passed to /token'); @@ -210,7 +209,7 @@ describe('pkce', function() { it('Throws if no codeVerifier', function() { oauthOptions.codeVerifier = undefined; try { - pkce.getToken(authClient, oauthOptions); + pkce.getToken(authClient, oauthOptions, urls); } catch(e) { expect(e instanceof AuthSdkError).toBe(true); expect(e.message).toBe('The "codeVerifier" (generated and saved by your app) must be passed to /token'); diff --git a/packages/okta-auth-js/test/spec/session.js b/packages/okta-auth-js/test/spec/session.js index 0b53e9ef1..341774701 100644 --- a/packages/okta-auth-js/test/spec/session.js +++ b/packages/okta-auth-js/test/spec/session.js @@ -157,6 +157,7 @@ describe('session', function() { }); return session.getSession(sdk) .then(function(res) { + // @ts-ignore http.get.mockReset(); jest.spyOn(http, 'get').mockReturnValue(null); res.user(); @@ -210,25 +211,27 @@ describe('session', function() { beforeEach(function() { currentUrl = 'http://i-am-here'; delete window.location; + // @ts-ignore window.location = { - href: currentUrl + href: currentUrl, + assign: jest.fn() }; }); it('redirects to /login/sessionCookieRedirect', function() { session.setCookieAndRedirect(sdk); - expect(window.location).toBe(baseUrl + '/login/sessionCookieRedirect?checkAccountSetupComplete=true&redirectUrl=' + encodeURIComponent(currentUrl)); + expect(window.location.assign).toHaveBeenCalledWith(baseUrl + '/login/sessionCookieRedirect?checkAccountSetupComplete=true&redirectUrl=' + encodeURIComponent(currentUrl)); }); it('can pass a sessionToken', function() { var sessionToken = 'blah-blah'; session.setCookieAndRedirect(sdk, sessionToken); - expect(window.location).toBe(baseUrl + '/login/sessionCookieRedirect?checkAccountSetupComplete=true&token=' + + expect(window.location.assign).toHaveBeenCalledWith(baseUrl + '/login/sessionCookieRedirect?checkAccountSetupComplete=true&token=' + encodeURIComponent(sessionToken) + '&redirectUrl=' + encodeURIComponent(currentUrl)); }); it('can pass a redirectUrl', function() { var sessionToken = 'blah-blah'; var redirectUrl = 'http://go-here-now'; session.setCookieAndRedirect(sdk, sessionToken, redirectUrl); - expect(window.location).toBe(baseUrl + '/login/sessionCookieRedirect?checkAccountSetupComplete=true&token=' + + expect(window.location.assign).toHaveBeenCalledWith(baseUrl + '/login/sessionCookieRedirect?checkAccountSetupComplete=true&token=' + encodeURIComponent(sessionToken) + '&redirectUrl=' + encodeURIComponent(redirectUrl)); }); }); diff --git a/packages/okta-auth-js/test/spec/storageBuilder.js b/packages/okta-auth-js/test/spec/storageBuilder.js index 6343d0f22..577f3589a 100644 --- a/packages/okta-auth-js/test/spec/storageBuilder.js +++ b/packages/okta-auth-js/test/spec/storageBuilder.js @@ -4,13 +4,13 @@ describe('storageBuilder', () => { it('throws if a storagename is not provided', () => { const fn = function() { - storageBuilder(); + storageBuilder(null, null); }; expect(fn).toThrowError('"storageName" is required'); }); it('Returns an interface around a storage object', () => { - const storage = storageBuilder({}, 'fake'); + const storage = storageBuilder({ getItem: jest.fn(), setItem: jest.fn() }, 'fake'); expect(typeof storage.getStorage).toBe('function'); expect(typeof storage.setStorage).toBe('function'); expect(typeof storage.clearStorage).toBe('function'); @@ -20,7 +20,8 @@ describe('storageBuilder', () => { describe('getStorage', () => { it('Calls "getItem" on the inner storage', () => { const inner = { - getItem: jest.fn() + getItem: jest.fn(), + setItem: jest.fn() }; const storageName = 'fake'; const storage = storageBuilder(inner, storageName); @@ -30,7 +31,8 @@ describe('storageBuilder', () => { it('JSON decodes the returned object', () => { const obj = { fakeObject: true }; const inner = { - getItem: jest.fn().mockReturnValue(JSON.stringify(obj)) + getItem: jest.fn().mockReturnValue(JSON.stringify(obj)), + setItem: jest.fn() }; const storageName = 'fake'; const storage = storageBuilder(inner, storageName); @@ -38,7 +40,8 @@ describe('storageBuilder', () => { }); it('throws if object cannot be decoded', () => { const inner = { - getItem: jest.fn().mockReturnValue('a string that wont decode') + getItem: jest.fn().mockReturnValue('a string that wont decode'), + setItem: jest.fn() }; const storageName = 'fake'; const storage = storageBuilder(inner, storageName); @@ -52,6 +55,7 @@ describe('storageBuilder', () => { describe('setStorage', () => { it('Calls "setItem" on the inner storage', () => { const inner = { + getItem: jest.fn(), setItem: jest.fn() }; const storageName = 'fake'; @@ -61,6 +65,7 @@ describe('storageBuilder', () => { }); it('JSON stringifies the object passed to inner storage', () => { const inner = { + getItem: jest.fn(), setItem: jest.fn() }; const storageName = 'fake'; @@ -71,6 +76,7 @@ describe('storageBuilder', () => { }); it('Throws an error if setItem throws', () => { const inner = { + getItem: jest.fn(), setItem: jest.fn().mockImplementation(() => { throw new Error('this error will be caught'); }) @@ -87,6 +93,7 @@ describe('storageBuilder', () => { describe('clearStorage', () => { it('if no key is passed, it will set storage to an empty object', () => { const inner = { + getItem: jest.fn(), setItem: jest.fn() }; const storageName = 'fake'; diff --git a/packages/okta-auth-js/test/spec/token.js b/packages/okta-auth-js/test/spec/token.js index 5f97c2f5e..3140de080 100644 --- a/packages/okta-auth-js/test/spec/token.js +++ b/packages/okta-auth-js/test/spec/token.js @@ -15,6 +15,7 @@ var pkce = require('../../lib/pkce'); var http = require('../../lib/http'); var sdkCrypto = require('../../lib/crypto'); +/** @returns {OktaAuth} */ function setupSync(options) { options = Object.assign({ issuer: 'http://example.okta.com', pkce: false }, options); return new OktaAuth(options); @@ -35,14 +36,14 @@ var secureCookieSettings = { describe('token.revoke', function() { it('throws if token is not passed', function() { var oa = setupSync(); - return oa.token.revoke() + return oa.token.revoke(null) .catch(function(err) { util.assertAuthSdkError(err, 'A valid access token object is required'); }); }); it('throws if invalid token is passed', function() { var oa = setupSync(); - var accessToken = { foo: 'bar' }; + var accessToken = /** @type {any} */({ foo: 'bar' }); return oa.token.revoke(accessToken) .catch(function(err) { util.assertAuthSdkError(err, 'A valid access token object is required'); @@ -50,7 +51,7 @@ describe('token.revoke', function() { }); it('throws if clientId is not set', function() { var oa = setupSync(); - var accessToken = { accessToken: 'fake' }; + var accessToken = { accessToken: 'fake', expiresAt: 0 }; return oa.token.revoke(accessToken) .catch(function(err) { util.assertAuthSdkError(err, 'A clientId must be specified in the OktaAuth constructor to revoke a token'); @@ -60,7 +61,7 @@ describe('token.revoke', function() { spyOn(http, 'post').and.returnValue(Promise.resolve()); var clientId = 'fake-client-id'; var oa = setupSync({ clientId: clientId }); - var accessToken = { accessToken: 'fake/ &token' }; + var accessToken = { accessToken: 'fake/ &token', expiresAt: 0 }; return oa.token.revoke(accessToken) .then(function() { expect(http.post).toHaveBeenCalledWith(oa, @@ -80,7 +81,7 @@ describe('token.revoke', function() { }); var clientId = 'fake-client-id'; var oa = setupSync({ clientId: clientId }); - var accessToken = { accessToken: 'fake/ &token' }; + var accessToken = { accessToken: 'fake/ &token', expiresAt: 0 }; return oa.token.revoke(accessToken) .catch(function(err) { expect(err).toBe(testError); @@ -860,6 +861,7 @@ describe('token.getWithPopup', function() { closed: false, close: jest.fn() }; + // @ts-ignore jest.spyOn(window, 'open').mockImplementation(function () { return mockWindow; // valid window is returned }); @@ -1294,14 +1296,15 @@ describe('token.getWithRedirect', function() { var originalLocation; afterEach(() => { - global.window.location = originalLocation; + window.location = originalLocation; }); beforeEach(function() { // mock window.location so we appear to be on an HTTPS origin - originalLocation = global.window.location; - delete global.window.location; - global.window.location = { + originalLocation = window.location; + delete window.location; + // @ts-ignore + window.location = { protocol: 'https:', hostname: 'somesite.local' }; @@ -1352,6 +1355,7 @@ describe('token.getWithRedirect', function() { } it('Uses insecure cookie settings if running on http://localhost', function() { delete window.location; + // @ts-ignore window.location = { protocol: 'http:', hostname: 'localhost' @@ -2907,7 +2911,8 @@ describe('token.getUserInfo', function() { return Promise.resolve(setupSync()) .then(function(oa) { jest.spyOn(oa.tokenManager, 'get').mockReturnValue(Promise.resolve()); - return oa.token.getUserInfo('just a string'); + var stringToken = /** @type {any} */('just a string'); + return oa.token.getUserInfo(stringToken); }) .then(function() { expect('not to be hit').toBe(true); @@ -2937,7 +2942,8 @@ describe('token.getUserInfo', function() { return Promise.resolve(setupSync()) .then(function(oa) { jest.spyOn(oa.tokenManager, 'get').mockReturnValue(Promise.resolve()); - return oa.token.getUserInfo(tokens.standardAccessTokenParsed, 'some string'); + var stringToken = /** @type {any} */('some string') + return oa.token.getUserInfo(tokens.standardAccessTokenParsed, stringToken); }) .then(function() { expect('not to be hit').toBe(true); @@ -3021,7 +3027,7 @@ describe('token.verify', function() { // Mock out sdk crypto jest.spyOn(client.features, 'isTokenVerifySupported').mockReturnValue(true); - jest.spyOn(sdkCrypto, 'verifyToken').mockReturnValue(true); + jest.spyOn(sdkCrypto, 'verifyToken').mockReturnValue(Promise.resolve(true)); jest.spyOn(sdkCrypto, 'getOidcHash').mockReturnValue(Promise.resolve(atHash)); // Return modified idToken diff --git a/packages/okta-auth-js/test/spec/tokenManager.js b/packages/okta-auth-js/test/spec/tokenManager.js index cb49e4ed3..85de22c13 100644 --- a/packages/okta-auth-js/test/spec/tokenManager.js +++ b/packages/okta-auth-js/test/spec/tokenManager.js @@ -1,10 +1,11 @@ /* global window, localStorage, sessionStorage */ - // Promise.allSettled is added in Node 12.10 var allSettled = require('promise.allsettled'); allSettled.shim(); // will be a no-op if not needed var Emitter = require('tiny-emitter'); + +// @ts-ignore var OktaAuth = require('OktaAuth'); var tokens = require('@okta/test.support/tokens'); var util = require('@okta/test.support/util'); @@ -17,6 +18,9 @@ var secureCookieSettings = { sameSite: 'none' }; +/** + * @returns {OktaAuth} + */ function setupSync(options) { options = options || {}; options.tokenManager = options.tokenManager || {}; @@ -43,15 +47,16 @@ describe('TokenManager', function() { sessionStorage.clear(); // Mock window.location so we appear to be on an HTTPS origin - originalLocation = global.window.location; - delete global.window.location; - global.window.location = { + originalLocation = window.location; + delete window.location; + // @ts-ignore + window.location = { protocol: 'https:', hostname: 'somesite.local' }; }); afterEach(function() { - global.window.location = originalLocation; + window.location = originalLocation; jest.useRealTimers(); }); @@ -61,6 +66,7 @@ describe('TokenManager', function() { var client = setupSync(); var handlerFn = jest.fn(); client.tokenManager.on('fake', handlerFn); + // @ts-ignore var emitter = Emitter.prototype.on.mock.instances[0]; expect(emitter).toBe(client.emitter); emitter.emit('fake'); @@ -246,6 +252,7 @@ describe('TokenManager', function() { it('throws an error when attempting to add a non-token', function() { var client = setupSync(); try { + // @ts-ignore client.tokenManager.add('test-idToken', [ tokens.standardIdTokenParsed, tokens.standardIdTokenParsed diff --git a/packages/okta-auth-js/tsconfig.json b/packages/okta-auth-js/tsconfig.json new file mode 100644 index 000000000..c300cc29c --- /dev/null +++ b/packages/okta-auth-js/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "composite": true, + "module": "commonjs", + "target": "es5", + "allowJs": true, + "checkJs": true, + "moduleResolution": "node", + "alwaysStrict": true, + "strictNullChecks": false, + "noEmit": false, + "declaration": true, + "outDir": "./build2/types", + "resolveJsonModule": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": false, + "rootDir": "../../", + "baseUrl": "./", + "paths": { + "OktaAuth": ["./lib/browser/browserIndex"] + } + }, + "files": [ + "package.json" + ], + "include": [ + "types/**/*.ts", + "lib/**/*.js", + "test/spec/**/*.js" + ], + "exclude": [ + "node_modules", + "build2" + ] +} diff --git a/packages/okta-auth-js/types/events.d.ts b/packages/okta-auth-js/types/events.d.ts new file mode 100644 index 000000000..8bdf0ec6f --- /dev/null +++ b/packages/okta-auth-js/types/events.d.ts @@ -0,0 +1,12 @@ + + +declare namespace OktaAuth { + interface EventSource { + on(event: string, handler: Function, context?: object): void; + off(event: string, handler: Function): void; + } + + interface EventEmitter extends EventSource { + emit(event: string, arg1?: any, arg2?: any): void; + } +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/features.d.ts b/packages/okta-auth-js/types/features.d.ts new file mode 100644 index 000000000..756d5420d --- /dev/null +++ b/packages/okta-auth-js/types/features.d.ts @@ -0,0 +1,11 @@ +declare namespace OktaAuth { + interface FeaturesAPI { + isLocalhost(): boolean; + isHTTPS(): boolean; + isFingerprintSupported(): boolean; + isPopupPostMessageSupported(): boolean; + hasTextEncoder(): boolean; + isTokenVerifySupported(): boolean; + isPKCESupported(): boolean; + } +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/fingerprint.d.ts b/packages/okta-auth-js/types/fingerprint.d.ts new file mode 100644 index 000000000..d89c7dd33 --- /dev/null +++ b/packages/okta-auth-js/types/fingerprint.d.ts @@ -0,0 +1,6 @@ +declare namespace OktaAuth { + type FingerprintFunction = (options?: object) => Promise; + interface FingerprintAPI extends FingerprintFunction { + _getUserAgent: () => string; + } +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/global.d.ts b/packages/okta-auth-js/types/global.d.ts new file mode 100644 index 000000000..db5f891e5 --- /dev/null +++ b/packages/okta-auth-js/types/global.d.ts @@ -0,0 +1,13 @@ +declare interface PromiseConstructor { + // eslint-disable-next-line max-len, @typescript-eslint/member-delimiter-style + allSettled(promises: Array>): Promise>; +} + +declare interface Node { + tagName: string; + src: string; +} + +declare interface Document { + documentMode: number; +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/http-requestor.d.ts b/packages/okta-auth-js/types/http-requestor.d.ts new file mode 100644 index 000000000..e639f5fe6 --- /dev/null +++ b/packages/okta-auth-js/types/http-requestor.d.ts @@ -0,0 +1,3 @@ +declare namespace OktaAuth { + type HttpRequestor = (method: string, url: string, args?: any) => Promise; +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/index.d.ts b/packages/okta-auth-js/types/index.d.ts new file mode 100644 index 000000000..189fa50d0 --- /dev/null +++ b/packages/okta-auth-js/types/index.d.ts @@ -0,0 +1,6 @@ + +/// + +declare module '@okta/okta-auth-js' { + export = OktaAuth; +} diff --git a/packages/okta-auth-js/types/okta-auth.d.ts b/packages/okta-auth-js/types/okta-auth.d.ts new file mode 100644 index 000000000..6acfa5381 --- /dev/null +++ b/packages/okta-auth-js/types/okta-auth.d.ts @@ -0,0 +1,49 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// + +declare namespace OktaAuth { + type OktaAuthBuilder = (args: any) => OktaAuthFactory; + type OktaAuthFactory = (storageUtil: any, httpRequestClient: any) => any; +} + +declare class OktaAuth { + constructor(options?: OktaAuth.OktaAuthOptions); + + // Core interface, implemented by browser & server + userAgent: string; + options: OktaAuth.OktaAuthOptions; + tx: OktaAuth.TransactionAPI; + session: OktaAuth.SessionAPI; + signIn(options?: OktaAuth.SigninOptions): Promise; + + + // Shared methods, added by builderUtil.addSharedPrototypes + verifyRecoveryToken(options?: object): Promise; + unlockAccount(options?: object): Promise; + forgotPassword(options?: object): Promise; + getIssuerOrigin(): string; + + // Browser interface + emitter: OktaAuth.EventEmitter; + fingerprint: OktaAuth.FingerprintAPI; + idToken: OktaAuth.IDTokenAPI; + token: OktaAuth.TokenAPI; + tokenManager: OktaAuth.TokenManagerAPI; + + // features API is also hoisted to static level + static features: OktaAuth.FeaturesAPI; + features: OktaAuth.FeaturesAPI; + + _onTokenManagerError(error: any): void; + signOut(options?: OktaAuth.SignoutOptions): Promise; + webfinger(params?: object): Promise; + closeSession(): Promise; + revokeAccessToken(token?: OktaAuth.AccessToken): Promise; +} diff --git a/packages/okta-auth-js/types/options.d.ts b/packages/okta-auth-js/types/options.d.ts new file mode 100644 index 000000000..ba91156a6 --- /dev/null +++ b/packages/okta-auth-js/types/options.d.ts @@ -0,0 +1,83 @@ +/// +/// + +declare namespace OktaAuth { + interface OAuthParams { + pkce?: boolean; + clientId?: string; + redirectUri?: string; + responseType?: string | string[]; + responseMode?: string; + state?: string; + nonce?: string; + scopes?: string[]; + ignoreSignature?: boolean; + codeChallengeMethod?: string; + codeVerifier?: string; + authorizationCode?: string; + } + + interface CustomUserAgent { + template?: string; + value?: string; + } + + interface SigninOptions { + sendFingerprint?: boolean; + username?: string; + password?: string; + } + + interface SignoutOptions { + postLogoutRedirectUri?: string; + accessToken?: AccessToken; + revokeAccessToken?: boolean; + idToken?: IDToken; + state?: string; + } + + interface CustomUrls { + issuer?: string; + authorizeUrl?: string; + userinfoUrl?: string; + tokenUrl?: string; + revokeUrl?: string; + logoutUrl?: string; + } + + interface CookieOptions { + secure?: boolean; + sameSite?: string | boolean; + } + + interface TokenManagerOptions { + autoRenew?: boolean; + secure?: boolean; + storage?: string; + storageKey?: string; + expireEarlySeconds?: number; + } + + type OnSessionExpiredFunction = () => void; + + interface OktaAuthOptions extends CustomUrls { + pkce?: boolean; + clientId?: string; + redirectUri?: string; + responseType?: string | string[]; + responseMode?: string; + scopes?: string[]; + ignoreSignature?: boolean; + tokenManager?: TokenManagerOptions; + postLogoutRedirectUri?: string; + onSessionExpired?: OnSessionExpiredFunction; + storageUtil?: StorageUtil; + ajaxRequest?: object; + httpRequestClient?: object; + userAgent?: CustomUserAgent; + cookies?: CookieOptions; + transformErrorXHR?: (xhr: object) => any; + headers?: object; + maxClockSkew?: number; + } +} diff --git a/packages/okta-auth-js/types/session.d.ts b/packages/okta-auth-js/types/session.d.ts new file mode 100644 index 000000000..473b9b8c3 --- /dev/null +++ b/packages/okta-auth-js/types/session.d.ts @@ -0,0 +1,15 @@ +declare namespace OktaAuth { + interface SessionObject { + status: string; + refresh?: () => Promise; + user?: () => Promise; + } + + interface SessionAPI { + close: () => Promise; + exists: () => Promise; + get: () => Promise; + refresh: () => Promise; + setCookieAndRedirect: (sessionToken?: string, redirectUri?: string) => void; + } +} diff --git a/packages/okta-auth-js/types/storage.d.ts b/packages/okta-auth-js/types/storage.d.ts new file mode 100644 index 000000000..58d38d205 --- /dev/null +++ b/packages/okta-auth-js/types/storage.d.ts @@ -0,0 +1,50 @@ +/// + +declare namespace OktaAuth { + interface StorageProvider { + setStorage(obj: any): void; + getStorage(): any; + clearStorage(key?: string): void; + updateStorage(key: string, value: any): void; + } + + interface PKCEMeta { + codeVerifier: string; + redirectUri: string; + } + + interface PKCEStorage extends StorageProvider { + setStorage(obj: PKCEMeta): void; + getStorage(): PKCEMeta; + } + + interface SetCookieCoptions extends CookieOptions { + path?: string; + } + + interface Cookies { + set(name: string, value: string, expiresAt: string, options: SetCookieCoptions): string; + get(name: string): string; + delete(name: string): string; + } + + interface StorageUtil { + browserHasLocalStorage(): boolean; + browserHasSessionStorage(): boolean; + getLocalStorage(): Storage; + getSessionStorage(): Storage; + getInMemoryStorage(): SimpleStorage; + getHttpCache(options?: CookieOptions): StorageProvider; + getCookieStorage(options?: CookieOptions): SimpleStorage; + getPKCEStorage(options?: CookieOptions): PKCEStorage; + testStorage(storage: any): boolean; + storage: Cookies; + } + + interface SimpleStorage { + getItem(key: string): any; + setItem(key: string, value: any): void; + } + + type StorageBuilder = (storage: Storage | SimpleStorage, name: string) => StorageProvider; +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/token-api.d.ts b/packages/okta-auth-js/types/token-api.d.ts new file mode 100644 index 000000000..6c7eac6ed --- /dev/null +++ b/packages/okta-auth-js/types/token-api.d.ts @@ -0,0 +1,42 @@ +declare namespace OktaAuth { + interface TokenHash { + [key: string]: Token; + } + + interface GetTokenResponse { + tokens: TokenHash; + state: string; + } + + type ParseFromUrlFunction = () => Promise; + + interface ParseFromUrlInterface extends ParseFromUrlFunction { + _getDocument: () => Document; + _getLocation: () => Location; + _getHistory: () => History; + } + + type GetWithRedirectFunction = (params?: OAuthParams) => Promise; + + interface GetWithRedirectAPI extends GetWithRedirectFunction { + _setLocation: (loc: string) => void; + } + + interface TokenAPI { + getUserInfo(accessToken?: AccessToken, idToken?: IDToken): Promise; + getWithRedirect: GetWithRedirectAPI; + parseFromUrl: ParseFromUrlInterface; + getWithoutPrompt(params?: OAuthParams): Promise; + getWithPopup(params?: OAuthParams): Promise; + decode(token: string): JWTObject; + revoke(token: AccessToken): Promise; + renew(token: Token): Promise; + verify(token: IDToken, params?: object): Promise; + } + + interface IDTokenAPI { + authorize: { + _getLocationHref: () => string; + }; + } +} diff --git a/packages/okta-auth-js/types/token-manager.d.ts b/packages/okta-auth-js/types/token-manager.d.ts new file mode 100644 index 000000000..1c3335d4f --- /dev/null +++ b/packages/okta-auth-js/types/token-manager.d.ts @@ -0,0 +1,22 @@ + +declare namespace OktaAuth { + interface SdkClock { + now?: () => number; + } + + interface TokenManagerRef { + options: TokenManagerOptions; + emitter: EventEmitter; + clock: SdkClock; + expireTimeouts: object; + renewPromise?: any; + } + + interface TokenManagerAPI extends EventSource { + get(key: string): Promise; + add(key: string, token: Token): void; + clear(): void; + remove(key: string): void; + renew(key: string): Promise; + } +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/tokens.d.ts b/packages/okta-auth-js/types/tokens.d.ts new file mode 100644 index 000000000..ce87c7214 --- /dev/null +++ b/packages/okta-auth-js/types/tokens.d.ts @@ -0,0 +1,51 @@ +declare namespace OktaAuth { + interface JWTObject { + header: object; + payload: object; + signature: object; + } + + /** + * + * This interface represents the union of possible known claims that are in an + * ID Token or returned from the /userinfo response and depend on the + * response_type and scope parameters in the authorize request + */ + interface UserClaims { + auth_time?: number; + aud?: string; + email?: string; + email_verified?: boolean; + exp?: number; + family_name?: string; + given_name?: string; + iat?: number; + iss?: string; + jti?: string; + locale?: string; + name?: string; + nonce?: string; + preferred_username?: string; + sub: string; + updated_at?: number; + ver?: number; + zoneinfo?: string; + [propName: string]: any; // For custom claims that may be configured by the org admin + } + + interface AbstractToken { + expiresAt: number; + } + + interface AccessToken extends AbstractToken { + accessToken: string; + } + + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + interface IDToken extends AbstractToken { + idToken: string; + claims: UserClaims; + } + + type Token = AccessToken | IDToken; +} \ No newline at end of file diff --git a/packages/okta-auth-js/types/tx.d.ts b/packages/okta-auth-js/types/tx.d.ts new file mode 100644 index 000000000..c55702653 --- /dev/null +++ b/packages/okta-auth-js/types/tx.d.ts @@ -0,0 +1,17 @@ +declare namespace OktaAuth { + type TransactionExistsFunction = () => boolean; + interface TransactionExists extends TransactionExistsFunction { + _get: (key: string) => string; + } + + class AuthTransaction { + constructor(sdk: OktaAuth, res?: object) + } + + interface TransactionAPI { + exists: TransactionExists; + status: (args?: object) => Promise; + resume: (args?: object) => Promise; + introspect: (args?: object) => Promise; + } +} \ No newline at end of file diff --git a/scripts/travis.sh b/scripts/travis.sh index 6ded4f33b..5635306a8 100755 --- a/scripts/travis.sh +++ b/scripts/travis.sh @@ -4,7 +4,8 @@ if [ "${TRAVIS_EVENT_TYPE}" = "cron" ] ; then export RUN_SAUCE_TESTS=true; yarn test:e2e else - # run the lint and e2e tests (on chrome headless) - yarn lint + # run the validate and e2e tests (on chrome headless) + # validate will run lint and typescript build + yarn validate yarn test fi diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..7a0fc8062 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "references": [ + { + "path": "./packages/okta-auth-js" + } + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0ea5f4340..9bbeb2ade 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1012,6 +1012,16 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1101,6 +1111,11 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -1135,6 +1150,19 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest@^25.2.3": + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" + integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + +"@types/json-schema@^7.0.3": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" + integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== + "@types/minimatch@*", "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1145,6 +1173,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.8.tgz#1d590429fe8187a02707720ecf38a6fe46ce294b" integrity sha512-6XzyyNM9EKQW4HKuzbo/CkOIjn/evtCmsU+MUM1xDfJ+3/rNjBttM1NgN7AOQvN6tP1Sl1D1PIKMreTArnxM9A== +"@types/node@^14.0.3": + version "14.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.3.tgz#57bcb277f753a3dabfa56cea0a93288aae82143c" + integrity sha512-a8TR2N5VEJCL9HEJrAfwv3UI1bZq50HydowDDVV6pfnY7ZwG5Pjii+nSDhrDtGW3XKMoVKOgG8zS/Kv5j399uA== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -1167,6 +1200,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^15.0.0": + version "15.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79" + integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== + dependencies: + "@types/yargs-parser" "*" + "@types/yauzl@^2.9.1": version "2.9.1" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" @@ -1174,6 +1214,49 @@ dependencies: "@types/node" "*" +"@typescript-eslint/eslint-plugin@^2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" + integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== + dependencies: + "@typescript-eslint/experimental-utils" "2.34.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" + integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + "@wdio/cli@^5.15.1": version "5.18.6" resolved "https://registry.yarnpkg.com/@wdio/cli/-/cli-5.18.6.tgz#a1052e0e22cd35a23d7f42202a8ad02eefbe5729" @@ -2980,6 +3063,11 @@ commander@^4.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== +comment-parser@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.7.4.tgz#f5eb83cbae323cae6533c057f41d52692361c83a" + integrity sha512-Nnl77/mt6sj1BiYSVMeMWzvD0183F2MFOJyFRmZHimUVDYS9J40AvXpiFA7RpU5pQH+HkvYc0dnsHpwW2xmbyQ== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3768,6 +3856,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== + diff@3.5.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -4254,6 +4347,19 @@ eslint-plugin-jasmine@^2.10.1: resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.10.1.tgz#5733b709e751f4bc40e31e1c16989bd2cdfbec97" integrity sha1-VzO3CedR9LxA4x4cFpib0s377Jc= +eslint-plugin-jsdoc@^25.4.2: + version "25.4.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-25.4.2.tgz#1a8480e880c4d343ba9bc77caf6c91e4500d7a71" + integrity sha512-IFZnxBBt2fGYZ9yaLt+KP/jHa6u8LQPwH9QzRlhbU+WKBq7ou6XTXoxG0EZVn9ohcbJ0sM8X70iRRX/J3Wu37w== + dependencies: + comment-parser "^0.7.4" + debug "^4.1.1" + jsdoctypeparser "^6.1.0" + lodash "^4.17.15" + regextras "^0.7.1" + semver "^6.3.0" + spdx-expression-parse "^3.0.1" + eslint-scope@^4.0.0, eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -4262,6 +4368,14 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^1.3.1: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -4269,6 +4383,13 @@ eslint-utils@^1.3.1: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" @@ -5165,7 +5286,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -6413,6 +6534,16 @@ jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-diff@^25.2.1: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" + integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" + jest-docblock@^24.3.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" @@ -6459,6 +6590,11 @@ jest-get-type@^24.9.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -6769,6 +6905,11 @@ jscodeshift@0.6.4: temp "^0.8.1" write-file-atomic "^2.3.0" +jsdoctypeparser@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-6.1.0.tgz#acfb936c26300d98f1405cb03e20b06748e512a8" + integrity sha512-UCQBZ3xCUBv/PLfwKAJhp6jmGOSLFNKzrotXGNgbKhWvz27wPsCsVeP7gIcHPElQw2agBmynAitXqhxR58XAmA== + jsdom@^11.5.1: version "11.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" @@ -8668,6 +8809,16 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^25.2.1, pretty-format@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" + integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== + dependencies: + "@jest/types" "^25.5.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + pretty-ms@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-5.1.0.tgz#b906bdd1ec9e9799995c372e2b1c34f073f95384" @@ -8874,6 +9025,11 @@ rc@^1.0.1, rc@^1.1.6: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-is@^16.12.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-is@^16.8.4: version "16.12.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" @@ -9099,6 +9255,11 @@ regexpp@^2.0.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== +regexpp@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + regexpu-core@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" @@ -9111,6 +9272,11 @@ regexpu-core@^4.6.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.1.0" +regextras@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.7.1.tgz#be95719d5f43f9ef0b9fa07ad89b7c606995a3b2" + integrity sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w== + registry-auth-token@^3.0.1: version "3.4.0" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" @@ -9532,6 +9698,11 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -9868,6 +10039,14 @@ spdx-expression-parse@^3.0.0: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" +spdx-expression-parse@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + spdx-license-ids@^3.0.0: version "3.0.5" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" @@ -10531,11 +10710,23 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +tslib@^1.8.1: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + tslib@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -10593,6 +10784,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^3.9.2: + version "3.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9" + integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw== + uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"