From aa50438ef168ac2a42d5b01064831ad117859cff Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Wed, 29 Sep 2021 12:46:39 -0400 Subject: [PATCH 1/6] feat: add state options to onAuthRequired callback --- src/OktaContext.ts | 11 ++++++++++- src/SecureRoute.tsx | 11 ++++++++--- src/Security.tsx | 20 ++++++++++++-------- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/OktaContext.ts b/src/OktaContext.ts index bdab0a93..caf9de1b 100644 --- a/src/OktaContext.ts +++ b/src/OktaContext.ts @@ -12,7 +12,15 @@ import * as React from 'react'; import { AuthState, OktaAuth } from '@okta/okta-auth-js'; -export type OnAuthRequiredFunction = (oktaAuth: OktaAuth) => Promise | void; +export enum AUTHSTATE_STATUS { + INITIALIZED = 'INITIALIZED', + UPDATED = 'UPDATED' +} + +export type OnAuthRequiredFunction = ( + oktaAuth: OktaAuth, + options: { state: AUTHSTATE_STATUS } +) => Promise | void; export type OnAuthResumeFunction = () => void; export type RestoreOriginalUriFunction = (oktaAuth: OktaAuth, originalUri: string) => Promise | void; @@ -21,6 +29,7 @@ export interface IOktaContext { oktaAuth: OktaAuth; authState: AuthState | null; _onAuthRequired?: OnAuthRequiredFunction; + _authStateStatus: AUTHSTATE_STATUS | null; } const OktaContext = React.createContext(null); diff --git a/src/SecureRoute.tsx b/src/SecureRoute.tsx index f3b9ea67..78536a86 100644 --- a/src/SecureRoute.tsx +++ b/src/SecureRoute.tsx @@ -11,7 +11,7 @@ */ import * as React from 'react'; -import { useOktaAuth, OnAuthRequiredFunction } from './OktaContext'; +import { useOktaAuth, OnAuthRequiredFunction, AUTHSTATE_STATUS } from './OktaContext'; import { Route, useRouteMatch, RouteProps } from 'react-router-dom'; import { toRelativeUrl } from '@okta/okta-auth-js'; @@ -21,7 +21,12 @@ const SecureRoute: React.FC<{ onAuthRequired, ...routeProps }) => { - const { oktaAuth, authState, _onAuthRequired } = useOktaAuth(); + const { + oktaAuth, + authState, + _onAuthRequired, + _authStateStatus + } = useOktaAuth(); const match = useRouteMatch(routeProps); const pendingLogin = React.useRef(false); @@ -37,7 +42,7 @@ const SecureRoute: React.FC<{ oktaAuth.setOriginalUri(originalUri); const onAuthRequiredFn = onAuthRequired || _onAuthRequired; if (onAuthRequiredFn) { - await onAuthRequiredFn(oktaAuth); + await onAuthRequiredFn(oktaAuth, { state: _authStateStatus as AUTHSTATE_STATUS }); } else { await oktaAuth.signInWithRedirect(); } diff --git a/src/Security.tsx b/src/Security.tsx index 9b184a37..faf9c754 100644 --- a/src/Security.tsx +++ b/src/Security.tsx @@ -12,7 +12,11 @@ import * as React from 'react'; import { AuthSdkError, AuthState, OktaAuth } from '@okta/okta-auth-js'; -import OktaContext, { OnAuthRequiredFunction, RestoreOriginalUriFunction } from './OktaContext'; +import OktaContext, { + OnAuthRequiredFunction, + RestoreOriginalUriFunction, + AUTHSTATE_STATUS +} from './OktaContext'; import OktaError from './OktaError'; const Security: React.FC<{ @@ -41,6 +45,7 @@ const Security: React.FC<{ const majorVersion = oktaAuthVersion?.split('.')[0]; return majorVersion; }); + const [authStateStatus, setAuthStateStatus] = React.useState(null); React.useEffect(() => { if (!oktaAuth || !restoreOriginalUri) { @@ -64,16 +69,14 @@ const Security: React.FC<{ // Update Security provider with latest authState const handler = (authState: AuthState) => { + const newAuthStateStatus = authStateStatus === AUTHSTATE_STATUS.INITIALIZED ? AUTHSTATE_STATUS.UPDATED : AUTHSTATE_STATUS.INITIALIZED; + setAuthStateStatus(newAuthStateStatus); setAuthState(authState); }; oktaAuth.authStateManager.subscribe(handler); - // Trigger an initial change event to make sure authState is latest - if (!oktaAuth.isLoginRedirect()) { - // Calculates initial auth state and fires change event for listeners - // Also starts the token auto-renew service - oktaAuth.start(); - } + // Start services + oktaAuth.start(); return () => { oktaAuth.authStateManager.unsubscribe(handler); @@ -102,7 +105,8 @@ const Security: React.FC<{ {children} From 194a2f5e7cd1e8b8554ce6df06c8ea810705aed9 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Wed, 29 Sep 2021 12:47:22 -0400 Subject: [PATCH 2/6] update generated samples --- .../samples/custom-login/config-overrides.js | 12 ++++++++++++ generated/samples/custom-login/env/index.js | 12 ++++++++++++ generated/samples/custom-login/package.json | 4 ++-- .../samples/doc-direct-auth/config-overrides.js | 12 ++++++++++++ generated/samples/doc-direct-auth/env/index.js | 12 ++++++++++++ generated/samples/doc-direct-auth/package.json | 2 +- .../doc-embedded-widget/config-overrides.js | 12 ++++++++++++ .../samples/doc-embedded-widget/env/index.js | 12 ++++++++++++ .../samples/doc-embedded-widget/package.json | 4 ++-- .../okta-hosted-login/config-overrides.js | 12 ++++++++++++ generated/samples/okta-hosted-login/env/index.js | 12 ++++++++++++ generated/samples/okta-hosted-login/package.json | 2 +- test/e2e/harness/package.json | 2 +- test/e2e/harness/tsconfig.json | 3 ++- yarn.lock | 16 ++++++++-------- 15 files changed, 113 insertions(+), 16 deletions(-) diff --git a/generated/samples/custom-login/config-overrides.js b/generated/samples/custom-login/config-overrides.js index db68d8d2..31821097 100644 --- a/generated/samples/custom-login/config-overrides.js +++ b/generated/samples/custom-login/config-overrides.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable import/no-extraneous-dependencies */ const webpack = require('webpack'); const envModule = require('./env')(); diff --git a/generated/samples/custom-login/env/index.js b/generated/samples/custom-login/env/index.js index 71c68be2..1e1ccc0a 100644 --- a/generated/samples/custom-login/env/index.js +++ b/generated/samples/custom-login/env/index.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable */ module.exports = function () { let oktaEnv; diff --git a/generated/samples/custom-login/package.json b/generated/samples/custom-login/package.json index b28b6a70..9acab367 100644 --- a/generated/samples/custom-login/package.json +++ b/generated/samples/custom-login/package.json @@ -3,9 +3,9 @@ "version": "0.3.0", "private": true, "dependencies": { - "@okta/okta-auth-js": "^5.4.0", + "@okta/okta-auth-js": "^5.5.0", "@okta/okta-react": "*", - "@okta/okta-signin-widget": "^5.10.0", + "@okta/okta-signin-widget": "^5.11.1", "colors": "^1.4.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.0.3", diff --git a/generated/samples/doc-direct-auth/config-overrides.js b/generated/samples/doc-direct-auth/config-overrides.js index db68d8d2..31821097 100644 --- a/generated/samples/doc-direct-auth/config-overrides.js +++ b/generated/samples/doc-direct-auth/config-overrides.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable import/no-extraneous-dependencies */ const webpack = require('webpack'); const envModule = require('./env')(); diff --git a/generated/samples/doc-direct-auth/env/index.js b/generated/samples/doc-direct-auth/env/index.js index 71c68be2..1e1ccc0a 100644 --- a/generated/samples/doc-direct-auth/env/index.js +++ b/generated/samples/doc-direct-auth/env/index.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable */ module.exports = function () { let oktaEnv; diff --git a/generated/samples/doc-direct-auth/package.json b/generated/samples/doc-direct-auth/package.json index 3ca61efb..efffd341 100644 --- a/generated/samples/doc-direct-auth/package.json +++ b/generated/samples/doc-direct-auth/package.json @@ -3,7 +3,7 @@ "version": "0.3.0", "private": true, "dependencies": { - "@okta/okta-auth-js": "^5.4.0", + "@okta/okta-auth-js": "^5.5.0", "@okta/okta-react": "*", "react": "^16.8.0", "react-dom": "^16.8.0", diff --git a/generated/samples/doc-embedded-widget/config-overrides.js b/generated/samples/doc-embedded-widget/config-overrides.js index db68d8d2..31821097 100644 --- a/generated/samples/doc-embedded-widget/config-overrides.js +++ b/generated/samples/doc-embedded-widget/config-overrides.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable import/no-extraneous-dependencies */ const webpack = require('webpack'); const envModule = require('./env')(); diff --git a/generated/samples/doc-embedded-widget/env/index.js b/generated/samples/doc-embedded-widget/env/index.js index 71c68be2..1e1ccc0a 100644 --- a/generated/samples/doc-embedded-widget/env/index.js +++ b/generated/samples/doc-embedded-widget/env/index.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable */ module.exports = function () { let oktaEnv; diff --git a/generated/samples/doc-embedded-widget/package.json b/generated/samples/doc-embedded-widget/package.json index d56fe10f..add2ef9b 100644 --- a/generated/samples/doc-embedded-widget/package.json +++ b/generated/samples/doc-embedded-widget/package.json @@ -3,9 +3,9 @@ "version": "0.3.0", "private": true, "dependencies": { - "@okta/okta-auth-js": "^5.4.0", + "@okta/okta-auth-js": "^5.5.0", "@okta/okta-react": "*", - "@okta/okta-signin-widget": "^5.10.0", + "@okta/okta-signin-widget": "^5.11.1", "react": "^16.8.0", "react-dom": "^16.8.0", "react-router-dom": "^5.2.0", diff --git a/generated/samples/okta-hosted-login/config-overrides.js b/generated/samples/okta-hosted-login/config-overrides.js index db68d8d2..31821097 100644 --- a/generated/samples/okta-hosted-login/config-overrides.js +++ b/generated/samples/okta-hosted-login/config-overrides.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable import/no-extraneous-dependencies */ const webpack = require('webpack'); const envModule = require('./env')(); diff --git a/generated/samples/okta-hosted-login/env/index.js b/generated/samples/okta-hosted-login/env/index.js index 71c68be2..1e1ccc0a 100644 --- a/generated/samples/okta-hosted-login/env/index.js +++ b/generated/samples/okta-hosted-login/env/index.js @@ -1,3 +1,15 @@ +/*! + * Copyright (c) 2017-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. + */ + /* eslint-disable */ module.exports = function () { let oktaEnv; diff --git a/generated/samples/okta-hosted-login/package.json b/generated/samples/okta-hosted-login/package.json index e4a9cda7..59f27e15 100644 --- a/generated/samples/okta-hosted-login/package.json +++ b/generated/samples/okta-hosted-login/package.json @@ -3,7 +3,7 @@ "version": "0.3.0", "private": true, "dependencies": { - "@okta/okta-auth-js": "^5.4.0", + "@okta/okta-auth-js": "^5.5.0", "@okta/okta-react": "*", "colors": "^1.4.0", "semantic-ui-css": "^2.4.1", diff --git a/test/e2e/harness/package.json b/test/e2e/harness/package.json index e71b984e..f36fd7f6 100644 --- a/test/e2e/harness/package.json +++ b/test/e2e/harness/package.json @@ -4,7 +4,7 @@ "version": "0.0.8", "private": true, "dependencies": { - "@okta/okta-auth-js": "^5.4.0", + "@okta/okta-auth-js": "^5.5.0", "@okta/okta-react": "*", "@types/node": "^14.14.7", "@types/react": "^16.9.56", diff --git a/test/e2e/harness/tsconfig.json b/test/e2e/harness/tsconfig.json index 1e57eea2..724c67eb 100644 --- a/test/e2e/harness/tsconfig.json +++ b/test/e2e/harness/tsconfig.json @@ -17,7 +17,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react" + "jsx": "react-jsx", + "noFallthroughCasesInSwitch": true }, "include": [ "src" diff --git a/yarn.lock b/yarn.lock index fc212c44..a70d8b53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2056,10 +2056,10 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@okta/okta-auth-js@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-5.4.0.tgz#f57519b2bc8ea1200c43e31cb4924d576ec6d059" - integrity sha512-JXFAhEM0cTjsn9SzhhmZK6bDlat93E8KC8c8v2XeZBJYDDuIrbyS9h2psN4K/gmaGoSu0v+vmFVXBlLBwiaEHA== +"@okta/okta-auth-js@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-5.5.0.tgz#aef4014f2561ca1e201a6819714b176020f488f4" + integrity sha512-9WQSyHA0l2YV8VhjjjlaW3FiNHybxoGUwmzpz7igSpfgiItMkvMxD8LB0BeGHGvIOpo3KRJXbPUAI/3Jk7n5dw== dependencies: "@babel/runtime" "^7.12.5" "@okta/okta-idx-js" "0.19.0" @@ -2087,10 +2087,10 @@ core-js "^3.15.1" jsonpath-plus "^5.1.0" -"@okta/okta-signin-widget@^5.10.0": - version "5.10.0" - resolved "https://registry.yarnpkg.com/@okta/okta-signin-widget/-/okta-signin-widget-5.10.0.tgz#b8d57344b64db18fdf34587eece739d17699d584" - integrity sha512-ZvkUTF/bnWV6M3zEFU+zQ+MhwfOmT8jbvjRZARFs46iZBrx4V73FvbEuDA0MeUtQK1HF0PSB2mQkJDAhqGQYug== +"@okta/okta-signin-widget@^5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@okta/okta-signin-widget/-/okta-signin-widget-5.11.1.tgz#8e8ead9a6bff25a78a3285601dd7092e810fe38c" + integrity sha512-aJh6BY52pp1XwkR4//EquzE9syoGfND2RqZ3G0I+7trAVQiYyR5e5Cf4okswIf9jaFK7e3ZgxVuhcfY9+R3kaQ== dependencies: "@babel/polyfill" "^7.10.1" "@babel/runtime" "^7.10.3" From 539b12617b1ac91b587003785de23c3ea81e9918 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Wed, 29 Sep 2021 14:04:28 -0400 Subject: [PATCH 3/6] test: update unit test --- test/jest/secureRoute.test.tsx | 17 +++++++++++++++-- test/jest/security.test.tsx | 19 ++----------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/test/jest/secureRoute.test.tsx b/test/jest/secureRoute.test.tsx index 693117a9..4e2e5f11 100644 --- a/test/jest/secureRoute.test.tsx +++ b/test/jest/secureRoute.test.tsx @@ -16,6 +16,7 @@ import { act } from 'react-dom/test-utils'; import { MemoryRouter, Route, RouteProps } from 'react-router-dom'; import SecureRoute from '../../src/SecureRoute'; import Security from '../../src/Security'; +import { AUTHSTATE_STATUS } from '../../src/OktaContext'; describe('', () => { let oktaAuth; @@ -215,6 +216,14 @@ describe('', () => { it('calls onAuthRequired if provided from Security', () => { const onAuthRequired = jest.fn(); + jest.spyOn(React, 'useState') + .mockReturnValueOnce([{ isAuthenticated: false }, jest.fn()]) + .mockReturnValueOnce(['999', jest.fn()]) + .mockReturnValueOnce([AUTHSTATE_STATUS.INITIALIZED, jest.fn()]); + + oktaAuth.authStateManager.updateAuthState = jest.fn().mockImplementation(() => { + + }), mount( @@ -224,12 +233,16 @@ describe('', () => { ); expect(oktaAuth.setOriginalUri).toHaveBeenCalled(); expect(oktaAuth.signInWithRedirect).not.toHaveBeenCalled(); - expect(onAuthRequired).toHaveBeenCalledWith(oktaAuth); + expect(onAuthRequired).toHaveBeenCalledWith(oktaAuth, { state: AUTHSTATE_STATUS.INITIALIZED }); }); it('calls onAuthRequired from SecureRoute if provide from both Security and SecureRoute', () => { const onAuthRequired1 = jest.fn(); const onAuthRequired2 = jest.fn(); + jest.spyOn(React, 'useState') + .mockReturnValueOnce([{ isAuthenticated: false }, jest.fn()]) + .mockReturnValueOnce(['999', jest.fn()]) + .mockReturnValueOnce([AUTHSTATE_STATUS.INITIALIZED, jest.fn()]); mount( @@ -240,7 +253,7 @@ describe('', () => { expect(oktaAuth.setOriginalUri).toHaveBeenCalled(); expect(oktaAuth.signInWithRedirect).not.toHaveBeenCalled(); expect(onAuthRequired1).not.toHaveBeenCalled(); - expect(onAuthRequired2).toHaveBeenCalledWith(oktaAuth); + expect(onAuthRequired2).toHaveBeenCalledWith(oktaAuth, { state: AUTHSTATE_STATUS.INITIALIZED }); }); }); diff --git a/test/jest/security.test.tsx b/test/jest/security.test.tsx index 34ef4162..1d693ad0 100644 --- a/test/jest/security.test.tsx +++ b/test/jest/security.test.tsx @@ -43,7 +43,6 @@ describe('', () => { subscribe: jest.fn(), unsubscribe: jest.fn(), }, - isLoginRedirect: jest.fn().mockImplementation(() => false), start: jest.fn(), stop: jest.fn(), }; @@ -57,7 +56,7 @@ describe('', () => { restoreOriginalUri }; mount(); - expect(addEnvironmentSpy).toBeCalledWith(`${process.env.PACKAGE_NAME}/${process.env.PACKAGE_VERSION}`); + expect(addEnvironmentSpy).toHaveBeenCalledWith(`${process.env.PACKAGE_NAME}/${process.env.PACKAGE_VERSION}`); }); it('logs a warning in case _oktaUserAgent is not available on auth SDK instance', () => { @@ -70,7 +69,7 @@ describe('', () => { restoreOriginalUri }; mount(); - expect(console.warn).toBeCalled(); + expect(console.warn).toHaveBeenCalled(); }); describe('throws version not match error', () => { @@ -187,20 +186,6 @@ describe('', () => { expect(MyComponent).toHaveBeenCalledTimes(2); }); - it('should not call start when in login redirect state', () => { - oktaAuth.isLoginRedirect = jest.fn().mockImplementation(() => true); - const mockProps = { - oktaAuth, - restoreOriginalUri - }; - mount( - - - - ); - expect(oktaAuth.start).not.toHaveBeenCalled(); - }); - it('subscribes to "authStateChange" and updates the context', () => { const mockAuthStates = [ initialAuthState, From 75885d77ed47b0cf6850826fb20983d459c4d8d1 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Wed, 29 Sep 2021 16:15:19 -0400 Subject: [PATCH 4/6] add test for authState status update --- src/Security.tsx | 9 +++++---- test/jest/security.test.tsx | 17 ++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Security.tsx b/src/Security.tsx index faf9c754..8df18dd0 100644 --- a/src/Security.tsx +++ b/src/Security.tsx @@ -45,7 +45,7 @@ const Security: React.FC<{ const majorVersion = oktaAuthVersion?.split('.')[0]; return majorVersion; }); - const [authStateStatus, setAuthStateStatus] = React.useState(null); + const authStateStatus = React.useRef(null); React.useEffect(() => { if (!oktaAuth || !restoreOriginalUri) { @@ -69,8 +69,9 @@ const Security: React.FC<{ // Update Security provider with latest authState const handler = (authState: AuthState) => { - const newAuthStateStatus = authStateStatus === AUTHSTATE_STATUS.INITIALIZED ? AUTHSTATE_STATUS.UPDATED : AUTHSTATE_STATUS.INITIALIZED; - setAuthStateStatus(newAuthStateStatus); + authStateStatus.current = authStateStatus.current === AUTHSTATE_STATUS.INITIALIZED + ? AUTHSTATE_STATUS.UPDATED + : AUTHSTATE_STATUS.INITIALIZED; setAuthState(authState); }; oktaAuth.authStateManager.subscribe(handler); @@ -106,7 +107,7 @@ const Security: React.FC<{ oktaAuth, authState, _onAuthRequired: onAuthRequired, - _authStateStatus: authStateStatus + _authStateStatus: authStateStatus.current }}> {children} diff --git a/test/jest/security.test.tsx b/test/jest/security.test.tsx index 1d693ad0..3873ca3d 100644 --- a/test/jest/security.test.tsx +++ b/test/jest/security.test.tsx @@ -15,7 +15,7 @@ import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { MemoryRouter } from 'react-router-dom'; import Security from '../../src/Security'; -import { useOktaAuth } from '../../src/OktaContext'; +import { AUTHSTATE_STATUS, useOktaAuth } from '../../src/OktaContext'; console.warn = jest.fn(); @@ -226,18 +226,21 @@ describe('', () => { .mockImplementationOnce(() => { const oktaProps = useOktaAuth(); expect(oktaProps.authState).toBe(initialAuthState); + expect(oktaProps._authStateStatus).toBe(null); return null; }) // second call .mockImplementationOnce(() => { const oktaProps = useOktaAuth(); expect(oktaProps.authState).toBe(mockAuthStates[1]); + expect(oktaProps._authStateStatus).toBe(AUTHSTATE_STATUS.INITIALIZED); return null; }) // third call .mockImplementationOnce(() => { const oktaProps = useOktaAuth(); expect(oktaProps.authState).toBe(mockAuthStates[2]); + expect(oktaProps._authStateStatus).toBe(AUTHSTATE_STATUS.UPDATED); return null; }); @@ -248,16 +251,16 @@ describe('', () => { ); - expect(callbacks.length).toEqual(2); - expect(oktaAuth.authStateManager.subscribe).toHaveBeenCalledTimes(1); - expect(oktaAuth.start).toHaveBeenCalledTimes(1); - expect(MyComponent).toHaveBeenCalledTimes(2); - MyComponent.mockClear(); + // mock authState update after mounted act(() => { stateCount++; callbacks.map(fn => fn(mockAuthStates[stateCount])); }); - expect(MyComponent).toHaveBeenCalledTimes(1); + + expect(callbacks.length).toEqual(2); + expect(oktaAuth.authStateManager.subscribe).toHaveBeenCalledTimes(1); + expect(oktaAuth.start).toHaveBeenCalledTimes(1); + expect(MyComponent).toHaveBeenCalledTimes(3); component.unmount(); expect(oktaAuth.stop).toHaveBeenCalledTimes(1); From dba8a55c3b2a5cc7233e73bc4b7f014dfffb6198 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Wed, 29 Sep 2021 17:06:35 -0400 Subject: [PATCH 5/6] code update with new state name --- src/OktaContext.ts | 10 +++++----- src/SecureRoute.tsx | 6 +++--- src/Security.tsx | 12 ++++++------ test/jest/secureRoute.test.tsx | 21 +++++++-------------- test/jest/security.test.tsx | 8 ++++---- 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/OktaContext.ts b/src/OktaContext.ts index caf9de1b..901a9ec5 100644 --- a/src/OktaContext.ts +++ b/src/OktaContext.ts @@ -12,14 +12,14 @@ import * as React from 'react'; import { AuthState, OktaAuth } from '@okta/okta-auth-js'; -export enum AUTHSTATE_STATUS { - INITIALIZED = 'INITIALIZED', - UPDATED = 'UPDATED' +export enum OnAuthRequiredState { + Initialized = 'INITIALIZED', + Updated = 'UPDATED' } export type OnAuthRequiredFunction = ( oktaAuth: OktaAuth, - options: { state: AUTHSTATE_STATUS } + options: { state: OnAuthRequiredState } ) => Promise | void; export type OnAuthResumeFunction = () => void; @@ -29,7 +29,7 @@ export interface IOktaContext { oktaAuth: OktaAuth; authState: AuthState | null; _onAuthRequired?: OnAuthRequiredFunction; - _authStateStatus: AUTHSTATE_STATUS | null; + _onAuthRequiredState: OnAuthRequiredState | null; } const OktaContext = React.createContext(null); diff --git a/src/SecureRoute.tsx b/src/SecureRoute.tsx index 78536a86..d2cc69dd 100644 --- a/src/SecureRoute.tsx +++ b/src/SecureRoute.tsx @@ -11,7 +11,7 @@ */ import * as React from 'react'; -import { useOktaAuth, OnAuthRequiredFunction, AUTHSTATE_STATUS } from './OktaContext'; +import { useOktaAuth, OnAuthRequiredFunction, OnAuthRequiredState } from './OktaContext'; import { Route, useRouteMatch, RouteProps } from 'react-router-dom'; import { toRelativeUrl } from '@okta/okta-auth-js'; @@ -25,7 +25,7 @@ const SecureRoute: React.FC<{ oktaAuth, authState, _onAuthRequired, - _authStateStatus + _onAuthRequiredState } = useOktaAuth(); const match = useRouteMatch(routeProps); const pendingLogin = React.useRef(false); @@ -42,7 +42,7 @@ const SecureRoute: React.FC<{ oktaAuth.setOriginalUri(originalUri); const onAuthRequiredFn = onAuthRequired || _onAuthRequired; if (onAuthRequiredFn) { - await onAuthRequiredFn(oktaAuth, { state: _authStateStatus as AUTHSTATE_STATUS }); + await onAuthRequiredFn(oktaAuth, { state: _onAuthRequiredState as OnAuthRequiredState }); } else { await oktaAuth.signInWithRedirect(); } diff --git a/src/Security.tsx b/src/Security.tsx index 8df18dd0..c2e1a0db 100644 --- a/src/Security.tsx +++ b/src/Security.tsx @@ -15,7 +15,7 @@ import { AuthSdkError, AuthState, OktaAuth } from '@okta/okta-auth-js'; import OktaContext, { OnAuthRequiredFunction, RestoreOriginalUriFunction, - AUTHSTATE_STATUS + OnAuthRequiredState } from './OktaContext'; import OktaError from './OktaError'; @@ -45,7 +45,7 @@ const Security: React.FC<{ const majorVersion = oktaAuthVersion?.split('.')[0]; return majorVersion; }); - const authStateStatus = React.useRef(null); + const onAuthRequiredState = React.useRef(null); React.useEffect(() => { if (!oktaAuth || !restoreOriginalUri) { @@ -69,9 +69,9 @@ const Security: React.FC<{ // Update Security provider with latest authState const handler = (authState: AuthState) => { - authStateStatus.current = authStateStatus.current === AUTHSTATE_STATUS.INITIALIZED - ? AUTHSTATE_STATUS.UPDATED - : AUTHSTATE_STATUS.INITIALIZED; + onAuthRequiredState.current = onAuthRequiredState.current === OnAuthRequiredState.Initialized + ? OnAuthRequiredState.Updated + : OnAuthRequiredState.Initialized; setAuthState(authState); }; oktaAuth.authStateManager.subscribe(handler); @@ -107,7 +107,7 @@ const Security: React.FC<{ oktaAuth, authState, _onAuthRequired: onAuthRequired, - _authStateStatus: authStateStatus.current + _onAuthRequiredState: onAuthRequiredState.current }}> {children} diff --git a/test/jest/secureRoute.test.tsx b/test/jest/secureRoute.test.tsx index 4e2e5f11..340ff054 100644 --- a/test/jest/secureRoute.test.tsx +++ b/test/jest/secureRoute.test.tsx @@ -16,7 +16,7 @@ import { act } from 'react-dom/test-utils'; import { MemoryRouter, Route, RouteProps } from 'react-router-dom'; import SecureRoute from '../../src/SecureRoute'; import Security from '../../src/Security'; -import { AUTHSTATE_STATUS } from '../../src/OktaContext'; +import { OnAuthRequiredState } from '../../src/OktaContext'; describe('', () => { let oktaAuth; @@ -216,14 +216,9 @@ describe('', () => { it('calls onAuthRequired if provided from Security', () => { const onAuthRequired = jest.fn(); - jest.spyOn(React, 'useState') - .mockReturnValueOnce([{ isAuthenticated: false }, jest.fn()]) - .mockReturnValueOnce(['999', jest.fn()]) - .mockReturnValueOnce([AUTHSTATE_STATUS.INITIALIZED, jest.fn()]); + jest.spyOn(React, 'useRef') + .mockReturnValueOnce({ current: OnAuthRequiredState.Initialized }); - oktaAuth.authStateManager.updateAuthState = jest.fn().mockImplementation(() => { - - }), mount( @@ -233,16 +228,14 @@ describe('', () => { ); expect(oktaAuth.setOriginalUri).toHaveBeenCalled(); expect(oktaAuth.signInWithRedirect).not.toHaveBeenCalled(); - expect(onAuthRequired).toHaveBeenCalledWith(oktaAuth, { state: AUTHSTATE_STATUS.INITIALIZED }); + expect(onAuthRequired).toHaveBeenCalledWith(oktaAuth, { state: OnAuthRequiredState.Initialized }); }); it('calls onAuthRequired from SecureRoute if provide from both Security and SecureRoute', () => { const onAuthRequired1 = jest.fn(); const onAuthRequired2 = jest.fn(); - jest.spyOn(React, 'useState') - .mockReturnValueOnce([{ isAuthenticated: false }, jest.fn()]) - .mockReturnValueOnce(['999', jest.fn()]) - .mockReturnValueOnce([AUTHSTATE_STATUS.INITIALIZED, jest.fn()]); + jest.spyOn(React, 'useRef') + .mockReturnValueOnce({ current: OnAuthRequiredState.Initialized }); mount( @@ -253,7 +246,7 @@ describe('', () => { expect(oktaAuth.setOriginalUri).toHaveBeenCalled(); expect(oktaAuth.signInWithRedirect).not.toHaveBeenCalled(); expect(onAuthRequired1).not.toHaveBeenCalled(); - expect(onAuthRequired2).toHaveBeenCalledWith(oktaAuth, { state: AUTHSTATE_STATUS.INITIALIZED }); + expect(onAuthRequired2).toHaveBeenCalledWith(oktaAuth, { state: OnAuthRequiredState.Initialized }); }); }); diff --git a/test/jest/security.test.tsx b/test/jest/security.test.tsx index 3873ca3d..daabf53d 100644 --- a/test/jest/security.test.tsx +++ b/test/jest/security.test.tsx @@ -15,7 +15,7 @@ import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { MemoryRouter } from 'react-router-dom'; import Security from '../../src/Security'; -import { AUTHSTATE_STATUS, useOktaAuth } from '../../src/OktaContext'; +import { OnAuthRequiredState, useOktaAuth } from '../../src/OktaContext'; console.warn = jest.fn(); @@ -226,21 +226,21 @@ describe('', () => { .mockImplementationOnce(() => { const oktaProps = useOktaAuth(); expect(oktaProps.authState).toBe(initialAuthState); - expect(oktaProps._authStateStatus).toBe(null); + expect(oktaProps._onAuthRequiredState).toBe(null); return null; }) // second call .mockImplementationOnce(() => { const oktaProps = useOktaAuth(); expect(oktaProps.authState).toBe(mockAuthStates[1]); - expect(oktaProps._authStateStatus).toBe(AUTHSTATE_STATUS.INITIALIZED); + expect(oktaProps._onAuthRequiredState).toBe(OnAuthRequiredState.Initialized); return null; }) // third call .mockImplementationOnce(() => { const oktaProps = useOktaAuth(); expect(oktaProps.authState).toBe(mockAuthStates[2]); - expect(oktaProps._authStateStatus).toBe(AUTHSTATE_STATUS.UPDATED); + expect(oktaProps._onAuthRequiredState).toBe(OnAuthRequiredState.Updated); return null; }); From 13b21d84550c1fb1c24a8f42b48851b90d13b7e2 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Wed, 29 Sep 2021 17:08:00 -0400 Subject: [PATCH 6/6] doc update --- CHANGELOG.md | 6 ++++++ README.md | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 775292f8..95d54329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 6.3.0 + +### Features + +- [#166](https://github.com/okta/okta-react/pull/166) Adds options with `OnAuthRequiredState` to `onAuthRequired` callback, so custom authentication approach can be provided based on when authState is updated + # 6.2.0 ### Other diff --git a/README.md b/README.md index 7c4c9001..52711488 100644 --- a/README.md +++ b/README.md @@ -393,7 +393,12 @@ export default MessageList = () => { #### onAuthRequired -*(optional)* Callback function. Called when authentication is required. If this is not supplied, `okta-react` redirects to Okta. This callback will receive [oktaAuth][Okta Auth SDK] instance as the first function parameter. This is triggered when a [SecureRoute](#secureroute) is accessed without authentication. A common use case for this callback is to redirect users to a custom login route when authentication is required for a [SecureRoute](#secureroute). +*(optional)* Callback function. Called when authentication is required. If this is not supplied, `okta-react` redirects to Okta. This callback will receive [oktaAuth][Okta Auth SDK] instance as the first function parameter and an options object as the second parameter. This is triggered when a [SecureRoute](#secureroute) is accessed without authentication. A common use case for this callback is to redirect users to a custom login route when authentication is required for a [SecureRoute](#secureroute). + +- options: + - state: The state that when `authState` is updated. It can be either: + - `OnAuthRequiredState.Initialized`: when `authState` is updated during app initial load + - `OnAuthRequiredState.Updated`: when `authState` is updated with tokens auto renew process #### Example @@ -410,10 +415,13 @@ const oktaAuth = new OktaAuth({ export default App = () => { const history = useHistory(); - const customAuthHandler = (oktaAuth) => { - // Redirect to the /login page that has a CustomLoginComponent - // This example is specific to React-Router - history.push('/login'); + const customAuthHandler = (oktaAuth, options) => { + // Provide custom auth approach based on option.state + if (option.state === OnAuthRequiredState.Initialized) { + // ... + } else if (option.state === OnAuthRequiredState.Updated) { + // ... + } }; const restoreOriginalUri = async (_oktaAuth, originalUri) => {