From da468b45efd9dfb074c8f6d2c35bf326beac4586 Mon Sep 17 00:00:00 2001 From: Aaron Granick Date: Fri, 14 Jan 2022 23:30:49 +0000 Subject: [PATCH] enables typescript strict mode and fixes type errors OKTA-459135 <<>> Artifact: okta-auth-js Files changed count: 114 PR Link: "https://github.com/okta/okta-auth-js/pull/1057" --- .vscode/launch.json | 2 + CHANGELOG.md | 3 +- lib/AuthStateManager.ts | 6 +- lib/OktaAuth.ts | 106 ++++++----- lib/OktaUserAgent.ts | 2 +- lib/PromiseQueue.ts | 7 +- lib/StorageManager.ts | 8 +- lib/TokenManager.ts | 14 +- lib/TransactionManager.ts | 19 +- lib/browser/browserStorage.ts | 21 +- lib/browser/fingerprint.ts | 8 +- lib/builderUtil.ts | 5 +- lib/crypto/oidcHash.ts | 2 +- lib/features.ts | 11 +- lib/fetch/fetchRequest.ts | 9 +- lib/http/request.ts | 11 +- .../SecurityQuestionVerification.ts | 3 +- lib/idx/authenticator/getAuthenticator.ts | 7 +- lib/idx/cancel.ts | 2 +- lib/idx/handleInteractionCodeRedirect.ts | 3 +- lib/idx/interact.ts | 3 +- lib/idx/introspect.ts | 4 +- lib/idx/recoverPassword.ts | 2 +- lib/idx/remediate.ts | 13 +- .../AuthenticatorEnrollmentData.ts | 13 +- .../AuthenticatorVerificationData.ts | 15 +- lib/idx/remediators/Base/AuthenticatorData.ts | 21 +- lib/idx/remediators/Base/Remediator.ts | 19 +- .../remediators/Base/SelectAuthenticator.ts | 16 +- .../remediators/Base/VerifyAuthenticator.ts | 2 +- lib/idx/remediators/ChallengeAuthenticator.ts | 3 +- lib/idx/remediators/EnrollAuthenticator.ts | 2 +- lib/idx/remediators/EnrollPoll.ts | 2 +- lib/idx/remediators/EnrollProfile.ts | 9 +- lib/idx/remediators/Identify.ts | 2 +- lib/idx/remediators/ReEnrollAuthenticator.ts | 5 +- lib/idx/remediators/ResetAuthenticator.ts | 2 +- .../SelectAuthenticatorAuthenticate.ts | 6 +- .../remediators/SelectAuthenticatorEnroll.ts | 2 +- lib/idx/remediators/SelectEnrollProfile.ts | 2 +- lib/idx/remediators/Skip.ts | 2 +- lib/idx/remediators/util.ts | 9 +- lib/idx/run.ts | 8 +- lib/idx/transactionMeta.ts | 55 ++---- lib/oidc/endpoints/authorize.ts | 7 +- lib/oidc/endpoints/token.ts | 3 +- lib/oidc/exchangeCodeForTokens.ts | 7 +- lib/oidc/getToken.ts | 7 +- lib/oidc/getWithRedirect.ts | 50 +---- lib/oidc/handleOAuthResponse.ts | 180 +++++++++--------- lib/oidc/parseFromUrl.ts | 2 +- lib/oidc/renewToken.ts | 2 +- lib/oidc/renewTokens.ts | 4 +- lib/oidc/revokeToken.ts | 63 +++--- lib/oidc/util/index.ts | 1 + lib/oidc/util/loginRedirect.ts | 5 +- lib/oidc/util/oauth.ts | 2 +- lib/oidc/util/oauthMeta.ts | 34 ++++ lib/oidc/util/pkce.ts | 2 +- lib/oidc/util/prepareTokenParams.ts | 25 ++- lib/oidc/util/validateClaims.ts | 7 +- lib/oidc/verifyToken.ts | 3 +- lib/server/serverStorage.ts | 2 +- lib/services/TokenService.ts | 9 +- lib/tx/AuthTransaction.ts | 8 +- lib/tx/TransactionState.ts | 12 +- lib/types/OktaAuthOptions.ts | 2 +- lib/types/Storage.ts | 4 +- lib/types/api.ts | 16 +- lib/util/url.ts | 2 +- test/integration/.eslintrc.json | 3 +- test/integration/spec/renewTokens.ts | 32 ++-- test/integration/util/createClient.ts | 2 +- test/integration/util/getTokens.ts | 8 +- test/spec/.eslintrc.json | 4 +- test/spec/OktaAuth/assertValidConfig.ts | 4 +- test/spec/OktaAuth/browser.ts | 4 +- test/spec/SavedObject.ts | 2 +- test/spec/StorageManager.ts | 6 +- test/spec/TokenManager/core.ts | 6 +- test/spec/TokenManager/crossTabs.ts | 2 +- test/spec/TokenManager/expireEvents.ts | 2 +- test/spec/TransactionManager.ts | 2 +- test/spec/features/core.ts | 20 +- test/spec/http/request.ts | 2 +- test/spec/idx/authenticate.ts | 2 +- test/spec/idx/poll.ts | 2 +- test/spec/idx/recoverPassword.ts | 2 +- test/spec/idx/register.ts | 18 +- test/spec/idx/remediators/EnrollPoll.ts | 62 ++++++ test/spec/idx/startTransaction.ts | 6 +- test/spec/oidc/endpoints/token.ts | 17 +- test/spec/oidc/endpoints/well-known.ts | 8 +- test/spec/oidc/getWithPopup.ts | 34 ++-- test/spec/oidc/getWithRedirect.ts | 60 ++---- test/spec/oidc/getWithRedirectLegacy.ts | 4 +- test/spec/oidc/getWithoutPrompt.ts | 14 +- test/spec/oidc/revokeToken.ts | 2 +- test/spec/oidc/util/defaultTokenParams.ts | 2 +- test/spec/oidc/util/handleOAuthResponse.ts | 84 +++++--- test/spec/oidc/util/loginRedirect.ts | 4 +- test/spec/oidc/util/oauthMeta.ts | 91 +++++++++ test/spec/oidc/util/validateClaims.ts | 2 +- test/support/idx/factories/errors.ts | 2 +- test/support/idx/factories/messages.ts | 4 +- test/support/idx/factories/responses.ts | 3 +- test/tsconfig.json | 33 ++++ test/types/auth.test-d.ts | 13 +- test/types/authStateManager.test-d.ts | 13 +- test/types/session.test-d.ts | 5 +- test/types/token.test-d.ts | 9 +- test/types/tokenManager.test-d.ts | 7 +- test/types/transaction.test-d.ts | 29 +-- tsconfig.json | 4 +- 114 files changed, 903 insertions(+), 657 deletions(-) create mode 100644 lib/oidc/util/oauthMeta.ts create mode 100644 test/spec/idx/remediators/EnrollPoll.ts create mode 100644 test/spec/oidc/util/oauthMeta.ts create mode 100644 test/tsconfig.json diff --git a/.vscode/launch.json b/.vscode/launch.json index e0d0ae4ff..db773d81c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,6 +31,7 @@ "--collectCoverage=false", "--testTimeout=120000", "--config=jest.browser.js", + "--testTimeout=120000", "${relativeFile}" ], "console": "integratedTerminal", @@ -47,6 +48,7 @@ "--collectCoverage=false", "--testTimeout=120000", "--config=jest.server.js", + "--testTimeout=120000", "${relativeFile}" ], "console": "integratedTerminal", diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3b4a2e5..6470096c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,10 @@ - [#1050](https://github.com/okta/okta-auth-js/pull/1050) Removes `userAgent` field from oktaAuth instance - [#1014](https://github.com/okta/okta-auth-js/pull/1014) Shared transaction storage is automatically cleared on success and error states. Storage is not cleared for "terminal" state which is neither success nor error. - [#1051](https://github.com/okta/okta-auth-js/pull/1051) Removes `useMultipleCookies` from CookieStorage options -- [#1059](https://github.com/okta/okta-auth-js/pull/1059) +- [#1059](https://github.com/okta/okta-auth-js/pull/1059) - Removes signOut option `clearTokensAfterRedirect` - Adds signOut option `clearTokensBeforeRedirect` (default: `false`) to remove local tokens before logout redirect happen +- [#1057](https://github.com/okta/okta-auth-js/pull/1057) Strict checks are now enabled in the Typescript compiler options. Some type signatures have been changed to match current behavior. ### Features diff --git a/lib/AuthStateManager.ts b/lib/AuthStateManager.ts index 6e089e8b4..e33a75790 100644 --- a/lib/AuthStateManager.ts +++ b/lib/AuthStateManager.ts @@ -27,7 +27,7 @@ const EVENT_AUTH_STATE_CHANGE = 'authStateChange'; const MAX_PROMISE_CANCEL_TIMES = 10; // only compare first level of authState -const isSameAuthState = (prevState: AuthState, state: AuthState) => { +const isSameAuthState = (prevState: AuthState | null, state: AuthState) => { // initial state is null if (!prevState) { return false; @@ -48,7 +48,6 @@ export class AuthStateManager { _authState: AuthState | null; _prevAuthState: AuthState | null; _logOptions: AuthStateLogOptions; - _lastEventTimestamp: number; constructor(sdk: OktaAuth) { if (!sdk.emitter) { @@ -59,7 +58,8 @@ export class AuthStateManager { this._pending = { ...DEFAULT_PENDING }; this._authState = INITIAL_AUTH_STATE; this._logOptions = {}; - + this._prevAuthState = null; + // Listen on tokenManager events to start updateState process // "added" event is emitted in both add and renew process // Only listen on "added" event to update auth state diff --git a/lib/OktaAuth.ts b/lib/OktaAuth.ts index 8d86314d7..e2d763317 100644 --- a/lib/OktaAuth.ts +++ b/lib/OktaAuth.ts @@ -43,7 +43,10 @@ import { IdxAPI, SignoutRedirectUrlOptions, HttpAPI, - FlowIdentifier + FlowIdentifier, + GetWithRedirectAPI, + ParseFromUrlInterface, + GetWithRedirectFunction, } from './types'; import { transactionStatus, @@ -136,7 +139,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { session: SessionAPI; pkce: PkceAPI; static features: FeaturesAPI; - features: FeaturesAPI; + features!: FeaturesAPI; token: TokenAPI; _tokenQueue: PromiseQueue; emitter: typeof Emitter; @@ -148,18 +151,20 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { _pending: { handleLogin: boolean }; constructor(args: OktaAuthOptions) { const options = this.options = buildOptions(args); - this.storageManager = new StorageManager(options.storageManager, options.cookies, options.storageUtil); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.storageManager = new StorageManager(options.storageManager!, options.cookies!, options.storageUtil!); this.transactionManager = new TransactionManager(Object.assign({ storageManager: this.storageManager, }, options.transactionManager)); this._oktaUserAgent = new OktaUserAgent(); - + this.tx = { status: transactionStatus.bind(null, this), resume: resumeTransaction.bind(null, this), exists: Object.assign(transactionExists.bind(null, this), { _get: (name) => { - const storage = options.storageUtil.storage; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const storage = options.storageUtil!.storage; return storage.get(name); } }), @@ -213,13 +218,43 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { }; this._tokenQueue = new PromiseQueue(); + const useQueue = (method) => { + return PromiseQueue.prototype.push.bind(this._tokenQueue, method, null); + }; + + // eslint-disable-next-line max-len + const getWithRedirectFn = useQueue(getWithRedirect.bind(null, this)) as GetWithRedirectFunction; + const getWithRedirectApi: GetWithRedirectAPI = Object.assign(getWithRedirectFn, { + // This is exposed so we can set window.location in our tests + _setLocation: function(url) { + window.location = url; + } + }); + // eslint-disable-next-line max-len + const parseFromUrlFn = useQueue(parseFromUrl.bind(null, this)) as ParseFromUrlInterface; + const parseFromUrlApi: ParseFromUrlInterface = Object.assign(parseFromUrlFn, { + // 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; + } + }); this.token = { prepareTokenParams: prepareTokenParams.bind(null, this), exchangeCodeForTokens: exchangeCodeForTokens.bind(null, this), getWithoutPrompt: getWithoutPrompt.bind(null, this), getWithPopup: getWithPopup.bind(null, this), - getWithRedirect: getWithRedirect.bind(null, this), - parseFromUrl: parseFromUrl.bind(null, this), + getWithRedirect: getWithRedirectApi, + parseFromUrl: parseFromUrlApi, decode: decodeToken, revoke: revokeToken.bind(null, this), renew: renewToken.bind(null, this), @@ -230,7 +265,14 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { isLoginRedirect: isLoginRedirect.bind(null, this) }; // Wrap all async token API methods using MethodQueue to avoid issues with concurrency - const syncMethods = ['decode', 'isLoginRedirect']; + const syncMethods = [ + // sync methods + 'decode', + 'isLoginRedirect', + // already bound + 'getWithRedirect', + 'parseFromUrl' + ]; Object.keys(this.token).forEach(key => { if (syncMethods.indexOf(key) >= 0) { // sync methods should not be wrapped return; @@ -238,29 +280,6 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { var method = this.token[key]; this.token[key] = PromiseQueue.prototype.push.bind(this._tokenQueue, method, null); }); - - Object.assign(this.token.getWithRedirect, { - // This is exposed so we can set window.location in our tests - _setLocation: function(url) { - window.location = url; - } - }); - Object.assign(this.token.parseFromUrl, { - // 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; - } - }); // IDX const boundStartTransaction = startTransaction.bind(null, this); @@ -294,11 +313,11 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { getTransactionMeta: getTransactionMeta.bind(null, this), saveTransactionMeta: saveTransactionMeta.bind(null, this), clearTransactionMeta: clearTransactionMeta.bind(null, this), - isTransactionMetaValid: isTransactionMetaValid.bind(null, this), + isTransactionMetaValid, setFlow: (flow: FlowIdentifier) => { this.options.flow = flow; }, - getFlow: (): FlowIdentifier => { + getFlow: (): FlowIdentifier | undefined => { return this.options.flow; }, canProceed: canProceed.bind(null, this), @@ -387,7 +406,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { } // Ends the current Okta SSO session without redirecting to Okta. - closeSession(): Promise { + closeSession(): Promise { return this.session.close() // DELETE /api/v1/sessions/me .then(async () => { // Clear all local tokens @@ -403,7 +422,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { } // Revokes the access token for the application session - async revokeAccessToken(accessToken?: AccessToken): Promise { + async revokeAccessToken(accessToken?: AccessToken): Promise { if (!accessToken) { accessToken = (await this.tokenManager.getTokens()).accessToken as AccessToken; const accessTokenKey = this.tokenManager.getStorageKeyByType('accessToken'); @@ -417,7 +436,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { } // Revokes the refresh token for the application session - async revokeRefreshToken(refreshToken?: RefreshToken): Promise { + async revokeRefreshToken(refreshToken?: RefreshToken): Promise { if (!refreshToken) { refreshToken = (await this.tokenManager.getTokens()).refreshToken as RefreshToken; const refreshTokenKey = this.tokenManager.getStorageKeyByType('refreshToken'); @@ -543,7 +562,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { const { autoRenew, autoRemove } = this.tokenManager.getOptions(); if (accessToken && this.tokenManager.hasExpired(accessToken)) { - accessToken = null; + accessToken = undefined; if (autoRenew) { try { accessToken = await this.tokenManager.renew('accessToken') as AccessToken; @@ -556,7 +575,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { } if (idToken && this.tokenManager.hasExpired(idToken)) { - idToken = null; + idToken = undefined; if (autoRenew) { try { idToken = await this.tokenManager.renew('idToken') as IDToken; @@ -612,7 +631,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { } } - getOriginalUri(state?: string): string { + getOriginalUri(state?: string): string | undefined { // Prefer shared storage (if state is available) state = state || this.options.state; if (state) { @@ -625,7 +644,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { // Try to load from session storage const storage = browserStorage.getSessionStorage(); - return storage ? storage.getItem(REFERRER_PATH_STORAGE_KEY) : undefined; + return storage ? storage.getItem(REFERRER_PATH_STORAGE_KEY) || undefined : undefined; } removeOriginalUri(state?: string): void { @@ -637,7 +656,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { state = state || this.options.state; if (state) { const sharedStorage = this.storageManager.getOriginalUriStorage(); - sharedStorage.removeItem(state); + sharedStorage.removeItem && sharedStorage.removeItem(state); } } @@ -672,7 +691,7 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { const { restoreOriginalUri } = this.options; if (restoreOriginalUri) { await restoreOriginalUri(this, originalUri); - } else { + } else if (originalUri) { window.location.replace(originalUri); } } @@ -702,7 +721,8 @@ class OktaAuth implements SDKInterface, SigninAPI, SignoutAPI { getIssuerOrigin(): string { // Infer the URL from the issuer URL, omitting the /oauth2/{authServerId} - return this.options.issuer.split('/oauth2/')[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.options.issuer!.split('/oauth2/')[0]; } // { username, (relayState) } diff --git a/lib/OktaUserAgent.ts b/lib/OktaUserAgent.ts index ac5a9dddf..c50998253 100644 --- a/lib/OktaUserAgent.ts +++ b/lib/OktaUserAgent.ts @@ -12,8 +12,8 @@ */ /* global SDK_VERSION */ -import { isBrowser } from './features'; +import { isBrowser } from './features'; export class OktaUserAgent { private environments: string[]; diff --git a/lib/PromiseQueue.ts b/lib/PromiseQueue.ts index 029727f45..663210e8c 100644 --- a/lib/PromiseQueue.ts +++ b/lib/PromiseQueue.ts @@ -63,10 +63,11 @@ class PromiseQueue { return; } this.running = true; - var queueItem = this.queue.shift(); - var res = queueItem.method.apply(queueItem.thisObject, queueItem.args); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + var queueItem = this.queue.shift()!; + var res = queueItem.method.apply(queueItem.thisObject, queueItem.args as never) as unknown; if (isPromise(res)) { - res.then(queueItem.resolve, queueItem.reject).finally(() => { + (res as Promise).then(queueItem.resolve, queueItem.reject).finally(() => { this.running = false; this.run(); }); diff --git a/lib/StorageManager.ts b/lib/StorageManager.ts index 5b72dbd3e..e8165ca47 100644 --- a/lib/StorageManager.ts +++ b/lib/StorageManager.ts @@ -62,6 +62,7 @@ export default class StorageManager { } // generic method to get any available storage provider + // eslint-disable-next-line complexity getStorage(options: StorageOptions): SimpleStorage { options = Object.assign({}, this.cookieOptions, options); // set defaults @@ -80,12 +81,13 @@ export default class StorageManager { const idx = storageTypes.indexOf(storageType); if (idx >= 0) { storageTypes = storageTypes.slice(idx); - storageType = null; + storageType = undefined; } } if (!storageType) { - storageType = this.storageUtil.findStorageType(storageTypes); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + storageType = this.storageUtil.findStorageType(storageTypes!); } return this.storageUtil.getStorageByType(storageType, options); } @@ -118,7 +120,7 @@ export default class StorageManager { // intermediate idxResponse // store for network traffic optimazation purpose // TODO: revisit in auth-js 6.0 epic JIRA: OKTA-399791 - getIdxResponseStorage(options?: StorageOptions): IdxResponseStorage { + getIdxResponseStorage(options?: StorageOptions): IdxResponseStorage | null { let storage; if (isBrowser()) { // on browser side only use memory storage diff --git a/lib/TokenManager.ts b/lib/TokenManager.ts index cfd1c239c..d7ce6282b 100644 --- a/lib/TokenManager.ts +++ b/lib/TokenManager.ts @@ -55,7 +55,7 @@ export const EVENT_ERROR = 'error'; interface TokenManagerState { expireTimeouts: Record; - renewPromise: Promise; + renewPromise: Promise | null; } function defaultState(): TokenManagerState { return { @@ -70,7 +70,7 @@ export class TokenManager implements TokenManagerInterface { private storage: StorageProvider; private state: TokenManagerState; private options: TokenManagerOptions; - private service: TokenService; + private service: TokenService | null; on: (event: string, handler: TokenManagerErrorEventHandler | TokenManagerEventHandler, context?: object) => void; off: (event: string, handler?: TokenManagerErrorEventHandler | TokenManagerEventHandler) => void; @@ -81,7 +81,8 @@ export class TokenManager implements TokenManagerInterface { if (!this.emitter) { throw new AuthSdkError('Emitter should be initialized before TokenManager'); } - + this.service = null; + options = Object.assign({}, DEFAULT_OPTIONS, removeNils(options)); if (isIE11OrLess()) { options._storageEventDelay = options._storageEventDelay || 1000; @@ -133,7 +134,8 @@ export class TokenManager implements TokenManagerInterface { } getExpireTime(token) { - var expireTime = token.expiresAt - this.options.expireEarlySeconds; + const expireEarlySeconds = this.options.expireEarlySeconds || 0; + var expireTime = token.expiresAt - expireEarlySeconds; return expireTime; } @@ -385,7 +387,7 @@ export class TokenManager implements TokenManagerInterface { // TODO: this methods is redundant and can be removed in the next major version OKTA-407224 async renewToken(token) { - return this.sdk.token.renew(token); + return this.sdk.token?.renew(token); } // TODO: this methods is redundant and can be removed in the next major version OKTA-407224 validateToken(token: Token) { @@ -393,7 +395,7 @@ export class TokenManager implements TokenManagerInterface { } // TODO: renew method should take no param, change in the next major version OKTA-407224 - renew(key): Promise { + renew(key): Promise { // Multiple callers may receive the same promise. They will all resolve or reject from the same request. if (this.state.renewPromise) { return this.state.renewPromise; diff --git a/lib/TransactionManager.ts b/lib/TransactionManager.ts index 803429bcf..6fcc001ac 100644 --- a/lib/TransactionManager.ts +++ b/lib/TransactionManager.ts @@ -48,7 +48,8 @@ export default class TransactionManager { saveLastResponse: boolean; constructor(options: TransactionManagerOptions) { - this.storageManager = options.storageManager; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.storageManager = options.storageManager!; this.legacyWidgetSupport = options.legacyWidgetSupport === false ? false : true; this.saveNonceCookie = options.saveNonceCookie === false ? false : true; this.saveStateCookie = options.saveStateCookie === false ? false : true; @@ -158,7 +159,7 @@ export default class TransactionManager { exists(options: TransactionMetaOptions = {}): boolean { try { - const meta: TransactionMeta = this.load(options); + const meta = this.load(options); return !!meta; } catch { return false; @@ -167,7 +168,7 @@ export default class TransactionManager { // load transaction meta from storage // eslint-disable-next-line complexity,max-statements - load(options: TransactionMetaOptions = {}): TransactionMeta { + load(options: TransactionMetaOptions = {}): TransactionMeta | null { let meta: TransactionMeta; @@ -257,7 +258,7 @@ export default class TransactionManager { // If meta is not valid, throw an exception to avoid misleading server-side error // The most likely cause of this error is trying to handle a callback twice // eslint-disable-next-line max-len - throw new AuthSdkError('Could not load PKCE codeVerifier from storage. This may indicate the auth flow has already completed or multiple auth flows are executing concurrently.', null); + throw new AuthSdkError('Could not load PKCE codeVerifier from storage. This may indicate the auth flow has already completed or multiple auth flows are executing concurrently.', undefined); } clearLegacyOAuthParams(): void { @@ -307,20 +308,20 @@ export default class TransactionManager { saveIdxResponse(idxResponse: RawIdxResponse): void { if (!this.saveLastResponse) { - return null; + return; } - const storage: StorageProvider = this.storageManager.getIdxResponseStorage(); + const storage = this.storageManager.getIdxResponseStorage(); if (!storage) { return; } storage.setStorage(idxResponse); } - loadIdxResponse(): RawIdxResponse { + loadIdxResponse(): RawIdxResponse | null { if (!this.saveLastResponse) { return null; } - const storage: StorageProvider = this.storageManager.getIdxResponseStorage(); + const storage = this.storageManager.getIdxResponseStorage(); if (!storage) { return null; } @@ -335,7 +336,7 @@ export default class TransactionManager { if (!this.saveLastResponse) { return; } - const storage: StorageProvider = this.storageManager.getIdxResponseStorage(); + const storage = this.storageManager.getIdxResponseStorage(); storage?.clearStorage(); } } \ No newline at end of file diff --git a/lib/browser/browserStorage.ts b/lib/browser/browserStorage.ts index dbcb14b2a..efa32b141 100644 --- a/lib/browser/browserStorage.ts +++ b/lib/browser/browserStorage.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -31,11 +32,11 @@ var storageUtil: BrowserStorageUtil = { // These are shimmed in `OktaAuthBase.ts` getHttpCache(): StorageProvider { - return null; + return null as never as StorageProvider; }, getPKCEStorage(): PKCEStorage { - return null; + return null as never as PKCEStorage; }, // IE11 bug that Microsoft doesn't plan to fix @@ -78,8 +79,8 @@ var storageUtil: BrowserStorageUtil = { return supported; }, - getStorageByType: function(storageType: StorageType, options: StorageOptions): SimpleStorage { - let storageProvider = null; + getStorageByType: function(storageType: StorageType, options?: StorageOptions): SimpleStorage { + let storageProvider; switch (storageType) { case 'sessionStorage': storageProvider = storageUtil.getSessionStorage(); @@ -132,9 +133,9 @@ var storageUtil: BrowserStorageUtil = { // Provides webStorage-like interface for cookies getCookieStorage: function(options): CookieStorage { - const secure = options.secure; - const sameSite = options.sameSite; - const sessionCookie = options.sessionCookie; + const secure = options!.secure; + const sameSite = options!.sameSite; + const sessionCookie = options!.sessionCookie; if (typeof secure === 'undefined' || typeof sameSite === 'undefined') { throw new AuthSdkError('getCookieStorage: "secure" and "sameSite" options must be provided'); } @@ -142,7 +143,7 @@ var storageUtil: BrowserStorageUtil = { getItem: storageUtil.storage.get, setItem: function(key, value, expiresAt = '2200-01-01T00:00:00.000Z') { // By defauilt, cookie shouldn't expire - expiresAt = sessionCookie ? null : expiresAt; + expiresAt = (sessionCookie ? null : expiresAt) as string; storageUtil.storage.set(key, value, expiresAt, { secure: secure, sameSite: sameSite, @@ -153,7 +154,7 @@ var storageUtil: BrowserStorageUtil = { } }; - if (!options.useSeparateCookies) { + if (!options!.useSeparateCookies) { return storage; } @@ -165,7 +166,7 @@ var storageUtil: BrowserStorageUtil = { var data = storage.getItem(); // read all cookies var value = {}; Object.keys(data).forEach(k => { - if (k.indexOf(key) === 0) { // filter out unrelated cookies + if (k.indexOf(key!) === 0) { // filter out unrelated cookies value[k.replace(`${key}_`, '')] = JSON.parse(data[k]); // populate with cookie data } }); diff --git a/lib/browser/fingerprint.ts b/lib/browser/fingerprint.ts index cd9e13966..02027ad93 100644 --- a/lib/browser/fingerprint.ts +++ b/lib/browser/fingerprint.ts @@ -20,7 +20,7 @@ import { } from '../oidc'; import { FingerprintOptions } from '../types'; -export default function fingerprint(sdk: OktaAuth, options: FingerprintOptions) { +export default function fingerprint(sdk: OktaAuth, options?: FingerprintOptions): Promise { options = options || {}; if (!isFingerprintSupported()) { @@ -51,7 +51,7 @@ export default function fingerprint(sdk: OktaAuth, options: FingerprintOptions) if (!msg) { return; } if (msg.type === 'FingerprintAvailable') { - return resolve(msg.fingerprint); + return resolve(msg.fingerprint as string); } if (msg.type === 'FingerprintServiceReady') { e.source.postMessage(JSON.stringify({ @@ -66,7 +66,7 @@ export default function fingerprint(sdk: OktaAuth, options: FingerprintOptions) timeout = setTimeout(function() { reject(new AuthSdkError('Fingerprinting timed out')); - }, options.timeout || 15000); + }, options?.timeout || 15000); }); return promise.finally(function() { @@ -75,5 +75,5 @@ export default function fingerprint(sdk: OktaAuth, options: FingerprintOptions) if (document.body.contains(iframe)) { iframe.parentElement.removeChild(iframe); } - }); + }) as Promise; } diff --git a/lib/builderUtil.ts b/lib/builderUtil.ts index 7a3876cef..ec0fb1182 100644 --- a/lib/builderUtil.ts +++ b/lib/builderUtil.ts @@ -24,14 +24,15 @@ function assertValidConfig(args: OktaAuthOptions) { 'Required usage: new OktaAuth({scopes: ["openid", "email"]})'); } - var issuer = args.issuer; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + var issuer = args.issuer!; if (!issuer) { throw new AuthSdkError('No issuer passed to constructor. ' + 'Required usage: new OktaAuth({issuer: "https://{yourOktaDomain}.com/oauth2/{authServerId}"})'); } var isUrlRegex = new RegExp('^http?s?://.+'); - if (!isUrlRegex.test(args.issuer)) { + if (!isUrlRegex.test(issuer)) { throw new AuthSdkError('Issuer must be a valid URL. ' + 'Required usage: new OktaAuth({issuer: "https://{yourOktaDomain}.com/oauth2/{authServerId}"})'); } diff --git a/lib/crypto/oidcHash.ts b/lib/crypto/oidcHash.ts index 2590cec61..d09bc0fdc 100644 --- a/lib/crypto/oidcHash.ts +++ b/lib/crypto/oidcHash.ts @@ -19,7 +19,7 @@ export function getOidcHash(str) { return webcrypto.subtle.digest('SHA-256', buffer).then(function(arrayBuffer) { var intBuffer = new Uint8Array(arrayBuffer); var firstHalf = intBuffer.slice(0, 16); - var hash = String.fromCharCode.apply(null, firstHalf); + var hash = String.fromCharCode.apply(null, firstHalf as unknown as number[]); var b64u = stringToBase64Url(hash); // url-safe base64 variant return b64u; }); diff --git a/lib/features.ts b/lib/features.ts index 787eaba4c..18822a65e 100644 --- a/lib/features.ts +++ b/lib/features.ts @@ -22,7 +22,11 @@ export function isBrowser() { } export function isIE11OrLess() { - return isBrowser() && !!document.documentMode && document.documentMode <= 11; + if (!isBrowser()) { + return false; + } + const documentMode = (document as any).documentMode; + return !!documentMode && documentMode <= 11; } export function getUserAgent() { @@ -38,8 +42,9 @@ export function isPopupPostMessageSupported() { if (!isBrowser()) { return false; } - var isIE8or9 = document.documentMode && document.documentMode < 10; - if (window.postMessage && !isIE8or9) { + const documentMode = (document as any).documentMode; + var isIE8or9 = documentMode && documentMode < 10; + if (typeof window.postMessage !== 'undefined' && !isIE8or9) { return true; } return false; diff --git a/lib/fetch/fetchRequest.ts b/lib/fetch/fetchRequest.ts index 16a5c3f8b..dfa566ca0 100644 --- a/lib/fetch/fetchRequest.ts +++ b/lib/fetch/fetchRequest.ts @@ -11,11 +11,12 @@ */ import crossFetch from 'cross-fetch'; -import { FetchOptions, FetchResponse, HttpResponse } from '../types'; +import { FetchOptions, HttpResponse } from '../types'; -function readData(response: FetchResponse): Promise { +function readData(response: Response): Promise { if (response.headers.get('Content-Type') && - response.headers.get('Content-Type').toLowerCase().indexOf('application/json') >= 0) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + response.headers.get('Content-Type')!.toLowerCase().indexOf('application/json') >= 0) { return response.json() // JSON parse can fail if response is not a valid object .catch(e => { @@ -32,7 +33,7 @@ function readData(response: FetchResponse): Promise { function formatResult(status: number, data: object | string, response: Response) { const isObject = typeof data === 'object'; const headers = {}; - for (const pair of response.headers.entries()) { + for (const pair of (response.headers as any).entries()) { headers[pair[0]] = pair[1]; } const result: HttpResponse = { diff --git a/lib/http/request.ts b/lib/http/request.ts index b9b5b14bd..2f1467762 100644 --- a/lib/http/request.ts +++ b/lib/http/request.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -26,12 +27,12 @@ export function httpRequest(sdk: OktaAuth, options: RequestOptions): Promise { +export async function introspect (authClient: OktaAuth, options: IntrospectOptions = {}): Promise { // try load from storage first let rawIdxResponse = authClient.transactionManager.loadIdxResponse(); // call idx.introspect if no existing idx response available in storage if (!rawIdxResponse) { - const version = options?.version || IDX_API_VERSION; + const version = options.version || IDX_API_VERSION; const domain = getOAuthDomain(authClient); try { rawIdxResponse = await idx.introspect({ domain, ...options, version }); diff --git a/lib/idx/recoverPassword.ts b/lib/idx/recoverPassword.ts index 95fd8e6c4..cf1d70d1e 100644 --- a/lib/idx/recoverPassword.ts +++ b/lib/idx/recoverPassword.ts @@ -36,7 +36,7 @@ export type PasswordRecoveryOptions = IdxOptions & ReEnrollAuthenticatorValues; export async function recoverPassword( - authClient: OktaAuth, options: PasswordRecoveryOptions + authClient: OktaAuth, options: PasswordRecoveryOptions = {} ): Promise { const flowSpec = getFlowSpecification(authClient, 'recoverPassword'); return run( diff --git a/lib/idx/remediate.ts b/lib/idx/remediate.ts index 6250cea4e..1ee7b9bc7 100644 --- a/lib/idx/remediate.ts +++ b/lib/idx/remediate.ts @@ -41,12 +41,13 @@ export function getRemediator( values: RemediationValues, options: RemediateOptions, ): Remediator { - const { remediators } = options; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const remediators = options.remediators!; let remediator; const remediatorCandidates = []; for (let remediation of idxRemediations) { - const isRemeditionInFlow = Object.keys(remediators).includes(remediation.name); + const isRemeditionInFlow = Object.keys(remediators as object).includes(remediation.name); if (!isRemeditionInFlow) { continue; } @@ -59,7 +60,7 @@ export function getRemediator( } // remediator cannot handle the current values // maybe return for next step - remediatorCandidates.push(remediator); + remediatorCandidates.push(remediator as never); } return remediatorCandidates[0]; @@ -85,14 +86,14 @@ function getIdxMessages(idxResponse: IdxResponse): IdxMessage[] { // Handle global messages const globalMessages = rawIdxState.messages?.value.map(message => message); if (globalMessages) { - messages = [...messages, ...globalMessages]; + messages = [...messages, ...globalMessages] as never; } // Handle field messages for current flow for (let remediation of neededToProceed) { const fieldMessages = Remediator.getMessages(remediation); if (fieldMessages) { - messages = [...messages, ...fieldMessages]; + messages = [...messages, ...fieldMessages] as never; } } @@ -114,7 +115,7 @@ function getNextStep( function handleIdxError(e, remediator?) { // Handle idx messages - const idxState: IdxResponse = isIdxResponse(e) ? e : null; + const idxState = isIdxResponse(e) ? e : null; if (!idxState) { // Thrown error terminates the interaction with idx throw e; diff --git a/lib/idx/remediators/AuthenticatorEnrollmentData.ts b/lib/idx/remediators/AuthenticatorEnrollmentData.ts index 95c6bd5d3..cd9736797 100644 --- a/lib/idx/remediators/AuthenticatorEnrollmentData.ts +++ b/lib/idx/remediators/AuthenticatorEnrollmentData.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -21,16 +22,16 @@ export type AuthenticatorEnrollmentDataValues = AuthenticatorDataValues & { export class AuthenticatorEnrollmentData extends AuthenticatorData { static remediationName = 'authenticator-enrollment-data'; - values: AuthenticatorEnrollmentDataValues; + values!: AuthenticatorEnrollmentDataValues; mapAuthenticator() { const authenticatorData = this.getAuthenticatorData(); - const authenticatorFromRemediation = getAuthenticatorFromRemediation(this.remediation); + const authenticatorFromRemediation = getAuthenticatorFromRemediation(this.remediation)!; return { - id: authenticatorFromRemediation.form.value - .find(({ name }) => name === 'id').value, - methodType: authenticatorData.methodType, - phoneNumber: authenticatorData.phoneNumber, + id: authenticatorFromRemediation.form!.value + .find(({ name }) => name === 'id')!.value, + methodType: authenticatorData!.methodType, + phoneNumber: authenticatorData!.phoneNumber, }; } diff --git a/lib/idx/remediators/AuthenticatorVerificationData.ts b/lib/idx/remediators/AuthenticatorVerificationData.ts index 1526b1fea..442e4ae4b 100644 --- a/lib/idx/remediators/AuthenticatorVerificationData.ts +++ b/lib/idx/remediators/AuthenticatorVerificationData.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -18,29 +19,29 @@ export type AuthenticatorVerificationDataValues = AuthenticatorDataValues; export class AuthenticatorVerificationData extends AuthenticatorData { static remediationName = 'authenticator-verification-data'; - values: AuthenticatorVerificationDataValues; + values!: AuthenticatorVerificationDataValues; mapAuthenticator() { const authenticatorData = this.getAuthenticatorData(); const authenticatorFromRemediation = this.getAuthenticatorFromRemediation(); return { - id: authenticatorFromRemediation.form.value - .find(({ name }) => name === 'id').value, - enrollmentId: authenticatorFromRemediation.form.value + id: authenticatorFromRemediation.form!.value + .find(({ name }) => name === 'id')!.value, + enrollmentId: authenticatorFromRemediation.form!.value .find(({ name }) => name === 'enrollmentId')?.value, - methodType: authenticatorData.methodType, + methodType: authenticatorData!.methodType, }; } getInputAuthenticator() { const authenticator = this.getAuthenticatorFromRemediation(); - const methodType = authenticator.form.value.find(({ name }) => name === 'methodType'); + const methodType = authenticator.form!.value.find(({ name }) => name === 'methodType'); // if has methodType in form, let user select the methodType if (methodType && methodType.options) { return { name: 'methodType', type: 'string', required: true }; } // no methodType, then return form values - const inputs = [...authenticator.form.value]; + const inputs = [...authenticator.form!.value]; return inputs; } diff --git a/lib/idx/remediators/Base/AuthenticatorData.ts b/lib/idx/remediators/Base/AuthenticatorData.ts index bda8cc8a9..fa82075d3 100644 --- a/lib/idx/remediators/Base/AuthenticatorData.ts +++ b/lib/idx/remediators/Base/AuthenticatorData.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -26,14 +27,14 @@ export class AuthenticatorData extends Remediator { 'authenticator': [] }; - values: AuthenticatorDataValues; + values!: AuthenticatorDataValues; authenticator: Authenticator; constructor(remediation: IdxRemediation, values: AuthenticatorDataValues = {}) { super(remediation, values); // set before other data calculation - this.authenticator = this.getAuthenticator(); + this.authenticator = this.getAuthenticator()!; this.formatAuthenticatorData(); } @@ -41,7 +42,7 @@ export class AuthenticatorData extends Remediator { protected formatAuthenticatorData() { const authenticatorData = this.getAuthenticatorData(); if (authenticatorData) { - this.values.authenticatorsData = this.values.authenticatorsData.map(data => { + this.values.authenticatorsData = this.values.authenticatorsData!.map(data => { if (data.key === this.authenticator.key) { return this.mapAuthenticatorDataFromValues(data); } @@ -50,18 +51,18 @@ export class AuthenticatorData extends Remediator { } else { const data = this.mapAuthenticatorDataFromValues(); if (data) { - this.values.authenticatorsData.push(data); + this.values.authenticatorsData!.push(data); } } } protected getAuthenticatorData() { - return this.values.authenticatorsData + return this.values.authenticatorsData! .find(({ key }) => key === this.authenticator.key); } canRemediate() { - return this.values.authenticatorsData + return this.values.authenticatorsData! .some(data => data.key === this.authenticator.key); } @@ -87,20 +88,20 @@ export class AuthenticatorData extends Remediator { } protected getAuthenticatorFromRemediation(): IdxRemediationValue { - const authenticator = this.remediation.value - .find(({ name }) => name === 'authenticator'); + const authenticator = this.remediation.value! + .find(({ name }) => name === 'authenticator') as IdxRemediationValue; return authenticator; } private getMethodTypes(): IdxOption[] { const authenticator: IdxRemediationValue = this.getAuthenticatorFromRemediation(); - return authenticator.form.value.find(({ name }) => name === 'methodType')?.options; + return authenticator.form!.value.find(({ name }) => name === 'methodType')?.options as IdxOption[]; } getValuesAfterProceed(): RemediationValues { this.values = super.getValuesAfterProceed(); // remove used authenticatorData - const authenticatorsData = this.values.authenticatorsData + const authenticatorsData = this.values.authenticatorsData! .filter(data => data.key !== this.authenticator.key); return { ...this.values, authenticatorsData }; } diff --git a/lib/idx/remediators/Base/Remediator.ts b/lib/idx/remediators/Base/Remediator.ts index 38ae97f04..9deb2b6e6 100644 --- a/lib/idx/remediators/Base/Remediator.ts +++ b/lib/idx/remediators/Base/Remediator.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -85,7 +86,7 @@ export class Remediator { return false; } const required = getRequiredValues(this.remediation); - const needed = required.find((key) => !this.hasData(key)); + const needed = required!.find((key) => !this.hasData(key)); if (needed) { return false; // missing data for a required field } @@ -97,7 +98,7 @@ export class Remediator { if (!key) { let allValues = getAllValues(this.remediation); - let res = allValues.reduce((data, key) => { + let res = allValues!.reduce((data, key) => { data[key] = this.getData(key); // recursive return data; }, {}); @@ -107,7 +108,7 @@ export class Remediator { // Map value by "map${Property}" function in each subClass if (typeof this[`map${titleCase(key)}`] === 'function') { return this[`map${titleCase(key)}`]( - this.remediation.value.find(({name}) => name === key) + this.remediation.value!.find(({name}) => name === key) ); } @@ -166,13 +167,13 @@ export class Remediator { } return Object.keys(this.map).reduce((inputs, key) => { - const inputFromRemediation = this.remediation.value.find(item => item.name === key); + const inputFromRemediation = this.remediation.value!.find(item => item.name === key); if (!inputFromRemediation) { return inputs; } - let input: Input; - const aliases = this.map[key]; + let input; + const aliases = this.map![key]; const { type } = inputFromRemediation; if (typeof this[`getInput${titleCase(key)}`] === 'function') { input = this[`getInput${titleCase(key)}`](inputFromRemediation); @@ -195,9 +196,9 @@ export class Remediator { } if (Array.isArray(input)) { - input.forEach(i => inputs.push(i)); + input.forEach(i => inputs.push(i as never)); } else { - inputs.push(input); + inputs.push(input as never); } return inputs; }, []); @@ -209,7 +210,7 @@ export class Remediator { } return remediation.value[0]?.form?.value.reduce((messages, field) => { if (field.messages) { - messages = [...messages, ...field.messages.value]; + messages = [...messages, ...field.messages.value] as never; } return messages; }, []); diff --git a/lib/idx/remediators/Base/SelectAuthenticator.ts b/lib/idx/remediators/Base/SelectAuthenticator.ts index c84af46a7..edb2285d5 100644 --- a/lib/idx/remediators/Base/SelectAuthenticator.ts +++ b/lib/idx/remediators/Base/SelectAuthenticator.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -13,7 +14,7 @@ import { Remediator, RemediationValues } from './Remediator'; import { getAuthenticatorFromRemediation } from '../util'; -import { IdxAuthenticator, IdxRemediationValue } from '../../types/idx-js'; +import { IdxAuthenticator, IdxOption, IdxRemediationValue } from '../../types/idx-js'; import { Authenticator } from '../../types'; // Find matched authenticator in provided order @@ -35,8 +36,8 @@ export type SelectAuthenticatorValues = RemediationValues & { // Base class - DO NOT expose static remediationName export class SelectAuthenticator extends Remediator { - values: SelectAuthenticatorValues; - selectedAuthenticator: IdxAuthenticator; + values!: SelectAuthenticatorValues; + selectedAuthenticator?: IdxAuthenticator; map = { authenticator: [] @@ -62,11 +63,12 @@ export class SelectAuthenticator extends Remediator { getNextStep() { const common = super.getNextStep(); const authenticatorFromRemediation = getAuthenticatorFromRemediation(this.remediation); - const options = authenticatorFromRemediation.options.map(option => { + const options = authenticatorFromRemediation.options!.map(option => { const { label, - relatesTo: { key } - } = option; + relatesTo + } = option as IdxOption; + const key = relatesTo!.key!; return { label, value: key }; }); return { ...common, options }; @@ -92,7 +94,7 @@ export class SelectAuthenticator extends Remediator { // remove used authenticators const authenticators = (this.values.authenticators as Authenticator[]) .filter(authenticator => { - return authenticator.key !== this.selectedAuthenticator.key; + return authenticator.key !== this.selectedAuthenticator!.key; }); return { ...this.values, authenticators }; } diff --git a/lib/idx/remediators/Base/VerifyAuthenticator.ts b/lib/idx/remediators/Base/VerifyAuthenticator.ts index 0ad368aa4..7eb1698fb 100644 --- a/lib/idx/remediators/Base/VerifyAuthenticator.ts +++ b/lib/idx/remediators/Base/VerifyAuthenticator.ts @@ -28,7 +28,7 @@ export interface VerifyAuthenticatorValues extends RemediationValues { export class VerifyAuthenticator extends Remediator { authenticator: Authenticator; - values: VerifyAuthenticatorValues; + values!: VerifyAuthenticatorValues; map = { 'credentials': [] diff --git a/lib/idx/remediators/ChallengeAuthenticator.ts b/lib/idx/remediators/ChallengeAuthenticator.ts index 582138911..a3bd32646 100644 --- a/lib/idx/remediators/ChallengeAuthenticator.ts +++ b/lib/idx/remediators/ChallengeAuthenticator.ts @@ -10,12 +10,11 @@ * See the License for the specific language governing permissions and limitations under the License. */ - import { VerifyAuthenticator, VerifyAuthenticatorValues } from './Base/VerifyAuthenticator'; export type ChallengeAuthenticatorValues = VerifyAuthenticatorValues; export class ChallengeAuthenticator extends VerifyAuthenticator { static remediationName = 'challenge-authenticator'; - values: ChallengeAuthenticatorValues; + values!: ChallengeAuthenticatorValues; } diff --git a/lib/idx/remediators/EnrollAuthenticator.ts b/lib/idx/remediators/EnrollAuthenticator.ts index 55974e60a..c1f28b430 100644 --- a/lib/idx/remediators/EnrollAuthenticator.ts +++ b/lib/idx/remediators/EnrollAuthenticator.ts @@ -17,5 +17,5 @@ export type EnrollAuthenticatorValues = VerifyAuthenticatorValues; export class EnrollAuthenticator extends VerifyAuthenticator { static remediationName = 'enroll-authenticator'; - values: EnrollAuthenticatorValues; + values!: EnrollAuthenticatorValues; } diff --git a/lib/idx/remediators/EnrollPoll.ts b/lib/idx/remediators/EnrollPoll.ts index 368ede5f5..96cb17a95 100644 --- a/lib/idx/remediators/EnrollPoll.ts +++ b/lib/idx/remediators/EnrollPoll.ts @@ -22,7 +22,7 @@ export interface EnrollPollValues extends RemediationValues { export class EnrollPoll extends Remediator { static remediationName = 'enroll-poll'; - values: EnrollPollValues; + values!: EnrollPollValues; canRemediate() { return Boolean(this.values.startPolling); diff --git a/lib/idx/remediators/EnrollProfile.ts b/lib/idx/remediators/EnrollProfile.ts index b6fece95a..32a5616aa 100644 --- a/lib/idx/remediators/EnrollProfile.ts +++ b/lib/idx/remediators/EnrollProfile.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -11,6 +12,7 @@ */ +import { IdxRemediationValue } from '../types/idx-js'; import { Remediator, RemediationValues } from './Base/Remediator'; export interface EnrollProfileValues extends RemediationValues { @@ -22,7 +24,7 @@ export interface EnrollProfileValues extends RemediationValues { export class EnrollProfile extends Remediator { static remediationName = 'enroll-profile'; - values: EnrollProfileValues; + values!: EnrollProfileValues; map = { 'userProfile': [] @@ -30,8 +32,9 @@ export class EnrollProfile extends Remediator { canRemediate() { const userProfileFromValues = this.getData().userProfile; - const userProfileFromRemediation = this.remediation.value.find(({ name }) => name === 'userProfile'); - return userProfileFromRemediation.form.value.reduce((canRemediate, curr) => { + // eslint-disable-next-line max-len + const userProfileFromRemediation = this.remediation.value!.find(({ name }) => name === 'userProfile') as IdxRemediationValue; + return userProfileFromRemediation.form!.value.reduce((canRemediate, curr) => { if (curr.required) { canRemediate = canRemediate && !!userProfileFromValues[curr.name]; } diff --git a/lib/idx/remediators/Identify.ts b/lib/idx/remediators/Identify.ts index 58795f05d..c9e6b9bc7 100644 --- a/lib/idx/remediators/Identify.ts +++ b/lib/idx/remediators/Identify.ts @@ -21,7 +21,7 @@ export interface IdentifyValues extends RemediationValues { export class Identify extends Remediator { static remediationName = 'identify'; - values: IdentifyValues; + values!: IdentifyValues; map = { 'identifier': ['username'], diff --git a/lib/idx/remediators/ReEnrollAuthenticator.ts b/lib/idx/remediators/ReEnrollAuthenticator.ts index cedca12e8..57cc86df6 100644 --- a/lib/idx/remediators/ReEnrollAuthenticator.ts +++ b/lib/idx/remediators/ReEnrollAuthenticator.ts @@ -20,7 +20,7 @@ export interface ReEnrollAuthenticatorValues extends RemediationValues { export class ReEnrollAuthenticator extends Remediator { static remediationName = 'reenroll-authenticator'; - values: ReEnrollAuthenticatorValues; + values!: ReEnrollAuthenticatorValues; map = { 'credentials': [] @@ -33,7 +33,8 @@ export class ReEnrollAuthenticator extends Remediator { } getInputCredentials(input) { - const challengeType = this.getAuthenticator().type; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const challengeType = this.getAuthenticator()!.type; const name = challengeType === 'password' ? 'newPassword' : 'verificationCode'; return { ...input.form.value[0], diff --git a/lib/idx/remediators/ResetAuthenticator.ts b/lib/idx/remediators/ResetAuthenticator.ts index 4743acc49..85c602113 100644 --- a/lib/idx/remediators/ResetAuthenticator.ts +++ b/lib/idx/remediators/ResetAuthenticator.ts @@ -17,5 +17,5 @@ export type ResetAuthenticatorValues = VerifyAuthenticatorValues; export class ResetAuthenticator extends VerifyAuthenticator { static remediationName = 'reset-authenticator'; - values: ResetAuthenticatorValues; + values!: ResetAuthenticatorValues; } diff --git a/lib/idx/remediators/SelectAuthenticatorAuthenticate.ts b/lib/idx/remediators/SelectAuthenticatorAuthenticate.ts index 25f62947c..88ac4176d 100644 --- a/lib/idx/remediators/SelectAuthenticatorAuthenticate.ts +++ b/lib/idx/remediators/SelectAuthenticatorAuthenticate.ts @@ -22,17 +22,17 @@ export type SelectAuthenticatorAuthenticateValues = SelectAuthenticatorValues & export class SelectAuthenticatorAuthenticate extends SelectAuthenticator { static remediationName = 'select-authenticator-authenticate'; - values: SelectAuthenticatorAuthenticateValues; + values!: SelectAuthenticatorAuthenticateValues; constructor(remediation: IdxRemediation, values: SelectAuthenticatorValues = {}) { super(remediation, values); // Preset password authenticator to trigger recover action const hasPasswordInOptions = getAuthenticatorFromRemediation(remediation) - .options?.some(({ relatesTo }) => relatesTo.key === AuthenticatorKey.OKTA_PASSWORD); + .options?.some(({ relatesTo }) => relatesTo?.key === AuthenticatorKey.OKTA_PASSWORD); if (hasPasswordInOptions && (this.values.flow === 'recoverPassword' || this.values.password)) { this.values.authenticators = [ - ...this.values.authenticators, + ...this.values.authenticators || [], { key: AuthenticatorKey.OKTA_PASSWORD } ] as Authenticator[]; } diff --git a/lib/idx/remediators/SelectAuthenticatorEnroll.ts b/lib/idx/remediators/SelectAuthenticatorEnroll.ts index 26d4c0c65..a75543f94 100644 --- a/lib/idx/remediators/SelectAuthenticatorEnroll.ts +++ b/lib/idx/remediators/SelectAuthenticatorEnroll.ts @@ -17,5 +17,5 @@ export type SelectAuthenticatorEnrollValues = SelectAuthenticatorValues; export class SelectAuthenticatorEnroll extends SelectAuthenticator { static remediationName = 'select-authenticator-enroll'; - values: SelectAuthenticatorEnrollValues; + values!: SelectAuthenticatorEnrollValues; } diff --git a/lib/idx/remediators/SelectEnrollProfile.ts b/lib/idx/remediators/SelectEnrollProfile.ts index d47e43cbb..83cd52096 100644 --- a/lib/idx/remediators/SelectEnrollProfile.ts +++ b/lib/idx/remediators/SelectEnrollProfile.ts @@ -19,7 +19,7 @@ export interface SelectEnrollProfileValues extends RemediationValues {} export class SelectEnrollProfile extends Remediator { static remediationName = 'select-enroll-profile'; - values: SelectEnrollProfileValues; + values!: SelectEnrollProfileValues; canRemediate() { return true; diff --git a/lib/idx/remediators/Skip.ts b/lib/idx/remediators/Skip.ts index 5052635f0..72a9610e4 100644 --- a/lib/idx/remediators/Skip.ts +++ b/lib/idx/remediators/Skip.ts @@ -20,7 +20,7 @@ export interface SkipValues extends RemediationValues { export class Skip extends Remediator { static remediationName = 'skip'; - values: SkipValues; + values!: SkipValues; map = { skip: ['skip'] diff --git a/lib/idx/remediators/util.ts b/lib/idx/remediators/util.ts index a68012939..53b6c2088 100644 --- a/lib/idx/remediators/util.ts +++ b/lib/idx/remediators/util.ts @@ -14,13 +14,13 @@ import { IdxRemediation, IdxRemediationValue } from '../types/idx-js'; export function getAllValues(idxRemediation: IdxRemediation) { - return idxRemediation.value.map(r => r.name); + return idxRemediation.value?.map(r => r.name); } export function getRequiredValues(idxRemediation: IdxRemediation) { - return idxRemediation.value.reduce((required, cur) => { + return idxRemediation.value?.reduce((required, cur) => { if (cur.required) { - required.push(cur.name); + required.push(cur.name as never); } return required; }, []); @@ -33,5 +33,6 @@ export function titleCase(str: string) { export function getAuthenticatorFromRemediation( remediation: IdxRemediation ): IdxRemediationValue { - return remediation.value.find(({ name }) => name === 'authenticator'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return remediation.value!.find(({ name }) => name === 'authenticator') as IdxRemediationValue; } diff --git a/lib/idx/run.ts b/lib/idx/run.ts index d1cf6ef39..c442f09c6 100644 --- a/lib/idx/run.ts +++ b/lib/idx/run.ts @@ -41,15 +41,15 @@ function getEnabledFeatures(idxResponse: IdxResponse): IdxFeature[] { const { actions, neededToProceed } = idxResponse; if (actions['currentAuthenticator-recover']) { - res.push(IdxFeature.PASSWORD_RECOVERY); + res.push(IdxFeature.PASSWORD_RECOVERY as never); } if (neededToProceed.some(({ name }) => name === 'select-enroll-profile')) { - res.push(IdxFeature.REGISTRATION); + res.push(IdxFeature.REGISTRATION as never); } if (neededToProceed.some(({ name }) => name === 'redirect-idp')) { - res.push(IdxFeature.SOCIAL_IDP); + res.push(IdxFeature.SOCIAL_IDP as never); } return res; @@ -70,7 +70,7 @@ function getAvailableSteps(remediations: IdxRemediation[]): NextStep[] { const T = remediatorMap[remediation.name]; if (T) { const remediator = new T(remediation); - res.push (remediator.getNextStep()); + res.push (remediator.getNextStep() as never); } } diff --git a/lib/idx/transactionMeta.ts b/lib/idx/transactionMeta.ts index 982cd28d5..495a7825a 100644 --- a/lib/idx/transactionMeta.ts +++ b/lib/idx/transactionMeta.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2021, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -10,53 +11,30 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { OktaAuth, IdxTransactionMeta, TransactionMetaOptions } from '../types'; +import { OktaAuth, IdxTransactionMeta, TransactionMetaOptions, PKCETransactionMeta } from '../types'; import { removeNils, warn } from '../util'; -import { getOAuthUrls } from '../oidc'; +import { createOAuthMeta } from '../oidc'; // Calculate new values -export async function createTransactionMeta(authClient: OktaAuth, options: TransactionMetaOptions = {}) { +export async function createTransactionMeta( + authClient: OktaAuth, + options: TransactionMetaOptions = {} +): Promise { const tokenParams = await authClient.token.prepareTokenParams(options); - const { - clientId, - redirectUri, - responseType, - responseMode, - scopes, - state, - nonce, - ignoreSignature, - codeVerifier, - codeChallengeMethod, - codeChallenge, - } = tokenParams; - const urls = getOAuthUrls(authClient, tokenParams); + const pkceMeta = createOAuthMeta(authClient, tokenParams) as PKCETransactionMeta; let { flow = 'default', withCredentials = true, - activationToken, - recoveryToken + activationToken = undefined, + recoveryToken = undefined, } = { ...authClient.options, ...options }; // local options override SDK options - const issuer = authClient.options.issuer; const meta: IdxTransactionMeta = { - withCredentials, + ...pkceMeta, flow, - issuer, - clientId, - redirectUri, - responseType, - responseMode, - scopes, - state, - nonce, - urls, - ignoreSignature, - codeVerifier, - codeChallengeMethod, - codeChallenge, + withCredentials, activationToken, - recoveryToken + recoveryToken, }; return meta; } @@ -70,7 +48,10 @@ export function hasSavedInteractionHandle(authClient: OktaAuth, options?: Transa } // Returns the saved transaction meta, if it exists and is valid -export function getSavedTransactionMeta(authClient: OktaAuth, options?: TransactionMetaOptions): IdxTransactionMeta { +export function getSavedTransactionMeta( + authClient: OktaAuth, + options?: TransactionMetaOptions +): IdxTransactionMeta | undefined { options = removeNils(options); options = { ...authClient.options, ...options }; // local options override SDK options let savedMeta; @@ -81,7 +62,7 @@ export function getSavedTransactionMeta(authClient: OktaAuth, options?: Transact } if (!savedMeta) { - return null; + return; } if (isTransactionMetaValid(savedMeta, options)) { diff --git a/lib/oidc/endpoints/authorize.ts b/lib/oidc/endpoints/authorize.ts index e5e991033..6fc291e42 100644 --- a/lib/oidc/endpoints/authorize.ts +++ b/lib/oidc/endpoints/authorize.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -51,11 +52,11 @@ export function convertTokenParamsToOAuthParams(tokenParams: TokenParams) { } }); - if (tokenParams.responseType.indexOf('id_token') !== -1 && - tokenParams.scopes.indexOf('openid') === -1) { + if (tokenParams.responseType!.indexOf('id_token') !== -1 && + tokenParams.scopes!.indexOf('openid') === -1) { throw new AuthSdkError('openid scope must be specified in the scopes argument when requesting an id_token'); } else { - oauthParams.scope = tokenParams.scopes.join(' '); + oauthParams.scope = tokenParams.scopes!.join(' '); } return oauthParams; diff --git a/lib/oidc/endpoints/token.ts b/lib/oidc/endpoints/token.ts index c83fe5f72..14f0946ee 100644 --- a/lib/oidc/endpoints/token.ts +++ b/lib/oidc/endpoints/token.ts @@ -90,7 +90,8 @@ export function postRefreshToken(sdk, options: TokenParams, refreshToken: Refres scope: refreshToken.scopes.join(' '), refresh_token: refreshToken.refreshToken, // eslint-disable-line camelcase }).map(function ([name, value]) { - return name + '=' + encodeURIComponent(value); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return name + '=' + encodeURIComponent(value!); }).join('&'), }); } \ No newline at end of file diff --git a/lib/oidc/exchangeCodeForTokens.ts b/lib/oidc/exchangeCodeForTokens.ts index a14f7ec71..6149038fb 100644 --- a/lib/oidc/exchangeCodeForTokens.ts +++ b/lib/oidc/exchangeCodeForTokens.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable max-len */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. @@ -49,7 +50,7 @@ export function exchangeCodeForTokens(sdk: OktaAuth, tokenParams: TokenParams, u // Here we modify the response from `/token` so that it more closely matches a response from `/authorize` // `responseType` is used to validate that the expected tokens were returned const responseType = ['token']; // an accessToken will always be returned - if (scopes.indexOf('openid') !== -1) { + if (scopes!.indexOf('openid') !== -1) { responseType.push('id_token'); // an idToken will be returned if "openid" is in the scopes } const handleResponseOptions: TokenParams = { @@ -59,11 +60,11 @@ export function exchangeCodeForTokens(sdk: OktaAuth, tokenParams: TokenParams, u responseType, ignoreSignature, }; - return handleOAuthResponse(sdk, handleResponseOptions, response, urls) + return handleOAuthResponse(sdk, handleResponseOptions, response, urls!) .then((response: TokenResponse) => { // For compatibility, "code" is returned in the TokenResponse. OKTA-326091 response.code = authorizationCode; - response.state = state; + response.state = state!; return response; }); }) diff --git a/lib/oidc/getToken.ts b/lib/oidc/getToken.ts index 6936d6778..0de7554ba 100644 --- a/lib/oidc/getToken.ts +++ b/lib/oidc/getToken.ts @@ -24,6 +24,7 @@ import { OktaAuth, TokenParams, PopupParams, + OAuthResponse, } from '../types'; import { prepareTokenParams } from './util/prepareTokenParams'; @@ -139,11 +140,11 @@ export function getToken(sdk: OktaAuth, options: TokenParams & PopupParams) { var iframeEl = loadFrame(requestUrl); return iframePromise .then(function (res) { - return handleOAuthResponse(sdk, tokenParams, res, urls); + return handleOAuthResponse(sdk, tokenParams, res as OAuthResponse, urls); }) .finally(function () { if (document.body.contains(iframeEl)) { - iframeEl.parentElement.removeChild(iframeEl); + iframeEl.parentElement?.removeChild(iframeEl); } }); @@ -188,7 +189,7 @@ export function getToken(sdk: OktaAuth, options: TokenParams & PopupParams) { return popupPromise .then(function (res) { - return handleOAuthResponse(sdk, tokenParams, res, urls); + return handleOAuthResponse(sdk, tokenParams, res as OAuthResponse, urls); }) .finally(function () { if (popupWindow && !popupWindow.closed) { diff --git a/lib/oidc/getWithRedirect.ts b/lib/oidc/getWithRedirect.ts index c0d0ccce1..f1abdeab1 100644 --- a/lib/oidc/getWithRedirect.ts +++ b/lib/oidc/getWithRedirect.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -11,54 +12,21 @@ * */ import { AuthSdkError } from '../errors'; -import { OktaAuth, TokenParams, TransactionMeta } from '../types'; +import { OktaAuth, TokenParams } from '../types'; import { clone } from '../util'; -import { getOAuthUrls, prepareTokenParams } from './util'; +import { prepareTokenParams, createOAuthMeta } from './util'; import { buildAuthorizeParams } from './endpoints/authorize'; -export function getWithRedirect(sdk: OktaAuth, options: TokenParams): Promise { +export async function getWithRedirect(sdk: OktaAuth, options?: TokenParams): Promise { if (arguments.length > 2) { return Promise.reject(new AuthSdkError('As of version 3.0, "getWithRedirect" takes only a single set of options')); } options = clone(options) || {}; - return prepareTokenParams(sdk, options) - .then(function (tokenParams: TokenParams) { - const urls = getOAuthUrls(sdk, options); - const requestUrl = urls.authorizeUrl + buildAuthorizeParams(tokenParams); - const issuer = sdk.options.issuer; - - // Gather the values we want to save in the transaction - const { - responseType, - state, - nonce, - scopes, - clientId, - ignoreSignature, - redirectUri, - codeVerifier, - codeChallenge, - codeChallengeMethod, - } = tokenParams; - - const oauthMeta: TransactionMeta = { - issuer, - responseType, - state, - nonce, - scopes, - clientId, - urls, - ignoreSignature, - redirectUri, - codeVerifier, - codeChallenge, - codeChallengeMethod - }; - - sdk.transactionManager.save(oauthMeta, { oauth: true }); - sdk.token.getWithRedirect._setLocation(requestUrl); - }); + const tokenParams = await prepareTokenParams(sdk, options); + const meta = createOAuthMeta(sdk, tokenParams); + const requestUrl = meta.urls.authorizeUrl + buildAuthorizeParams(tokenParams); + sdk.transactionManager.save(meta, { oauth: true }); + sdk.token.getWithRedirect._setLocation(requestUrl); } diff --git a/lib/oidc/handleOAuthResponse.ts b/lib/oidc/handleOAuthResponse.ts index e57098aac..d86fc847d 100644 --- a/lib/oidc/handleOAuthResponse.ts +++ b/lib/oidc/handleOAuthResponse.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable complexity, max-statements */ /*! @@ -32,7 +33,7 @@ import { verifyToken } from './verifyToken'; import { getDefaultTokenParams } from './util'; function validateResponse(res: OAuthResponse, oauthParams: TokenParams) { - if (res['error'] || res['error_description']) { + if (res['error'] && res['error_description']) { throw new OAuthError(res['error'], res['error_description']); } @@ -41,8 +42,12 @@ function validateResponse(res: OAuthResponse, oauthParams: TokenParams) { } } -// eslint-disable-next-line max-len -export function handleOAuthResponse(sdk: OktaAuth, tokenParams: TokenParams, res: OAuthResponse, urls: CustomUrls): Promise { +export async function handleOAuthResponse( + sdk: OktaAuth, + tokenParams: TokenParams, + res: OAuthResponse, + urls?: CustomUrls +): Promise { var pkce = sdk.options.pkce !== false; // The result contains an authorization_code and PKCE is enabled @@ -57,7 +62,7 @@ export function handleOAuthResponse(sdk: OktaAuth, tokenParams: TokenParams, res tokenParams = tokenParams || getDefaultTokenParams(sdk); urls = urls || getOAuthUrls(sdk, tokenParams); - var responseType = tokenParams.responseType; + var responseType = tokenParams.responseType || []; if (!Array.isArray(responseType)) { responseType = [responseType]; } @@ -71,92 +76,83 @@ export function handleOAuthResponse(sdk: OktaAuth, tokenParams: TokenParams, res var clientId = tokenParams.clientId || sdk.options.clientId; // Handling the result from implicit flow or PKCE token exchange - return Promise.resolve() - .then(function () { - validateResponse(res, tokenParams); - }).then(function () { - var tokenDict = {} as Tokens; - var expiresIn = res.expires_in; - var tokenType = res.token_type; - var accessToken = res.access_token; - var idToken = res.id_token; - var refreshToken = res.refresh_token; - var now = Math.floor(Date.now()/1000); - - if (accessToken) { - var accessJwt = sdk.token.decode(accessToken); - tokenDict.accessToken = { - accessToken: accessToken, - claims: accessJwt.payload, - expiresAt: Number(expiresIn) + now, - tokenType: tokenType, - scopes: scopes, - authorizeUrl: urls.authorizeUrl, - userinfoUrl: urls.userinfoUrl - }; - } - - if (refreshToken) { - tokenDict.refreshToken = { - refreshToken: refreshToken, - // should not be used, this is the accessToken expire time - // TODO: remove "expiresAt" in the next major version OKTA-407224 - expiresAt: Number(expiresIn) + now, - scopes: scopes, - tokenUrl: urls.tokenUrl, - authorizeUrl: urls.authorizeUrl, - issuer: urls.issuer, - }; - } - - if (idToken) { - var idJwt = sdk.token.decode(idToken); - - var idTokenObj: IDToken = { - idToken: idToken, - claims: idJwt.payload, - expiresAt: idJwt.payload.exp - idJwt.payload.iat + now, // adjusting expiresAt to be in local time - scopes: scopes, - authorizeUrl: urls.authorizeUrl, - issuer: urls.issuer, - clientId: clientId - }; - - var validationParams: TokenVerifyParams = { - clientId: clientId, - issuer: urls.issuer, - nonce: tokenParams.nonce, - accessToken: accessToken - }; - - if (tokenParams.ignoreSignature !== undefined) { - validationParams.ignoreSignature = tokenParams.ignoreSignature; - } - - return verifyToken(sdk, idTokenObj, validationParams) - .then(function () { - tokenDict.idToken = idTokenObj; - return tokenDict; - }); - } - - return tokenDict; - }) - .then(function (tokenDict): TokenResponse { - // Validate received tokens against requested response types - if (responseType.indexOf('token') !== -1 && !tokenDict.accessToken) { - // eslint-disable-next-line max-len - throw new AuthSdkError('Unable to parse OAuth flow response: response type "token" was requested but "access_token" was not returned.'); - } - if (responseType.indexOf('id_token') !== -1 && !tokenDict.idToken) { - // eslint-disable-next-line max-len - throw new AuthSdkError('Unable to parse OAuth flow response: response type "id_token" was requested but "id_token" was not returned.'); - } - - return { - tokens: tokenDict, - state: res.state, - code: res.code - }; - }); + validateResponse(res, tokenParams); + + var tokenDict = {} as Tokens; + var expiresIn = res.expires_in; + var tokenType = res.token_type; + var accessToken = res.access_token; + var idToken = res.id_token; + var refreshToken = res.refresh_token; + var now = Math.floor(Date.now()/1000); + + if (accessToken) { + var accessJwt = sdk.token.decode(accessToken); + tokenDict.accessToken = { + accessToken: accessToken, + claims: accessJwt.payload, + expiresAt: Number(expiresIn) + now, + tokenType: tokenType!, + scopes: scopes, + authorizeUrl: urls.authorizeUrl!, + userinfoUrl: urls.userinfoUrl! + }; + } + + if (refreshToken) { + tokenDict.refreshToken = { + refreshToken: refreshToken, + // should not be used, this is the accessToken expire time + // TODO: remove "expiresAt" in the next major version OKTA-407224 + expiresAt: Number(expiresIn) + now, + scopes: scopes, + tokenUrl: urls.tokenUrl!, + authorizeUrl: urls.authorizeUrl!, + issuer: urls.issuer!, + }; + } + + if (idToken) { + var idJwt = sdk.token.decode(idToken); + var idTokenObj: IDToken = { + idToken: idToken, + claims: idJwt.payload, + expiresAt: idJwt.payload.exp! - idJwt.payload.iat! + now, // adjusting expiresAt to be in local time + scopes: scopes, + authorizeUrl: urls.authorizeUrl!, + issuer: urls.issuer!, + clientId: clientId! + }; + + var validationParams: TokenVerifyParams = { + clientId: clientId!, + issuer: urls.issuer!, + nonce: tokenParams.nonce, + accessToken: accessToken + }; + + if (tokenParams.ignoreSignature !== undefined) { + validationParams.ignoreSignature = tokenParams.ignoreSignature; + } + + await verifyToken(sdk, idTokenObj, validationParams); + tokenDict.idToken = idTokenObj; + } + + // Validate received tokens against requested response types + if (responseType.indexOf('token') !== -1 && !tokenDict.accessToken) { + // eslint-disable-next-line max-len + throw new AuthSdkError('Unable to parse OAuth flow response: response type "token" was requested but "access_token" was not returned.'); + } + if (responseType.indexOf('id_token') !== -1 && !tokenDict.idToken) { + // eslint-disable-next-line max-len + throw new AuthSdkError('Unable to parse OAuth flow response: response type "id_token" was requested but "id_token" was not returned.'); + } + + return { + tokens: tokenDict, + state: res.state!, + code: res.code + }; + } \ No newline at end of file diff --git a/lib/oidc/parseFromUrl.ts b/lib/oidc/parseFromUrl.ts index 49070c777..7b1832f3c 100644 --- a/lib/oidc/parseFromUrl.ts +++ b/lib/oidc/parseFromUrl.ts @@ -84,7 +84,7 @@ export function cleanOAuthResponseFromUrl(sdk, options: ParseFromUrlOptions) { responseMode === 'query' ? removeSearch(sdk) : removeHash(sdk); } -export async function parseFromUrl(sdk, options: string | ParseFromUrlOptions): Promise { +export async function parseFromUrl(sdk, options?: string | ParseFromUrlOptions): Promise { options = options || {}; if (isString(options)) { options = { url: options } as ParseFromUrlOptions; diff --git a/lib/oidc/renewToken.ts b/lib/oidc/renewToken.ts index 025239824..15d711423 100644 --- a/lib/oidc/renewToken.ts +++ b/lib/oidc/renewToken.ts @@ -33,7 +33,7 @@ function getSingleToken(originalToken: Token, tokens: Tokens) { } // If we have a refresh token, renew using that, otherwise getWithoutPrompt -export async function renewToken(sdk: OktaAuth, token: Token): Promise { +export async function renewToken(sdk: OktaAuth, token: Token): Promise { if (!isIDToken(token) && !isAccessToken(token)) { throwInvalidTokenError(); } diff --git a/lib/oidc/renewTokens.ts b/lib/oidc/renewTokens.ts index 9bf36352a..58fde4f04 100644 --- a/lib/oidc/renewTokens.ts +++ b/lib/oidc/renewTokens.ts @@ -18,10 +18,10 @@ import { getDefaultTokenParams } from './util'; // If we have a refresh token, renew using that, otherwise getWithoutPrompt // eslint-disable-next-line complexity -export async function renewTokens(sdk, options: TokenParams): Promise { +export async function renewTokens(sdk, options?: TokenParams): Promise { const tokens = sdk.tokenManager.getTokensSync(); if (tokens.refreshToken) { - return renewTokensWithRefresh(sdk, options, tokens.refreshToken); + return renewTokensWithRefresh(sdk, options || {}, tokens.refreshToken); } if (!tokens.accessToken && !tokens.idToken) { diff --git a/lib/oidc/revokeToken.ts b/lib/oidc/revokeToken.ts index ba0981979..f63fe7d29 100644 --- a/lib/oidc/revokeToken.ts +++ b/lib/oidc/revokeToken.ts @@ -27,36 +27,33 @@ import { } from '../types'; // refresh tokens have precedence to be revoked if no token is specified -export function revokeToken(sdk: OktaAuth, token: RevocableToken): Promise { - return Promise.resolve() - .then(function () { - var accessToken: string; - var refreshToken: string; - if (token) { - accessToken = (token as AccessToken).accessToken; - refreshToken = (token as RefreshToken).refreshToken; - } - - if(!accessToken && !refreshToken) { - throw new AuthSdkError('A valid access or refresh token object is required'); - } - var clientId = sdk.options.clientId; - var clientSecret = sdk.options.clientSecret; - if (!clientId) { - throw new AuthSdkError('A clientId must be specified in the OktaAuth constructor to revoke a token'); - } - var revokeUrl = getOAuthUrls(sdk).revokeUrl; - var args = toQueryString({ - // eslint-disable-next-line camelcase - token_type_hint: refreshToken ? 'refresh_token' : 'access_token', - token: refreshToken || accessToken, - }).slice(1); - var creds = clientSecret ? btoa(`${clientId}:${clientSecret}`) : btoa(clientId); - return post(sdk, revokeUrl, args, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': 'Basic ' + creds - } - }); - }); -} \ No newline at end of file +export async function revokeToken(sdk: OktaAuth, token: RevocableToken): Promise { + let accessToken = ''; + let refreshToken = ''; + if (token) { + accessToken = (token as AccessToken).accessToken; + refreshToken = (token as RefreshToken).refreshToken; + } + if(!accessToken && !refreshToken) { + throw new AuthSdkError('A valid access or refresh token object is required'); + } + var clientId = sdk.options.clientId; + var clientSecret = sdk.options.clientSecret; + if (!clientId) { + throw new AuthSdkError('A clientId must be specified in the OktaAuth constructor to revoke a token'); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + var revokeUrl = getOAuthUrls(sdk).revokeUrl!; + var args = toQueryString({ + // eslint-disable-next-line camelcase + token_type_hint: refreshToken ? 'refresh_token' : 'access_token', + token: refreshToken || accessToken, + }).slice(1); + var creds = clientSecret ? btoa(`${clientId}:${clientSecret}`) : btoa(clientId); + return post(sdk, revokeUrl, args, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + creds + } + }); +} diff --git a/lib/oidc/util/index.ts b/lib/oidc/util/index.ts index 80145a385..7701b60f5 100644 --- a/lib/oidc/util/index.ts +++ b/lib/oidc/util/index.ts @@ -17,6 +17,7 @@ export * from './defaultTokenParams'; export * from './errors'; export * from './loginRedirect'; export * from './oauth'; +export * from './oauthMeta'; import pkce from './pkce'; export { pkce }; export * from './prepareTokenParams'; diff --git a/lib/oidc/util/loginRedirect.ts b/lib/oidc/util/loginRedirect.ts index d2ebaeb50..a9c5c4ae3 100644 --- a/lib/oidc/util/loginRedirect.ts +++ b/lib/oidc/util/loginRedirect.ts @@ -34,7 +34,10 @@ export function hasErrorInUrl(hashOrSearch: string): boolean { export function isRedirectUri(uri: string, sdk: OktaAuth): boolean { var authParams = sdk.options; - return uri && uri.indexOf(authParams.redirectUri) === 0; + if (!uri || !authParams.redirectUri) { + return false; + } + return uri.indexOf(authParams.redirectUri) === 0; } export function isCodeFlow(options: OktaAuthOptions) { diff --git a/lib/oidc/util/oauth.ts b/lib/oidc/util/oauth.ts index 2c3e8c7c8..a9fcb6349 100644 --- a/lib/oidc/util/oauth.ts +++ b/lib/oidc/util/oauth.ts @@ -40,7 +40,7 @@ export function getOAuthDomain(sdk: OktaAuth, options: CustomUrls = {}) { return domain; } -export function getOAuthUrls(sdk: OktaAuth, options?: CustomUrls) { +export function getOAuthUrls(sdk: OktaAuth, options?: CustomUrls): CustomUrls { if (arguments.length > 2) { throw new AuthSdkError('As of version 3.0, "getOAuthUrls" takes only a single set of options'); } diff --git a/lib/oidc/util/oauthMeta.ts b/lib/oidc/util/oauthMeta.ts new file mode 100644 index 000000000..1967081ba --- /dev/null +++ b/lib/oidc/util/oauthMeta.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { OAuthTransactionMeta, OktaAuth, PKCETransactionMeta, TokenParams } from '../../types'; +import { getOAuthUrls } from './oauth'; + +export function createOAuthMeta(sdk: OktaAuth, tokenParams: TokenParams): OAuthTransactionMeta | PKCETransactionMeta { + const issuer = sdk.options.issuer!; + const urls = getOAuthUrls(sdk, tokenParams); + const oauthMeta: OAuthTransactionMeta = { + issuer, + urls, + clientId: tokenParams.clientId!, + redirectUri: tokenParams.redirectUri!, + responseType: tokenParams.responseType!, + responseMode: tokenParams.responseMode!, + scopes: tokenParams.scopes!, + state: tokenParams.state!, + nonce: tokenParams.nonce!, + ignoreSignature: tokenParams.ignoreSignature!, + }; + + if (tokenParams.pkce === false) { + // Implicit flow or authorization_code without PKCE + return oauthMeta; + } + + const pkceMeta: PKCETransactionMeta = { + ...oauthMeta, + codeVerifier: tokenParams.codeVerifier!, + codeChallengeMethod: tokenParams.codeChallengeMethod!, + codeChallenge: tokenParams.codeChallenge!, + }; + + return pkceMeta; +} diff --git a/lib/oidc/util/pkce.ts b/lib/oidc/util/pkce.ts index 1a4ec9f03..2d9e499fc 100644 --- a/lib/oidc/util/pkce.ts +++ b/lib/oidc/util/pkce.ts @@ -38,7 +38,7 @@ function generateVerifier(prefix?: string): string { function computeChallenge(str: string): PromiseLike { var buffer = new TextEncoder().encode(str); return webcrypto.subtle.digest('SHA-256', buffer).then(function(arrayBuffer) { - var hash = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)); + var hash = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer) as unknown as number[]); var b64u = stringToBase64Url(hash); // url-safe base64 variant return b64u; }); diff --git a/lib/oidc/util/prepareTokenParams.ts b/lib/oidc/util/prepareTokenParams.ts index 80e9e3d18..66a5d2e60 100644 --- a/lib/oidc/util/prepareTokenParams.ts +++ b/lib/oidc/util/prepareTokenParams.ts @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -33,12 +34,12 @@ export function assertPKCESupport(sdk: OktaAuth) { } } -export async function validateCodeChallengeMethod(sdk: OktaAuth, codeChallengeMethod: string) { +export async function validateCodeChallengeMethod(sdk: OktaAuth, codeChallengeMethod?: string) { // set default code challenge method, if none provided codeChallengeMethod = codeChallengeMethod || sdk.options.codeChallengeMethod || DEFAULT_CODE_CHALLENGE_METHOD; // validate against .well-known/openid-configuration - const wellKnownResponse = await getWellKnown(sdk, null); + const wellKnownResponse = await getWellKnown(sdk); var methods = wellKnownResponse['code_challenge_methods_supported'] || []; if (methods.indexOf(codeChallengeMethod) === -1) { throw new AuthSdkError('Invalid code_challenge_method'); @@ -46,7 +47,10 @@ export async function validateCodeChallengeMethod(sdk: OktaAuth, codeChallengeMe return codeChallengeMethod; } -export async function preparePKCE(sdk: OktaAuth, tokenParams: TokenParams): Promise { +export async function preparePKCE( + sdk: OktaAuth, + tokenParams: TokenParams +): Promise { let { codeVerifier, codeChallenge, @@ -63,23 +67,26 @@ export async function preparePKCE(sdk: OktaAuth, tokenParams: TokenParams): Prom codeChallengeMethod = await validateCodeChallengeMethod(sdk, codeChallengeMethod); // Clone/copy the params. Set PKCE values - var clonedParams = clone(tokenParams) || {}; - Object.assign(clonedParams, tokenParams, { + tokenParams = { + ...tokenParams, responseType: 'code', // responseType is forced codeVerifier, codeChallenge, codeChallengeMethod - }); - return clonedParams; + }; + + return tokenParams; } // Prepares params for a call to /authorize or /token -export async function prepareTokenParams(sdk: OktaAuth, tokenParams?: TokenParams): Promise { +export async function prepareTokenParams( + sdk: OktaAuth, + tokenParams: TokenParams = {} +): Promise { // build params using defaults + options const defaults = getDefaultTokenParams(sdk); tokenParams = Object.assign({}, defaults, clone(tokenParams)); - if (tokenParams.pkce === false) { // Implicit flow or authorization_code without PKCE return tokenParams; diff --git a/lib/oidc/util/validateClaims.ts b/lib/oidc/util/validateClaims.ts index 4d9636f23..908ae6765 100644 --- a/lib/oidc/util/validateClaims.ts +++ b/lib/oidc/util/validateClaims.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -40,16 +41,16 @@ export function validateClaims(sdk: OktaAuth, claims: UserClaims, validationPara 'does not match [' + aud + ']'); } - if (claims.iat > claims.exp) { + if (claims.iat! > claims.exp!) { throw new AuthSdkError('The JWT expired before it was issued'); } if (!sdk.options.ignoreLifetime) { - if ((now - sdk.options.maxClockSkew) > claims.exp) { + if ((now - sdk.options.maxClockSkew!) > claims.exp!) { throw new AuthSdkError('The JWT expired and is no longer valid'); } - if (claims.iat > (now + sdk.options.maxClockSkew)) { + if (claims.iat! > (now + sdk.options.maxClockSkew!)) { throw new AuthSdkError('The JWT was issued in the future'); } } diff --git a/lib/oidc/verifyToken.ts b/lib/oidc/verifyToken.ts index a7074567f..b371d3cff 100644 --- a/lib/oidc/verifyToken.ts +++ b/lib/oidc/verifyToken.ts @@ -51,7 +51,8 @@ export async function verifyToken(sdk: OktaAuth, token: IDToken, validationParam return token; } - const key = await getKey(sdk, token.issuer, jwt.header.kid); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const key = await getKey(sdk, token.issuer, jwt.header.kid!); const valid = await sdkCrypto.verifyToken(token.idToken, key); if (!valid) { throw new AuthSdkError('The token signature is not valid'); diff --git a/lib/server/serverStorage.ts b/lib/server/serverStorage.ts index 3e8db0466..40f68f0a7 100644 --- a/lib/server/serverStorage.ts +++ b/lib/server/serverStorage.ts @@ -68,7 +68,7 @@ class ServerStorage implements StorageUtil { } getStorageByType(storageType: StorageType): SimpleStorage { - let storageProvider = null; + let storageProvider; switch (storageType) { case 'memory': storageProvider = this.getStorage(); diff --git a/lib/services/TokenService.ts b/lib/services/TokenService.ts index c441057d7..96f91f093 100644 --- a/lib/services/TokenService.ts +++ b/lib/services/TokenService.ts @@ -32,13 +32,15 @@ function shouldThrottleRenew(renewTimeQueue) { export class TokenService { private tokenManager: TokenManager; private options: TokenManagerOptions; - private storageListener: (event: StorageEvent) => void; - private onTokenExpiredHandler: (key: string) => void; + private storageListener?: (event: StorageEvent) => void; + private onTokenExpiredHandler?: (key: string) => void; private syncTimeout: unknown; constructor(tokenManager: TokenManager, options: TokenManagerOptions = {}) { this.tokenManager = tokenManager; this.options = options; + this.storageListener = undefined; + this.onTokenExpiredHandler = undefined; } start() { @@ -92,7 +94,8 @@ export class TokenService { this.tokenManager.clearExpireEventTimeoutAll(); this.tokenManager.off(EVENT_EXPIRED, this.onTokenExpiredHandler); if (this.options.syncStorage && isBrowser()) { - window.removeEventListener('storage', this.storageListener); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + window.removeEventListener('storage', this.storageListener!); clearTimeout(this.syncTimeout as any); } } diff --git a/lib/tx/AuthTransaction.ts b/lib/tx/AuthTransaction.ts index d32f2dbae..9572c1752 100644 --- a/lib/tx/AuthTransaction.ts +++ b/lib/tx/AuthTransaction.ts @@ -65,7 +65,7 @@ export class AuthTransaction implements TransactionState, AuthTransactionFunctio poll?: AuthTransactionFunction; prev?: AuthTransactionFunction; - data: TransactionState; + data?: TransactionState; stateToken?: string; sessionToken?: string; status: string | IdxStatus; @@ -76,7 +76,9 @@ export class AuthTransaction implements TransactionState, AuthTransactionFunctio scopes?: Array >; target?: Record; authentication?: Record; - constructor(sdk, res = null) { + constructor(sdk, res: TransactionState | null = null) { + this.data = undefined; + this.status = undefined as unknown as string; if (res) { this.data = res; @@ -234,7 +236,7 @@ function flattenEmbedded(sdk, res, obj, ref) { if (Array.isArray(obj)) { var objArr = []; for (var o = 0, ol = obj.length; o < ol; o++) { - objArr.push(flattenEmbedded(sdk, res, obj[o], ref)); + objArr.push(flattenEmbedded(sdk, res, obj[o], ref) as never); } return objArr; } diff --git a/lib/tx/TransactionState.ts b/lib/tx/TransactionState.ts index d8ae0299b..d8db3e656 100644 --- a/lib/tx/TransactionState.ts +++ b/lib/tx/TransactionState.ts @@ -11,10 +11,19 @@ */ -export class TransactionState { +export interface TransactionLink { + name?: string; + type: string; + href: string; + hints?: { + allow?: string[]; + }; +} +export interface TransactionState { interactionHandle?: string; // Authn V1 only + status: string; stateToken?: string; type?: string; expiresAt?: string; @@ -28,4 +37,5 @@ export class TransactionState { profile?: { updatePhone?: boolean; }; + _links?: Record; } \ No newline at end of file diff --git a/lib/types/OktaAuthOptions.ts b/lib/types/OktaAuthOptions.ts index cb4613c1b..eafbd5bd2 100644 --- a/lib/types/OktaAuthOptions.ts +++ b/lib/types/OktaAuthOptions.ts @@ -59,7 +59,7 @@ export interface OktaAuthOptions extends CustomUrls { headers?: object; maxClockSkew?: number; transformAuthState?: (oktaAuth: OktaAuth, authState: AuthState) => Promise; - restoreOriginalUri?: (oktaAuth: OktaAuth, originalUri: string) => Promise; + restoreOriginalUri?: (oktaAuth: OktaAuth, originalUri?: string) => Promise; devMode?: boolean; storageManager?: StorageManagerOptions; transactionManager?: TransactionManagerOptions; diff --git a/lib/types/Storage.ts b/lib/types/Storage.ts index 71630f992..ee2916b9f 100644 --- a/lib/types/Storage.ts +++ b/lib/types/Storage.ts @@ -69,7 +69,7 @@ export type StorageType = 'memory' | 'sessionStorage' | 'localStorage' | 'cookie export interface StorageUtil { storage: TxStorage; testStorageType(storageType: StorageType): boolean; - getStorageByType(storageType: StorageType, options: StorageOptions): SimpleStorage; + getStorageByType(storageType: StorageType, options?: StorageOptions): SimpleStorage; findStorageType(types: StorageType[]): StorageType; } @@ -97,7 +97,7 @@ export interface NodeStorageUtil extends StorageUtil { } export interface CookieStorage extends SimpleStorage { - setItem(key: string, value: any, expiresAt?: string): void; // can customize expiresAt + setItem(key: string, value: any, expiresAt?: string | null): void; // can customize expiresAt getItem(key?: string): any; // if no key is passed, all cookies are returned removeItem(key: string); // remove a cookie } diff --git a/lib/types/api.ts b/lib/types/api.ts index f7560f615..dcc483119 100644 --- a/lib/types/api.ts +++ b/lib/types/api.ts @@ -39,7 +39,7 @@ import { TransactionMetaOptions } from './Transaction'; export interface OktaAuth { options: OktaAuthOptions; getIssuerOrigin(): string; - getOriginalUri(): string; + getOriginalUri(): string | undefined; _oktaUserAgent: OktaUserAgent; storageManager: StorageManager; @@ -49,8 +49,8 @@ export interface OktaAuth { idx: IdxAPI; // Browser only - features?: FeaturesAPI; - token?: TokenAPI; + features: FeaturesAPI; + token: TokenAPI; } export interface APIError { @@ -173,8 +173,8 @@ export interface TokenAPI extends BaseTokenAPI { getWithoutPrompt(params?: TokenParams): Promise; getWithPopup(params?: TokenParams): Promise; revoke(token: RevocableToken): Promise; - renew(token: Token): Promise; - renewTokens(): Promise; + renew(token: Token): Promise; + renewTokens(options?: TokenParams): Promise; renewTokensWithRefresh(tokenParams: TokenParams, refreshTokenObject: RefreshToken): Promise; verify(token: IDToken, params?: object): Promise; isLoginRedirect(): boolean; @@ -283,7 +283,7 @@ export interface IdxAPI { canProceed(options?: { state?: string }): boolean; proceed: (options?: ProceedOptions) => Promise; cancel: (options?: CancelOptions) => Promise; - getFlow(): FlowIdentifier; + getFlow(): FlowIdentifier | undefined; setFlow(flow: FlowIdentifier): void; // call `start` instead of `startTransaction`. `startTransaction` will be removed in next major version (7.0) @@ -295,11 +295,11 @@ export interface IdxAPI { handleInteractionCodeRedirect: (url: string) => Promise; isEmailVerifyCallback: (search: string) => boolean; parseEmailVerifyCallback: (search: string) => EmailVerifyCallbackResponse; - handleEmailVerifyCallback: (search: string) => Promise; + handleEmailVerifyCallback: (search: string) => Promise; isEmailVerifyCallbackError: (error: Error) => boolean; // transaction meta - getSavedTransactionMeta: (options?: TransactionMetaOptions) => IdxTransactionMeta; + getSavedTransactionMeta: (options?: TransactionMetaOptions) => IdxTransactionMeta | undefined; createTransactionMeta: (options?: TransactionMetaOptions) => Promise; getTransactionMeta: (options?: TransactionMetaOptions) => Promise; saveTransactionMeta: (meta: unknown) => void; diff --git a/lib/util/url.ts b/lib/util/url.ts index 2984819d8..cbac27710 100644 --- a/lib/util/url.ts +++ b/lib/util/url.ts @@ -37,7 +37,7 @@ export function toQueryString(obj) { if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key] !== undefined && obj[key] !== null) { - str.push(key + '=' + encodeURIComponent(obj[key])); + str.push(key + '=' + encodeURIComponent(obj[key]) as never); } } } diff --git a/test/integration/.eslintrc.json b/test/integration/.eslintrc.json index 48f47176b..024f56c27 100644 --- a/test/integration/.eslintrc.json +++ b/test/integration/.eslintrc.json @@ -60,6 +60,7 @@ "@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/camelcase": 0, "@typescript-eslint/no-empty-function": 0, - "@typescript-eslint/no-var-requires": 0 + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-non-null-assertion": 0 } } diff --git a/test/integration/spec/renewTokens.ts b/test/integration/spec/renewTokens.ts index e6d272f99..dab8c9400 100644 --- a/test/integration/spec/renewTokens.ts +++ b/test/integration/spec/renewTokens.ts @@ -48,14 +48,14 @@ describe('renewTokens', () => { const client = createClient({ pkce: false, responseType: ['token'] }); const { tokens: originalTokens } = await signinAndGetTokens(client, { scopes }); expect(originalTokens.idToken).toBeUndefined(); - expect(originalTokens.accessToken.scopes.sort()).toEqual(scopes); + expect(originalTokens.accessToken!.scopes.sort()).toEqual(scopes); client.tokenManager.setTokens(originalTokens); mockGetWithoutPrompt(); const renewedTokens = await client.token.renewTokens(); expect(mocked.getWithoutPrompt.getWithoutPrompt).toHaveBeenCalled(); - expect(originalTokens.accessToken.accessToken).not.toEqual(renewedTokens.accessToken.accessToken); + expect(originalTokens.accessToken!.accessToken).not.toEqual(renewedTokens.accessToken!.accessToken); expect(renewedTokens.idToken).toBeUndefined(); - expect(renewedTokens.accessToken.scopes.sort()).toEqual(scopes); + expect(renewedTokens.accessToken!.scopes.sort()).toEqual(scopes); }); it('renews with only an id token', async () => { @@ -63,16 +63,16 @@ describe('renewTokens', () => { const client = createClient({ pkce: false, responseType: ['id_token'] }); const { tokens: originalTokens } = await signinAndGetTokens(client, { scopes }); expect(originalTokens.accessToken).toBeUndefined(); - expect(originalTokens.idToken.scopes.sort()).toEqual(scopes); + expect(originalTokens.idToken!.scopes.sort()).toEqual(scopes); assertHasClaims(originalTokens, ['name']); assertDoesNotHaveClaims(originalTokens, ['email']); client.tokenManager.setTokens(originalTokens); mockGetWithoutPrompt(); const renewedTokens = await client.token.renewTokens(); expect(mocked.getWithoutPrompt.getWithoutPrompt).toHaveBeenCalled(); - expect(originalTokens.idToken.idToken).not.toEqual(renewedTokens.idToken.idToken); + expect(originalTokens.idToken!.idToken).not.toEqual(renewedTokens.idToken!.idToken); expect(renewedTokens.accessToken).toBeUndefined(); - expect(renewedTokens.idToken.scopes.sort()).toEqual(scopes); + expect(renewedTokens.idToken!.scopes.sort()).toEqual(scopes); assertHasClaims(renewedTokens, ['name']); assertDoesNotHaveClaims(renewedTokens, ['email']); }); @@ -88,8 +88,8 @@ describe('renewTokens', () => { mockGetWithoutPrompt(); const renewedTokens = await client.token.renewTokens(); expect(mocked.getWithoutPrompt.getWithoutPrompt).toHaveBeenCalled(); - expect(originalTokens.accessToken.accessToken).not.toEqual(renewedTokens.accessToken.accessToken); - expect(originalTokens.idToken.idToken).not.toEqual(renewedTokens.idToken.idToken); + expect(originalTokens.accessToken!.accessToken).not.toEqual(renewedTokens.accessToken!.accessToken); + expect(originalTokens.idToken!.idToken).not.toEqual(renewedTokens.idToken!.idToken); assertScopesMatch(renewedTokens, scopes); assertHasClaims(renewedTokens, ['email']); assertDoesNotHaveClaims(renewedTokens, ['name']); @@ -106,8 +106,8 @@ describe('renewTokens', () => { mockGetWithoutPrompt(); const renewedTokens = await client.token.renewTokens(); expect(mocked.getWithoutPrompt.getWithoutPrompt).toHaveBeenCalled(); - expect(originalTokens.accessToken.accessToken).not.toEqual(renewedTokens.accessToken.accessToken); - expect(originalTokens.idToken.idToken).not.toEqual(renewedTokens.idToken.idToken); + expect(originalTokens.accessToken!.accessToken).not.toEqual(renewedTokens.accessToken!.accessToken); + expect(originalTokens.idToken!.idToken).not.toEqual(renewedTokens.idToken!.idToken); assertScopesMatch(renewedTokens, scopes); assertHasClaims(renewedTokens, ['name']); assertDoesNotHaveClaims(renewedTokens, ['email']); @@ -124,8 +124,8 @@ describe('renewTokens', () => { mockGetWithoutPrompt(); const renewedTokens = await client.token.renewTokens(); expect(mocked.getWithoutPrompt.getWithoutPrompt).toHaveBeenCalled(); - expect(originalTokens.accessToken.accessToken).not.toEqual(renewedTokens.accessToken.accessToken); - expect(originalTokens.idToken.idToken).not.toEqual(renewedTokens.idToken.idToken); + expect(originalTokens.accessToken!.accessToken).not.toEqual(renewedTokens.accessToken!.accessToken); + expect(originalTokens.idToken!.idToken).not.toEqual(renewedTokens.idToken!.idToken); assertScopesMatch(renewedTokens, scopes); assertHasClaims(renewedTokens, ['name']); assertDoesNotHaveClaims(renewedTokens, ['email']); @@ -144,8 +144,8 @@ describe('renewTokens', () => { client.tokenManager.setTokens(originalTokens); mockGetWithoutPrompt(true); const renewedTokens = await client.token.renewTokens(); - expect(originalTokens.accessToken.accessToken).not.toEqual(renewedTokens.accessToken.accessToken); - expect(originalTokens.idToken.idToken).not.toEqual(renewedTokens.idToken.idToken); + expect(originalTokens.accessToken!.accessToken).not.toEqual(renewedTokens.accessToken!.accessToken); + expect(originalTokens.idToken!.idToken).not.toEqual(renewedTokens.idToken!.idToken); assertScopesMatch(renewedTokens, scopes); assertHasClaims(renewedTokens, ['name']); assertDoesNotHaveClaims(renewedTokens, ['email']); @@ -161,8 +161,8 @@ describe('renewTokens', () => { client.tokenManager.setTokens(originalTokens); mockGetWithoutPrompt(true); const renewedTokens = await client.token.renewTokens(); - expect(originalTokens.accessToken.accessToken).not.toEqual(renewedTokens.accessToken.accessToken); - expect(originalTokens.idToken.idToken).not.toEqual(renewedTokens.idToken.idToken); + expect(originalTokens.accessToken!.accessToken).not.toEqual(renewedTokens.accessToken!.accessToken); + expect(originalTokens.idToken!.idToken).not.toEqual(renewedTokens.idToken!.idToken); assertScopesMatch(renewedTokens, scopes); assertHasClaims(renewedTokens, ['name']); assertDoesNotHaveClaims(renewedTokens, ['email']); diff --git a/test/integration/util/createClient.ts b/test/integration/util/createClient.ts index 4e342483d..d75fb16bf 100644 --- a/test/integration/util/createClient.ts +++ b/test/integration/util/createClient.ts @@ -1,7 +1,7 @@ import { OktaAuth } from '../../../lib'; // Cleanup clients after test completes -const activeClients = []; +const activeClients: OktaAuth[] = []; afterEach(() => { activeClients.forEach(client => { client.tokenManager.clearExpireEventTimeoutAll(); diff --git a/test/integration/util/getTokens.ts b/test/integration/util/getTokens.ts index d398ec259..da21b7136 100644 --- a/test/integration/util/getTokens.ts +++ b/test/integration/util/getTokens.ts @@ -1,6 +1,6 @@ import fetch from 'cross-fetch'; import waitFor from '@okta/test.support/waitFor'; -import { AuthTransaction, handleOAuthResponse } from '../../../lib'; +import { AuthTransaction, CustomUrls, handleOAuthResponse } from '../../../lib'; import { getWithRedirect } from '../../../lib/oidc'; import { parseOAuthResponseFromUrl } from '../../../lib/oidc/parseFromUrl'; @@ -31,16 +31,16 @@ async function getTokens(client, tokenParams) { getWithRedirect(client, tokenParams); await waitFor(() => localContext.authorizeUrl); const { authorizeUrl } = localContext; - const res = await fetch(authorizeUrl, { + const res = await fetch(authorizeUrl as unknown as string, { redirect: 'manual' }); const redirectUrl = res.headers.get('location'); const oauthResponse = parseOAuthResponseFromUrl(client, { - url: redirectUrl, + url: redirectUrl!, responseMode: 'fragment' }); const transactionMeta = client.transactionManager.load(); - const tokenResponse = await handleOAuthResponse(client, transactionMeta, oauthResponse, null); + const tokenResponse = await handleOAuthResponse(client, transactionMeta, oauthResponse, undefined as unknown as CustomUrls); unmockGetWithRedirect(client); return tokenResponse; } diff --git a/test/spec/.eslintrc.json b/test/spec/.eslintrc.json index 48f47176b..daa8b622e 100644 --- a/test/spec/.eslintrc.json +++ b/test/spec/.eslintrc.json @@ -60,6 +60,8 @@ "@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/camelcase": 0, "@typescript-eslint/no-empty-function": 0, - "@typescript-eslint/no-var-requires": 0 + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-non-null-assertion": 0, + "@typescript-eslint/no-explicit-any": 0 } } diff --git a/test/spec/OktaAuth/assertValidConfig.ts b/test/spec/OktaAuth/assertValidConfig.ts index 8b2da4c69..377849947 100644 --- a/test/spec/OktaAuth/assertValidConfig.ts +++ b/test/spec/OktaAuth/assertValidConfig.ts @@ -12,14 +12,14 @@ -import { OktaAuth } from '@okta/okta-auth-js'; +import { OktaAuth, OktaAuthOptions } from '@okta/okta-auth-js'; describe('assertValidConfig', () => { it('throw an error if no arguments are passed to the constructor', function () { var err; try { - new OktaAuth(undefined); // eslint-disable-line no-new + new OktaAuth(undefined as unknown as OktaAuthOptions); // eslint-disable-line no-new } catch (e) { err = e; } diff --git a/test/spec/OktaAuth/browser.ts b/test/spec/OktaAuth/browser.ts index a6893e395..f6d48b468 100644 --- a/test/spec/OktaAuth/browser.ts +++ b/test/spec/OktaAuth/browser.ts @@ -43,7 +43,7 @@ describe('OktaAuth (browser)', function() { beforeEach(function() { originalLocation = global.window.location; - delete global.window.location; + delete (global.window as any).location; global.window.location = { protocol: 'https:', hostname: 'somesite.local', @@ -104,7 +104,7 @@ describe('OktaAuth (browser)', function() { it('console warning if secure is forced to false running on HTTP', () => { window.location.protocol = 'http:'; window.location.hostname = 'my-site'; - jest.spyOn(console, 'warn').mockReturnValue(null); + jest.spyOn(console, 'warn').mockReturnValue(); auth = new OktaAuth({ issuer: 'http://my-okta-domain' , cookies: { secure: true }}); // eslint-disable-next-line no-console diff --git a/test/spec/SavedObject.ts b/test/spec/SavedObject.ts index e8d6d05d5..9c5ef749d 100644 --- a/test/spec/SavedObject.ts +++ b/test/spec/SavedObject.ts @@ -18,7 +18,7 @@ describe('SavedObject', () => { it('throws if a storage is not provided', () => { const fn = function() { - return new SavedObject(null, ''); + return new SavedObject(undefined as unknown as SimpleStorage, ''); }; expect(fn).toThrowError('"storage" is required'); }); diff --git a/test/spec/StorageManager.ts b/test/spec/StorageManager.ts index 56bf59507..8fa281d32 100644 --- a/test/spec/StorageManager.ts +++ b/test/spec/StorageManager.ts @@ -274,7 +274,7 @@ describe('StorageManager', () => { it('always use memory storage on browser side', () => { const storageManager = setup(); const options = {}; - const res: IdxResponseStorage = storageManager.getIdxResponseStorage(options); + const res: IdxResponseStorage = storageManager.getIdxResponseStorage(options)!; expect(storageManager.storageUtil.getStorageByType).toHaveBeenCalledWith('memory', options); expect((res as SavedObject).storageName).toBe('okta-idx-response-storage'); }); @@ -306,7 +306,7 @@ describe('StorageManager', () => { } }; const storageManager = setup(options); - const res: IdxResponseStorage = storageManager.getIdxResponseStorage(); + const res: IdxResponseStorage = storageManager.getIdxResponseStorage()!; expect(typeof (res as SavedObject).storageProvider.getItem).toBe('function'); expect(typeof (res as SavedObject).storageProvider.setItem).toBe('function'); expect(typeof (res as SavedObject).storageProvider.removeItem).toBe('function'); @@ -317,7 +317,7 @@ describe('StorageManager', () => { const options: StorageManagerOptions = {}; const storageManager = setup(options); storageManager.getTransactionStorage = jest.fn().mockReturnValue(null); - const res: IdxResponseStorage = storageManager.getIdxResponseStorage(); + const res: IdxResponseStorage = storageManager.getIdxResponseStorage()!; expect(res).toBeNull(); }); }); diff --git a/test/spec/TokenManager/core.ts b/test/spec/TokenManager/core.ts index ff267901b..80cbaf1bf 100644 --- a/test/spec/TokenManager/core.ts +++ b/test/spec/TokenManager/core.ts @@ -138,7 +138,7 @@ describe('TokenManager', function() { expect(client.tokenManager.service).toBe(null); }); it('does not error if there is no service instance', () => { - expect(client.tokenManager.service).toBe(undefined); + expect(client.tokenManager.service).toBe(null); client.tokenManager.stop(); }); }); @@ -169,7 +169,7 @@ describe('TokenManager', function() { it('Event callbacks can have an optional context', function() { setupSync(); var context = jest.fn(); - var handler = jest.fn().mockImplementation(function() { + var handler = jest.fn().mockImplementation(function(this: any) { expect(this).toBe(context); }); client.tokenManager.on('fake', handler, context); @@ -342,7 +342,7 @@ describe('TokenManager', function() { expect(results.length).toBe(2); results.forEach(function(result) { expect(result.status).toBe('rejected'); - util.expectErrorToEqual(result.reason, { + util.expectErrorToEqual((result as any).reason, { name: 'Error', message: 'expected', tokenKey: 'test-idToken' diff --git a/test/spec/TokenManager/crossTabs.ts b/test/spec/TokenManager/crossTabs.ts index 94b8a7da9..08d57b29c 100644 --- a/test/spec/TokenManager/crossTabs.ts +++ b/test/spec/TokenManager/crossTabs.ts @@ -46,7 +46,7 @@ describe('cross tabs communication', () => { } }); - function createInstance(options = null) { + function createInstance(options?) { instance = new TokenManager(sdkMock, options); instance.start(); return instance; diff --git a/test/spec/TokenManager/expireEvents.ts b/test/spec/TokenManager/expireEvents.ts index f8505f0df..787aa79c6 100644 --- a/test/spec/TokenManager/expireEvents.ts +++ b/test/spec/TokenManager/expireEvents.ts @@ -30,7 +30,7 @@ describe('expire events', () => { }; }); - function createInstance(options = null) { + function createInstance(options?) { testContext.instance = new TokenManager(testContext.sdkMock, options); jest.spyOn(testContext.instance.clock, 'now').mockReturnValue(0); } diff --git a/test/spec/TransactionManager.ts b/test/spec/TransactionManager.ts index f66525b7d..949e154ff 100644 --- a/test/spec/TransactionManager.ts +++ b/test/spec/TransactionManager.ts @@ -77,7 +77,7 @@ describe('TransactionManager', () => { options, meta }; - jest.spyOn(global.console, 'warn').mockReturnValue(null); // ignore storage warnings + jest.spyOn(global.console, 'warn').mockReturnValue(undefined); // ignore storage warnings }); function createInstance(additionalOptions = {}) { diff --git a/test/spec/features/core.ts b/test/spec/features/core.ts index d9b9f3667..fee4656cd 100644 --- a/test/spec/features/core.ts +++ b/test/spec/features/core.ts @@ -17,7 +17,7 @@ const modulesToMock = { const mocked = { crypto: { - webcrypto: null + webcrypto: undefined } }; @@ -68,7 +68,7 @@ describe('features', function() { beforeEach(() => { orig.Uint8Array = global.Uint8Array; orig.TextEncoder = global.TextEncoder; - mocked.crypto.webcrypto = { subtle: {} }; + (mocked.crypto as any).webcrypto = { subtle: {} }; }); afterEach(() => { global.Uint8Array = orig.Uint8Array as unknown as Uint8ArrayConstructor; @@ -88,12 +88,12 @@ describe('features', function() { }); it('fails if no webcrypto.subtle', function() { - mocked.crypto.webcrypto = {}; + (mocked.crypto as any).webcrypto = {}; expect(OktaAuth.features.isTokenVerifySupported()).toBe(false); }); it('fails if no Uint8Array', function() { - delete global.Uint8Array; + delete (global as any).Uint8Array; expect(OktaAuth.features.isTokenVerifySupported()).toBe(false); }); }); @@ -103,7 +103,7 @@ describe('features', function() { expect(OktaAuth.features.hasTextEncoder()).toBe(true); }); it('returns false if TextEncoder is undefined', function() { - delete global.TextEncoder; + delete (global as any).TextEncoder; expect(OktaAuth.features.hasTextEncoder()).toBe(false); }); }); @@ -119,17 +119,17 @@ describe('features', function() { }); it('fails if no webcrypto.subtle', function() { - mocked.crypto.webcrypto = {}; + (mocked.crypto as any).webcrypto = {}; expect(OktaAuth.features.isPKCESupported()).toBe(false); }); it('fails if no Uint8Array', function() { - delete global.Uint8Array; + delete (global as any).Uint8Array; expect(OktaAuth.features.isPKCESupported()).toBe(false); }); it('fails if no TextEncoder', function() { - delete global.TextEncoder; + delete (global as any).TextEncoder; expect(OktaAuth.features.isPKCESupported()).toBe(false); }); @@ -179,7 +179,7 @@ describe('features', function() { var err; spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(false); spyOn(OktaAuth.features, 'isHTTPS').and.returnValue(true); - delete global.TextEncoder; + delete (global as any).TextEncoder; var auth = new OktaAuth({ issuer: 'https://dev-12345.oktapreview.com', @@ -202,7 +202,7 @@ describe('features', function() { var err; spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(false); spyOn(OktaAuth.features, 'isHTTPS').and.returnValue(false); - delete global.TextEncoder; + delete (global as any).TextEncoder; var auth = new OktaAuth({ issuer: 'https://dev-12345.oktapreview.com', diff --git a/test/spec/http/request.ts b/test/spec/http/request.ts index bc309f333..224ae5312 100644 --- a/test/spec/http/request.ts +++ b/test/spec/http/request.ts @@ -134,7 +134,7 @@ describe('HTTP Requestor', () => { return httpRequest(sdk, { url, headers: { - 'fake': undefined + 'fake': undefined as unknown as string } }) .then(res => { diff --git a/test/spec/idx/authenticate.ts b/test/spec/idx/authenticate.ts index 193c789a9..b2446688f 100644 --- a/test/spec/idx/authenticate.ts +++ b/test/spec/idx/authenticate.ts @@ -754,7 +754,7 @@ describe('idx/authenticate', () => { const res = await authenticate(authClient, { resend: true }); - expect(res.nextStep.canResend).toBe(true); + expect(res.nextStep!.canResend).toBe(true); }); it('returns a PENDING error if an invalid code is provided', async () => { diff --git a/test/spec/idx/poll.ts b/test/spec/idx/poll.ts index 98c2a3aec..44b0bdfe7 100644 --- a/test/spec/idx/poll.ts +++ b/test/spec/idx/poll.ts @@ -174,7 +174,7 @@ describe('idx/poll', () => { jest.spyOn(enrollPollResponse, 'proceed'); const res = await proceed(authClient, {}); - const refresh = res.nextStep.poll.refresh; + const refresh = res.nextStep!.poll!.refresh; const transaction = await poll(authClient, { refresh }); expect(enrollPollResponse.proceed).toHaveBeenCalledTimes(3); expect(transaction.status).toEqual(IdxStatus.TERMINAL); diff --git a/test/spec/idx/recoverPassword.ts b/test/spec/idx/recoverPassword.ts index dd5edaee2..de625379e 100644 --- a/test/spec/idx/recoverPassword.ts +++ b/test/spec/idx/recoverPassword.ts @@ -322,7 +322,7 @@ describe('idx/recoverPassword', () => { authenticator: { id: 'id-email' } }); expect(res.status).toBe(IdxStatus.PENDING); - expect(res.nextStep.name).toBe('challenge-authenticator'); + expect(res.nextStep!.name).toBe('challenge-authenticator'); }); it('returns a terminal error if reset password is not allowed by server policy', async () => { diff --git a/test/spec/idx/register.ts b/test/spec/idx/register.ts index a34c3467c..a3062ada3 100644 --- a/test/spec/idx/register.ts +++ b/test/spec/idx/register.ts @@ -289,7 +289,7 @@ describe('idx/register', () => { } catch (error) { didThrow = true; expect(error).toBeInstanceOf(AuthSdkError); - expect(error.errorSummary).toBe('Registration is not supported based on your current org configuration.'); + expect((error as any).errorSummary).toBe('Registration is not supported based on your current org configuration.'); } expect(didThrow).toBe(true); }); @@ -302,7 +302,7 @@ describe('idx/register', () => { } catch (error) { didThrow = true; expect(error).toBeInstanceOf(AuthSdkError); - expect(error.errorSummary).toBe('Registration is not supported based on your current org configuration.'); + expect((error as any).errorSummary).toBe('Registration is not supported based on your current org configuration.'); } expect(didThrow).toBe(true); expect(mocked.startTransaction.startTransaction).toHaveBeenCalledWith(authClient, { flow: 'register', autoRemediate: false }); @@ -324,7 +324,7 @@ describe('idx/register', () => { } catch (error) { didThrow = true; expect(error).toBeInstanceOf(AuthSdkError); - expect(error.errorSummary).toBe('activationToken is not supported based on your current org configuration.'); + expect((error as any).errorSummary).toBe('activationToken is not supported based on your current org configuration.'); } expect(didThrow).toBe(true); }); @@ -425,7 +425,7 @@ describe('idx/register', () => { } }); - const inputs = res.nextStep.inputs.map(({name}) => name); + const inputs = res.nextStep!.inputs!.map(({name}) => name); const inputValues = inputs.reduce((formData, inputName, inputIndex) => ({ ...formData, [inputName]: `value${inputIndex}` @@ -1857,7 +1857,7 @@ describe('idx/register', () => { }); expect(selectAuthenticatorResponse.proceed).toHaveBeenCalled(); expect(enrollPollResponse.proceed).not.toHaveBeenCalled(); - expect(Object.keys(response.nextStep)).toContain('poll'); + expect(Object.keys(response.nextStep as object)).toContain('poll'); }); it('offers QR code as a default channel for adding OV account', async () => { @@ -1874,12 +1874,12 @@ describe('idx/register', () => { jest.spyOn(mocked.introspect, 'introspect') .mockResolvedValueOnce(selectAuthenticatorResponse); - let { nextStep: - { authenticator: { contextualData } } - } = await register(authClient, { + const { nextStep } = await register(authClient, { authenticator: AuthenticatorKey.OKTA_VERIFY }); - expect(Object.keys(contextualData)).toContain('qrcode'); + const { authenticator } = nextStep!; + const { contextualData } = authenticator!; + expect(Object.keys(contextualData as object)).toContain('qrcode'); }); }); diff --git a/test/spec/idx/remediators/EnrollPoll.ts b/test/spec/idx/remediators/EnrollPoll.ts new file mode 100644 index 000000000..80c2a69dc --- /dev/null +++ b/test/spec/idx/remediators/EnrollPoll.ts @@ -0,0 +1,62 @@ +/*! + * Copyright (c) 2021-present, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") + * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and limitations under the License. + */ + +import { + EnrollPollRemediationFactory, +} from '@okta/test.support/idx'; +import { RemediationValues, Remediator } from '../../../../lib/idx/remediators'; +import { EnrollPoll } from '../../../../lib/idx/remediators/EnrollPoll'; + +describe('EnrollPoll remediator', () => { + let testContext; + beforeEach(() => { + const enrollPollRemediation = EnrollPollRemediationFactory.build(); + const values: RemediationValues = { + authenticators: [], + authenticatorsData: [] + }; + testContext = { + enrollPollRemediation, + values + }; + }); + + it('sets static property `remediationName`', () => { + expect(EnrollPoll.remediationName).toBe('enroll-poll'); + }); + + it('extends Remediator', () => { + const { enrollPollRemediation, values } = testContext; + const remediator: Remediator = new EnrollPoll(enrollPollRemediation, values); + Object.keys(Remediator.prototype).forEach(val => { + expect(remediator[val]).toBeTruthy(); + }); + }); + + describe('getValuesAfterProceed', () => { + it('removes `startPolling` from the values', () => { + const { enrollPollRemediation, values } = testContext; + values.foo = 'bar'; + values.startPolling = 'omgyes'; + const remediator: EnrollPoll = new EnrollPoll(enrollPollRemediation, values); + const newValues: RemediationValues = remediator.getValuesAfterProceed(); + expect(newValues).toEqual({ + authenticators: [], + authenticatorsData: [], + foo: 'bar' + }); + }); + }); + + + +}); \ No newline at end of file diff --git a/test/spec/idx/startTransaction.ts b/test/spec/idx/startTransaction.ts index 8a0691f25..126345f6c 100644 --- a/test/spec/idx/startTransaction.ts +++ b/test/spec/idx/startTransaction.ts @@ -161,7 +161,7 @@ describe('idx/startTransaction', () => { jest.spyOn(mocked.introspect, 'introspect').mockResolvedValue(idxResponse); const { authClient } = testContext; const res = await startTransaction(authClient); - expect(res.enabledFeatures.includes(IdxFeature.PASSWORD_RECOVERY)).toBeTruthy(); + expect(res.enabledFeatures!.includes(IdxFeature.PASSWORD_RECOVERY)).toBeTruthy(); }); it('has registration feature enabled with RegistrationEnabledResponseFactory', async () => { @@ -173,7 +173,7 @@ describe('idx/startTransaction', () => { jest.spyOn(mocked.introspect, 'introspect').mockResolvedValue(idxResponse); const { authClient } = testContext; const res = await startTransaction(authClient); - expect(res.enabledFeatures.includes(IdxFeature.REGISTRATION)).toBeTruthy(); + expect(res.enabledFeatures!.includes(IdxFeature.REGISTRATION)).toBeTruthy(); }); it('has social idp feature enabled with SocialIDPEnabledResponseFactory', async () => { @@ -185,7 +185,7 @@ describe('idx/startTransaction', () => { jest.spyOn(mocked.introspect, 'introspect').mockResolvedValue(idxResponse); const { authClient } = testContext; const res = await startTransaction(authClient); - expect(res.enabledFeatures.includes(IdxFeature.SOCIAL_IDP)).toBeTruthy(); + expect(res.enabledFeatures!.includes(IdxFeature.SOCIAL_IDP)).toBeTruthy(); }); it('maps remediations to availableSteps', async () => { diff --git a/test/spec/oidc/endpoints/token.ts b/test/spec/oidc/endpoints/token.ts index 2fd2ede98..ca99b175e 100644 --- a/test/spec/oidc/endpoints/token.ts +++ b/test/spec/oidc/endpoints/token.ts @@ -27,6 +27,7 @@ import { OktaAuth, AuthSdkError } from '@okta/okta-auth-js'; import util from '@okta/test.support/util'; import { postToTokenEndpoint } from '../../../../lib/oidc/endpoints/token'; import factory from '@okta/test.support/factory'; +import { CustomUrls } from '../../../../lib/types'; describe('token endpoint', function() { var ISSUER = 'http://example.okta.com'; @@ -112,40 +113,40 @@ describe('token endpoint', function() { it('Throws if no clientId', function() { oauthOptions.clientId = undefined; try { - postToTokenEndpoint(authClient, oauthOptions, null); + postToTokenEndpoint(authClient, oauthOptions, undefined as unknown as CustomUrls); } 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'); + expect((e as Error).message).toBe('A clientId must be specified in the OktaAuth constructor to get a token'); } }); it('Throws if no redirectUri', function() { oauthOptions.redirectUri = undefined; try { - postToTokenEndpoint(authClient, oauthOptions, null); + postToTokenEndpoint(authClient, oauthOptions, undefined as unknown as CustomUrls); } catch(e) { expect(e instanceof AuthSdkError).toBe(true); - expect(e.message).toBe('The redirectUri passed to /authorize must also be passed to /token'); + expect((e as Error).message).toBe('The redirectUri passed to /authorize must also be passed to /token'); } }); it('Throws if no authorizationCode', function() { oauthOptions.authorizationCode = undefined; try { - postToTokenEndpoint(authClient, oauthOptions, null); + postToTokenEndpoint(authClient, oauthOptions, undefined as unknown as CustomUrls); } catch(e) { expect(e instanceof AuthSdkError).toBe(true); - expect(e.message).toBe('An authorization code (returned from /authorize) must be passed to /token'); + expect((e as Error).message).toBe('An authorization code (returned from /authorize) must be passed to /token'); } }); it('Throws if no codeVerifier', function() { oauthOptions.codeVerifier = undefined; try { - postToTokenEndpoint(authClient, oauthOptions, null); + postToTokenEndpoint(authClient, oauthOptions, undefined as unknown as CustomUrls); } 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'); + expect((e as Error).message).toBe('The "codeVerifier" (generated and saved by your app) must be passed to /token'); } }); diff --git a/test/spec/oidc/endpoints/well-known.ts b/test/spec/oidc/endpoints/well-known.ts index 33be62fa6..53148ed27 100644 --- a/test/spec/oidc/endpoints/well-known.ts +++ b/test/spec/oidc/endpoints/well-known.ts @@ -358,7 +358,7 @@ describe('getKey', function() { }, execute: function(test) { oauthUtilHelpers.loadWellKnownAndKeysCache(test.oa); - return getKey(test.oa, null, 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); + return getKey(test.oa, '', 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); }, expectations: function(test, key) { expect(key).toEqual(tokens.standardKey); @@ -386,7 +386,7 @@ describe('getKey', function() { response: wellKnownResponse } }); - return getKey(test.oa, null, 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); + return getKey(test.oa, '', 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); }, expectations: function(test, key) { expect(key).toEqual(tokens.standardKey); @@ -442,7 +442,7 @@ describe('getKey', function() { } }); - return getKey(test.oa, null, 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); + return getKey(test.oa, '', 'U5R8cHbGw445Qbq8zVO1PcCpXL8yG6IcovVa3laCoxM'); }, expectations: function(test, key) { expect(key).toEqual(tokens.standardKey); @@ -488,7 +488,7 @@ describe('getKey', function() { } }); - return getKey(test.oa, null, 'invalidKid'); + return getKey(test.oa, '', 'invalidKid'); }, expectations: function(test, err) { util.assertAuthSdkError(err, 'The key id, invalidKid, was not found in the server\'s keys'); diff --git a/test/spec/oidc/getWithPopup.ts b/test/spec/oidc/getWithPopup.ts index 9ea224060..0aa7de99a 100644 --- a/test/spec/oidc/getWithPopup.ts +++ b/test/spec/oidc/getWithPopup.ts @@ -63,19 +63,22 @@ describe('token.getWithPopup', function() { }); it('promise will reject if fails due to timeout', function() { - var timeoutMs = 120000; + var timeoutMs = 120001; var mockWindow = { closed: false, close: jest.fn(), location: { - assign: jest.fn() + assign: jest.fn().mockImplementation(() => { + jest.runAllTicks(); // resolve pending promises + jest.advanceTimersByTime(timeoutMs); // should trigger timeout + }) } }; jest.spyOn(window, 'open').mockImplementation(function () { return mockWindow as unknown as Window; // valid window is returned }); jest.useFakeTimers(); - var promise = oauthUtil.setup({ + return oauthUtil.setup({ closePopup: true, // prevent any message being passed willFail: true, oktaAuthArgs: { @@ -103,20 +106,13 @@ describe('token.getWithPopup', function() { errorCauses: [] }); }); - return Promise.resolve() - .then(function() { - jest.runAllTicks(); // resolve pending promises - jest.advanceTimersByTime(timeoutMs); // should trigger timeout - return promise; - }); }); it('promise will reject if popup is blocked', function() { jest.spyOn(window, 'open').mockImplementation(function () { return null; // null window is returned }); - jest.useFakeTimers(); - var promise = oauthUtil.setup({ + return oauthUtil.setup({ closePopup: true, willFail: true, oktaAuthArgs: { @@ -133,6 +129,7 @@ describe('token.getWithPopup', function() { expect(true).toEqual(false); }) .catch(function(err) { + // should fail after 100ms util.expectErrorToEqual(err, { name: 'AuthSdkError', message: 'Unable to parse OAuth flow response', @@ -143,11 +140,6 @@ describe('token.getWithPopup', function() { errorCauses: [] }); }); - return Promise.resolve() - .then(function () { - jest.runAllTimers(); - return promise; - }); }); it('returns tokens using idp', function() { @@ -264,7 +256,7 @@ describe('token.getWithPopup', function() { // mock popup creation var popups = []; function getOpenPopups() { - return popups.filter(function(popup) { + return popups.filter(function(popup: Window) { return !popup.closed; }); } @@ -272,7 +264,7 @@ describe('token.getWithPopup', function() { return oauthUtil.setupSimultaneousPostMessage() .then(function(context) { - function FakePopup() { + function FakePopup(this: any) { this.closed = false; this.close = () => { this.closed = true; @@ -283,7 +275,7 @@ describe('token.getWithPopup', function() { } jest.spyOn(window, 'open').mockImplementation(function() { var popup = new FakePopup(); - popups.push(popup); + popups.push(popup as never); return popup; }); // getWithPopup, but don't resolve @@ -313,7 +305,7 @@ describe('token.getWithPopup', function() { context.emitter.emit('trigger', oauthUtil.mockedState); return firstPopup.then(val => { expect(val.tokens.idToken).toEqual(tokens.standardIdTokenParsed); - expect(popups[0].closed).toBe(true); // first popup should be closed + expect((popups[0] as Window).closed).toBe(true); // first popup should be closed expect(popups.length).toBe(1); // 2nd popup is not open yet return waitFor(() => { return getOpenPopups().length > 0 ? context : false; @@ -329,7 +321,7 @@ describe('token.getWithPopup', function() { return secondPopup .then(function(val) { expect(val.tokens.idToken).toEqual(tokens.standardIdToken2Parsed); - expect(popups[1].closed).toBe(true); // 2nd popup should be closed + expect((popups[1] as Window).closed).toBe(true); // 2nd popup should be closed expect(popups.length).toBe(2); // no other popups were created }); }); diff --git a/test/spec/oidc/getWithRedirect.ts b/test/spec/oidc/getWithRedirect.ts index 8411c1a51..02bda0c50 100644 --- a/test/spec/oidc/getWithRedirect.ts +++ b/test/spec/oidc/getWithRedirect.ts @@ -1,9 +1,9 @@ import { getWithRedirect } from '../../../lib/oidc/getWithRedirect'; -import { TokenParams } from '../../../lib/types'; jest.mock('../../../lib/oidc/util', () => { return { prepareTokenParams: () => {}, + createOAuthMeta: () => {}, getOAuthUrls: () => {} }; }); @@ -43,19 +43,26 @@ describe('getWithRedirect', () => { const urls = { authorizeUrl: 'http://fake-authorize' }; + const meta = { + urls + }; testContext = { sdk, tokenParams, authorizeParams, - urls + urls, + meta }; jest.spyOn(mocked.util, 'prepareTokenParams').mockResolvedValue(testContext.tokenParams); jest.spyOn(mocked.util, 'getOAuthUrls').mockReturnValue(testContext.urls); jest.spyOn(mocked.authorize, 'buildAuthorizeParams').mockReturnValue(testContext.authorizeParams); + jest.spyOn(mocked.util, 'createOAuthMeta').mockReturnValue(testContext.meta); }); it('throws an error if more than 2 parameters are passed', async () => { const { sdk } = testContext; + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + //@ts-ignore const promise = getWithRedirect.apply(null, [sdk, {}, {}]); await expect(promise).rejects.toThrow('As of version 3.0, "getWithRedirect" takes only a single set of options'); }); @@ -66,57 +73,14 @@ describe('getWithRedirect', () => { jest.spyOn(sdk.transactionManager, 'save'); }); - it('saves issuer from sdk', async () => { - const { sdk, urls } = testContext; + it('saves using the oauth option', async () => { + const { sdk, meta } = testContext; const issuer = 'http://fake'; sdk.options.issuer = issuer; await getWithRedirect(sdk, {}); - expect(sdk.transactionManager.save).toHaveBeenCalledWith({ - issuer, - urls - }, { oauth: true }); - }); - - it('saves urls from `getOAuthUrls`', async () => { - const { sdk, urls } = testContext; - const options = { foo: 'bar' } as unknown as TokenParams; - await getWithRedirect(sdk, options); - expect(mocked.util.getOAuthUrls).toHaveBeenCalledWith(sdk, options); - expect(sdk.transactionManager.save).toHaveBeenCalledWith({ - urls - }, { oauth: true }); + expect(sdk.transactionManager.save).toHaveBeenCalledWith(meta, { oauth: true }); }); - it('saves OAuth values from the tokenParams', async () => { - const { sdk, urls, tokenParams } = testContext; - Object.assign(tokenParams, { - responseType: 'code', - state: 'mock-state', - nonce: 'mock-nonce', - scopes: ['a', 'b'], - clientId: 'mock-clientId', - ignoreSignature: true, - redirectUri: 'http://localhost/login/callback', - codeVerifier: 'abcd', - codeChallenge: 'efgh', - codeChallengeMethod: 'fake', - }); - - await getWithRedirect(sdk, {}); - expect(sdk.transactionManager.save).toHaveBeenCalledWith({ - responseType: 'code', - state: 'mock-state', - nonce: 'mock-nonce', - scopes: ['a', 'b'], - clientId: 'mock-clientId', - ignoreSignature: true, - redirectUri: 'http://localhost/login/callback', - codeVerifier: 'abcd', - codeChallenge: 'efgh', - codeChallengeMethod: 'fake', - urls - }, { oauth: true }); - }); }); diff --git a/test/spec/oidc/getWithRedirectLegacy.ts b/test/spec/oidc/getWithRedirectLegacy.ts index 999e099ca..832af5398 100644 --- a/test/spec/oidc/getWithRedirectLegacy.ts +++ b/test/spec/oidc/getWithRedirectLegacy.ts @@ -65,7 +65,7 @@ describe('token.getWithRedirect', function() { // mock window.location so we appear to be on an HTTPS origin originalLocation = global.window.location; - delete global.window.location; + delete (global.window as any).location; global.window.location = { protocol: 'https:', hostname: 'somesite.local' @@ -116,7 +116,7 @@ describe('token.getWithRedirect', function() { spyOn(pkce, 'computeChallenge').and.returnValue(Promise.resolve(codeChallenge)); } it('Uses insecure cookie settings if running on http://localhost', function() { - delete window.location; + delete (window as any).location; window.location = { protocol: 'http:', hostname: 'localhost' diff --git a/test/spec/oidc/getWithoutPrompt.ts b/test/spec/oidc/getWithoutPrompt.ts index c3d719561..60082a710 100644 --- a/test/spec/oidc/getWithoutPrompt.ts +++ b/test/spec/oidc/getWithoutPrompt.ts @@ -85,10 +85,11 @@ describe('token.getWithoutPrompt', function() { // Capture the iframe body = document.getElementsByTagName('body')[0]; var origAppend = body.appendChild; - jest.spyOn(body, 'appendChild').mockImplementation(function (el: HTMLFrameElement) { - if (el.tagName === 'IFRAME') { + jest.spyOn(body, 'appendChild').mockImplementation(function (this: any, el) { + const frame = el as HTMLFrameElement; + if (frame.tagName === 'IFRAME') { // Remove the src so it doesn't actually load - el.src = ''; + frame.src = ''; return origAppend.call(this, el); } return origAppend.apply(this, arguments); @@ -447,10 +448,11 @@ describe('token.getWithoutPrompt', function() { // mock frame creation body = document.getElementsByTagName('body')[0]; var origAppend = body.appendChild; - jest.spyOn(body, 'appendChild').mockImplementation(function(el: HTMLFrameElement) { - if (el.tagName === 'IFRAME') { + jest.spyOn(body, 'appendChild').mockImplementation(function(this: any, el) { + const frame = el as HTMLFrameElement; + if (frame.tagName === 'IFRAME') { // Remove the src so it doesn't actually load - el.src = ''; + frame.src = ''; return origAppend.call(this, el); } return origAppend.apply(this, arguments); diff --git a/test/spec/oidc/revokeToken.ts b/test/spec/oidc/revokeToken.ts index 36513b761..58bae9772 100644 --- a/test/spec/oidc/revokeToken.ts +++ b/test/spec/oidc/revokeToken.ts @@ -50,7 +50,7 @@ function createAccessToken(strValue): AccessToken { describe('token.revoke', function() { it('throws if token is not passed', function() { var oa = setupSync(); - return oa.token.revoke(undefined as AccessToken) + return oa.token.revoke(undefined as unknown as AccessToken) .catch(function(err) { util.assertAuthSdkError(err, 'A valid access or refresh token object is required'); }); diff --git a/test/spec/oidc/util/defaultTokenParams.ts b/test/spec/oidc/util/defaultTokenParams.ts index eb5e1a90e..8801c1521 100644 --- a/test/spec/oidc/util/defaultTokenParams.ts +++ b/test/spec/oidc/util/defaultTokenParams.ts @@ -37,7 +37,7 @@ describe('getDefaultTokenParams', () => { }); afterEach(() => { if ((global.window as any).fake) { - delete global.window; + delete (global as any).window; } }); it('`pkce`: uses value from sdk.options', () => { diff --git a/test/spec/oidc/util/handleOAuthResponse.ts b/test/spec/oidc/util/handleOAuthResponse.ts index d36b83c63..05aaf33f0 100644 --- a/test/spec/oidc/util/handleOAuthResponse.ts +++ b/test/spec/oidc/util/handleOAuthResponse.ts @@ -18,6 +18,7 @@ const verifyToken = jest.fn(); jest.mock('../../../../lib/oidc/verifyToken', () => { return { verifyToken }; }); import { handleOAuthResponse } from '../../../../lib/oidc'; +import { CustomUrls } from '../../../../lib/types'; describe('handleOAuthResponse', () => { let sdk; @@ -44,42 +45,42 @@ describe('handleOAuthResponse', () => { describe('baseline', () => { it('returns access_token from the response', async () => { - const res = await handleOAuthResponse(sdk, { responseType: 'token' }, { access_token: 'foo' }, undefined); + const res = await handleOAuthResponse(sdk, { responseType: 'token' }, { access_token: 'foo' }, undefined as unknown as CustomUrls); expect(res.tokens).toBeTruthy(); expect(res.tokens.accessToken).toBeTruthy(); - expect(res.tokens.accessToken.accessToken).toBe('foo'); + expect(res.tokens.accessToken!.accessToken).toBe('foo'); }); it('returns id_token from the response', async () => { - const res = await handleOAuthResponse(sdk, { responseType: 'id_token' }, { id_token: 'foo' }, undefined); + const res = await handleOAuthResponse(sdk, { responseType: 'id_token' }, { id_token: 'foo' }, undefined as unknown as CustomUrls); expect(res.tokens).toBeTruthy(); expect(res.tokens.idToken).toBeTruthy(); - expect(res.tokens.idToken.idToken).toBe('foo'); + expect(res.tokens.idToken!.idToken).toBe('foo'); }); it('returns refresh_token from the response', async () => { - const res = await handleOAuthResponse(sdk, { responseType: 'refresh_token' }, { refresh_token: 'foo' }, undefined); + const res = await handleOAuthResponse(sdk, { responseType: 'refresh_token' }, { refresh_token: 'foo' }, undefined as unknown as CustomUrls); expect(res.tokens).toBeTruthy(); expect(res.tokens.refreshToken).toBeTruthy(); - expect(res.tokens.refreshToken.refreshToken).toBe('foo'); + expect(res.tokens.refreshToken!.refreshToken).toBe('foo'); }); it('returns all tokens from the response', async () => { const tokenParams = { responseType: ['token', 'id_token', 'refresh_token'] }; const oauthRes = { id_token: 'foo', access_token: 'blar', refresh_token: 'bloo' }; - const res = await handleOAuthResponse(sdk, tokenParams, oauthRes, undefined); + const res = await handleOAuthResponse(sdk, tokenParams, oauthRes, undefined as unknown as CustomUrls); expect(res.tokens).toBeTruthy(); expect(res.tokens.accessToken).toBeTruthy(); - expect(res.tokens.accessToken.accessToken).toBe('blar'); + expect(res.tokens.accessToken!.accessToken).toBe('blar'); expect(res.tokens.idToken).toBeTruthy(); - expect(res.tokens.idToken.idToken).toBe('foo'); + expect(res.tokens.idToken!.idToken).toBe('foo'); expect(res.tokens.refreshToken).toBeTruthy(); - expect(res.tokens.refreshToken.refreshToken).toBe('bloo'); + expect(res.tokens.refreshToken!.refreshToken).toBe('bloo'); }); it('prefers "scope" value from endpoint response over method parameter', async () => { const tokenParams = { responseType: ['token', 'id_token', 'refresh_token'], scopes: ['profile'] }; const oauthRes = { id_token: 'foo', access_token: 'blar', refresh_token: 'bloo', scope: 'openid offline_access' }; - const res = await handleOAuthResponse(sdk, tokenParams, oauthRes, undefined); - expect(res.tokens.accessToken.scopes).toEqual(['openid', 'offline_access']); - expect(res.tokens.idToken.scopes).toEqual(['openid', 'offline_access']); - expect(res.tokens.refreshToken.scopes).toEqual(['openid', 'offline_access']); + const res = await handleOAuthResponse(sdk, tokenParams, oauthRes, undefined as unknown as CustomUrls); + expect(res.tokens.accessToken!.scopes).toEqual(['openid', 'offline_access']); + expect(res.tokens.idToken!.scopes).toEqual(['openid', 'offline_access']); + expect(res.tokens.refreshToken!.scopes).toEqual(['openid', 'offline_access']); }); describe('errors', () => { @@ -87,58 +88,85 @@ describe('handleOAuthResponse', () => { sdk = mockOktaAuth(); }); - it('throws if response contains "error"', async () => { + it('does not throw if response contains only "error" without "error_description"', async () => { + let errorThrown = false; try { - await handleOAuthResponse(sdk, undefined, { error: 'blah' }, undefined); + await handleOAuthResponse(sdk, {}, { error: 'blah' }, undefined as unknown as CustomUrls); } catch (err) { - expect(err.name).toBe('OAuthError'); - expect(err.errorCode).toBe('blah'); + errorThrown = true; } + expect(errorThrown).toBe(false); }); - - it('throws if response contains "error_description"', async () => { + + it('does not throw if response contains only "error_description" without "error"', async () => { + let errorThrown = false; + try { + await handleOAuthResponse(sdk, {}, { error_description: 'blah' }, undefined as unknown as CustomUrls); + } catch (err) { + errorThrown = true; + } + expect(errorThrown).toBe(false); + }); + + it('throws if response contains both "error" and "error_description"', async () => { + let errorThrown = false; try { - await handleOAuthResponse(sdk, undefined, { error_description: 'blah' }, undefined); + await handleOAuthResponse(sdk, {}, { error: 'error code', error_description: 'error description' }, undefined as unknown as CustomUrls); } catch (err) { + errorThrown = true; expect(err.name).toBe('OAuthError'); - expect(err.errorSummary).toBe('blah'); + expect(err.errorCode).toBe('error code'); + expect(err.errorSummary).toBe('error description'); } + expect(errorThrown).toBe(true); }); it('throws if state does not match', async () => { + let errorThrown = false; try { - await handleOAuthResponse(sdk, { state: 'bar' }, { state: 'foo' }, undefined); + await handleOAuthResponse(sdk, { state: 'bar' }, { state: 'foo' }, undefined as unknown as CustomUrls); } catch (err) { + errorThrown = true; expect(err.name).toBe('AuthSdkError'); expect(err.errorSummary).toBe(`OAuth flow response state doesn't match request state`); } + expect(errorThrown).toBe(true); }); it('throws if ID token was expected but not returend', async () => { + let errorThrown = false; try { - await handleOAuthResponse(sdk, { responseType: ['token', 'id_token'] }, { access_token: 'foo' }, undefined); + await handleOAuthResponse(sdk, { responseType: ['token', 'id_token'] }, { access_token: 'foo' }, undefined as unknown as CustomUrls); } catch (err) { + errorThrown = true; expect(err.name).toBe('AuthSdkError'); expect(err.errorCode).toBe('INTERNAL'); expect(err.errorSummary).toBe(`Unable to parse OAuth flow response: response type "id_token" was requested but "id_token" was not returned.`); } + expect(errorThrown).toBe(true); }); it('throws if access token was expected but not returend', async () => { + let errorThrown = false; try { - await handleOAuthResponse(sdk, { responseType: ['token', 'id_token'] }, { id_token: 'foo' }, undefined); + await handleOAuthResponse(sdk, { responseType: ['token', 'id_token'] }, { id_token: 'foo' }, undefined as unknown as CustomUrls); } catch (err) { + errorThrown = true; expect(err.name).toBe('AuthSdkError'); expect(err.errorCode).toBe('INTERNAL'); expect(err.errorSummary).toBe(`Unable to parse OAuth flow response: response type "token" was requested but "access_token" was not returned.`); } + expect(errorThrown).toBe(true); }); it('throws if id_token and access token were expected but not returned', async () => { + let errorThrown = false; try { - await handleOAuthResponse(sdk, { responseType: ['token', 'id_token'] }, { }, undefined); + await handleOAuthResponse(sdk, { responseType: ['token', 'id_token'] }, { }, undefined as unknown as CustomUrls); } catch (err) { + errorThrown = true; expect(err.name).toBe('AuthSdkError'); expect(err.errorCode).toBe('INTERNAL'); expect(err.errorSummary).toBe(`Unable to parse OAuth flow response: response type "token" was requested but "access_token" was not returned.`); } + expect(errorThrown).toBe(true); }); }); }); @@ -167,7 +195,7 @@ describe('handleOAuthResponse', () => { describe('Authorization code flow', () => { it('calls `exchangeCodeForTokens` if response contains "code"', async () => { - const res = await handleOAuthResponse(sdk, {}, { code: 'blah' }, undefined); + const res = await handleOAuthResponse(sdk, {}, { code: 'blah' }, undefined as unknown as CustomUrls); expect(exchangeCodeForTokens).toHaveBeenCalledWith(sdk, { authorizationCode: 'blah', interactionCode: undefined @@ -178,7 +206,7 @@ describe('handleOAuthResponse', () => { describe('Interaction code flow', () => { it('calls `exchangeCodeForTokens` if response contains "interaction_code"', async () => { - const res = await handleOAuthResponse(sdk, {}, { 'interaction_code': 'blah' }, undefined); + const res = await handleOAuthResponse(sdk, {}, { 'interaction_code': 'blah' }, undefined as unknown as CustomUrls); expect(exchangeCodeForTokens).toHaveBeenCalledWith(sdk, { authorizationCode: undefined, interactionCode: 'blah' diff --git a/test/spec/oidc/util/loginRedirect.ts b/test/spec/oidc/util/loginRedirect.ts index ddeaef280..80c9a70c5 100644 --- a/test/spec/oidc/util/loginRedirect.ts +++ b/test/spec/oidc/util/loginRedirect.ts @@ -40,7 +40,7 @@ describe('util/loginRedirect', function() { }); function mockHash(hash) { - delete window.location; + delete (window as any).location; window.location = { hash: `#${hash}`, href: `${redirectUri}#${hash}` @@ -48,7 +48,7 @@ describe('util/loginRedirect', function() { } function mockSearch(search) { - delete window.location; + delete (window as any).location; window.location = { search: `?${search}`, href: `${redirectUri}?${search}` diff --git a/test/spec/oidc/util/oauthMeta.ts b/test/spec/oidc/util/oauthMeta.ts new file mode 100644 index 000000000..9a7bbee84 --- /dev/null +++ b/test/spec/oidc/util/oauthMeta.ts @@ -0,0 +1,91 @@ +import { createOAuthMeta } from '../../../../lib/oidc/util/oauthMeta'; + +jest.mock('../../../../lib/oidc/util/oauth', () => { + return { + getOAuthUrls: () => {} + }; +}); + + +const mocked = { + oauth: require('../../../../lib/oidc/util/oauth'), +}; + +describe('oauthMeta', () => { + let testContext; + beforeEach(() => { + const sdk = { + options: { + + }, + getOriginalUri: () => {}, + transactionManager: { + save: () => {} + }, + token: { + getWithRedirect: { + _setLocation: () => {} + } + } + }; + const tokenParams = { + ignoreSignatue: true + }; + const urls = { + authorizeUrl: 'http://fake-authorize' + }; + testContext = { + sdk, + tokenParams, + urls, + }; + }); + + it('saves issuer from sdk', async () => { + const { sdk, tokenParams } = testContext; + const issuer = 'http://fake'; + sdk.options.issuer = issuer; + const meta = createOAuthMeta(sdk, tokenParams); + expect(meta.issuer).toBe(issuer); + }); + + it('saves urls from `getOAuthUrls`', async () => { + const { sdk, urls, tokenParams } = testContext; + jest.spyOn(mocked.oauth, 'getOAuthUrls').mockReturnValue(urls); + const meta = createOAuthMeta(sdk, tokenParams); + expect(mocked.oauth.getOAuthUrls).toHaveBeenCalledWith(sdk, tokenParams); + expect(meta.urls).toEqual(urls); + }); + + it('saves OAuth values from the tokenParams', async () => { + const { sdk, tokenParams } = testContext; + Object.assign(tokenParams, { + responseType: 'code', + responseMode: 'fragment', + state: 'mock-state', + nonce: 'mock-nonce', + scopes: ['a', 'b'], + clientId: 'mock-clientId', + ignoreSignature: true, + redirectUri: 'http://localhost/login/callback', + codeVerifier: 'abcd', + codeChallenge: 'efgh', + codeChallengeMethod: 'fake', + }); + + const meta = createOAuthMeta(sdk, tokenParams); + expect(meta).toEqual({ + responseType: 'code', + responseMode: 'fragment', + state: 'mock-state', + nonce: 'mock-nonce', + scopes: ['a', 'b'], + clientId: 'mock-clientId', + ignoreSignature: true, + redirectUri: 'http://localhost/login/callback', + codeVerifier: 'abcd', + codeChallenge: 'efgh', + codeChallengeMethod: 'fake', + }); + }); +}); diff --git a/test/spec/oidc/util/validateClaims.ts b/test/spec/oidc/util/validateClaims.ts index b0c467134..9c0cd4a5f 100644 --- a/test/spec/oidc/util/validateClaims.ts +++ b/test/spec/oidc/util/validateClaims.ts @@ -34,7 +34,7 @@ describe('validateClaims', function () { }); it('throws an AuthSdkError when no jwt is provided', function () { - var fn = function () { validateClaims(sdk, undefined, validationOptions); }; + var fn = function () { validateClaims(sdk, undefined as unknown as UserClaims, validationOptions); }; expect(fn).toThrowError('The jwt, iss, and aud arguments are all required'); }); diff --git a/test/support/idx/factories/errors.ts b/test/support/idx/factories/errors.ts index e843a6d36..e8eaf35fc 100644 --- a/test/support/idx/factories/errors.ts +++ b/test/support/idx/factories/errors.ts @@ -20,7 +20,7 @@ export const IdxErrorMessageFactory = Factory.define(() => { return { class: 'ERROR', i18n: { - key: undefined + key: undefined as unknown as string }, message: 'Default error message' }; diff --git a/test/support/idx/factories/messages.ts b/test/support/idx/factories/messages.ts index a57269565..5898db9fb 100644 --- a/test/support/idx/factories/messages.ts +++ b/test/support/idx/factories/messages.ts @@ -17,7 +17,7 @@ import { IdxMessage, IdxMessages } from '../../../../lib/idx/types/idx-js'; export const IdxMessagesFactory = Factory.define(() => { return { type: 'array', - value: null + value: [] }; }); @@ -25,7 +25,7 @@ export const IdxInfoMessageFactory = Factory.define(() => { return { class: 'INFO', i18n: { - key: undefined + key: 'undefined' }, message: 'Default info message' }; diff --git a/test/support/idx/factories/responses.ts b/test/support/idx/factories/responses.ts index 69c3c8b48..69e54a9fc 100644 --- a/test/support/idx/factories/responses.ts +++ b/test/support/idx/factories/responses.ts @@ -41,7 +41,8 @@ export const IdxResponseFactory = Factory.define { return { - proceed: () => Promise.resolve(transientParams.nextResponse), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + proceed: () => Promise.resolve(transientParams.nextResponse!), neededToProceed: [], rawIdxState: RawIdxResponseFactory.build({ version: transientParams.idxVersion, diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 000000000..3469d8a33 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "es6", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "alwaysStrict": true, + "noImplicitAny": false, + "strict": true, + "noEmit": true, + "outDir": "../build/test", + "resolveJsonModule": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": false, + "skipLibCheck": true, + "rootDir": "../", + "baseUrl": "../", + "lib": [ + "ES2020.Promise" + ] + }, + "include": [ + "../types/global.d.ts", + "spec/**/*.ts", + "integration/**/*.ts", + "support/**/*.ts", + "types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/test/types/auth.test-d.ts b/test/types/auth.test-d.ts index 34e02a61e..b37a2c95f 100644 --- a/test/types/auth.test-d.ts +++ b/test/types/auth.test-d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -94,12 +95,12 @@ const authorizeOptions2: TokenParams = { // originalUri expectType(authClient.setOriginalUri(`${window.location.href}`)); - expectType(authClient.getOriginalUri()); + expectType(authClient.getOriginalUri()!); expectType(authClient.removeOriginalUri()); // Tokens - expectType(authClient.getIdToken()); - expectType(authClient.getAccessToken()); + expectType(authClient.getIdToken()!); + expectType(authClient.getAccessToken()!); // User expectType(await authClient.isAuthenticated()); @@ -126,7 +127,7 @@ const authorizeOptions2: TokenParams = { revokeRefreshToken: false, accessToken: tokens.accessToken, })); - expectAssignable(await authClient.closeSession()); - expectType(await authClient.revokeAccessToken(tokens.accessToken)); - expectType(await authClient.revokeRefreshToken(tokens.refreshToken)); + expectAssignable(await authClient.closeSession()); + expectType(await authClient.revokeAccessToken(tokens.accessToken)); + expectType(await authClient.revokeRefreshToken(tokens.refreshToken)); })(); diff --git a/test/types/authStateManager.test-d.ts b/test/types/authStateManager.test-d.ts index cabc9456d..856e64053 100644 --- a/test/types/authStateManager.test-d.ts +++ b/test/types/authStateManager.test-d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -30,11 +31,11 @@ const authClient = new OktaAuth({}); authStateManager.updateAuthState(); - const authState = authStateManager.getAuthState(); + const authState = authStateManager.getAuthState()!; expectType(authState); - expectType(authState.accessToken); - expectType(authState.idToken); - expectType(authState.refreshToken); - expectType(authState.isAuthenticated); - expectType(authState.error.message); + expectType(authState.accessToken!); + expectType(authState.idToken!); + expectType(authState.refreshToken!); + expectType(authState.isAuthenticated!); + expectType(authState.error!.message); })(); diff --git a/test/types/session.test-d.ts b/test/types/session.test-d.ts index 1312b2b44..6ef146b6b 100644 --- a/test/types/session.test-d.ts +++ b/test/types/session.test-d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -26,6 +27,6 @@ const authClient = new OktaAuth({}); // Session expectType(session.status); - expectType(await session.user()); - expectType(await session.refresh()); + expectType(await session.user!()); + expectType(await session.refresh!()); })(); diff --git a/test/types/token.test-d.ts b/test/types/token.test-d.ts index a20615889..c00c96cf6 100644 --- a/test/types/token.test-d.ts +++ b/test/types/token.test-d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -73,9 +74,9 @@ const tokens = { const tokenRes = await authClient.token.getWithoutPrompt(authorizeOptions); expectType(tokenRes); expectType(tokenRes.tokens); - expectType(tokenRes.tokens.accessToken); - expectType(tokenRes.tokens.idToken); - expectType(tokenRes.tokens.refreshToken); + expectType(tokenRes.tokens.accessToken!); + expectType(tokenRes.tokens.idToken!); + expectType(tokenRes.tokens.refreshToken!); expectType(await authClient.token.getWithPopup(authorizeOptions)); expectType(await authClient.token.getWithRedirect(authorizeOptions)); @@ -98,7 +99,7 @@ const tokens = { expectType(decodedToken.header.alg); expectType(decodedToken.signature); - expectType(await authClient.token.renew(accessTokenExample)); + expectType(await authClient.token.renew(accessTokenExample)); const userInfo = await authClient.token.getUserInfo(accessTokenExample, idTokenExample); expectType(userInfo); diff --git a/test/types/tokenManager.test-d.ts b/test/types/tokenManager.test-d.ts index 644c8193b..838540000 100644 --- a/test/types/tokenManager.test-d.ts +++ b/test/types/tokenManager.test-d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -39,13 +40,13 @@ const authClient = new OktaAuth({}); // Manage tokenManager.setTokens(tokens); - expectType(tokenManager.add('accessToken', tokens.accessToken)); + expectType(tokenManager.add('accessToken', tokens.accessToken!)); expectType(tokenManager.remove('accessToken')); expectType(tokenManager.clear()); // Renew expectType(tokenManager.hasExpired(accessToken)); - expectType(await tokenManager.renew('idToken')); + expectType(await tokenManager.renew('idToken')); // Events tokenManager.on('expired', function (key, expiredToken) { @@ -55,7 +56,7 @@ const authClient = new OktaAuth({}); tokenManager.on('renewed', function (key, newToken, oldToken) { expectType(key); expectType(newToken); - expectType(oldToken); + expectType(oldToken!); }); tokenManager.on('error', function (error) { expectAssignable(error); diff --git a/test/types/transaction.test-d.ts b/test/types/transaction.test-d.ts index 576f2619a..c579a5982 100644 --- a/test/types/transaction.test-d.ts +++ b/test/types/transaction.test-d.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /*! * Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") @@ -25,43 +26,43 @@ const authClient = new OktaAuth({}); // Manage transaction expectType(await authClient.tx.resume()); - expectType(await tx.verify({ + expectType(await tx.verify!({ passCode: '123456', autoPush: true })); - expectType(await tx.activate({ + expectType(await tx.activate!({ passCode: '123456' })); - expectType(await tx.cancel()); - expectType(await tx.poll({ + expectType(await tx.cancel!()); + expectType(await tx.poll!({ autoPush: true })); - expectType(await tx.prev()); - expectType(await tx.skip()); - expectType(await tx.changePassword({ + expectType(await tx.prev!()); + expectType(await tx.skip!()); + expectType(await tx.changePassword!({ oldPassword: '0ldP4ssw0rd', newPassword: 'N3wP4ssw0rd' })); - expectType(await tx.resetPassword({ + expectType(await tx.resetPassword!({ newPassword: 'N3wP4ssw0rd' })); - expectType(await tx.unlock({ + expectType(await tx.unlock!({ username: 'dade.murphy@example.com', factorType: 'EMAIL', relayState: 'd3de23' })); - expectType(await tx.answer({ + expectType(await tx.answer!({ answer: 'My favorite recovery question answer' })); - expectType(await tx.recovery({ + expectType(await tx.recovery!({ recoveryToken: '00xdqXOE5qDZX8-PBR1bYv8AESqIFinDy3yul01tyh' })); - expectType(await tx.resend()); + expectType(await tx.resend!()); // Questions - const questionFactor = tx.factors.find(function(factor) { + const questionFactor = tx.factors!.find(function(factor) { return factor.provider === 'OKTA' && factor.factorType === 'question'; - }); + })!; const questions = await questionFactor.questions() as Array; expectType>(questions); questionFactor.enroll({ diff --git a/tsconfig.json b/tsconfig.json index 13ced6c2e..a537e02be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,9 @@ "moduleResolution": "node", "alwaysStrict": true, "noImplicitAny": false, - "strictNullChecks": false, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, "noEmit": false, "declaration": true, "emitDeclarationOnly": false,