From 2b71e23450d7079232260a219164368cfed31a2d Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Nov 2024 16:00:30 -0500 Subject: [PATCH] update to include any params available in oauth authorize --- packages/core/src/SDKConfig/SDKConfig.ts | 5 ++++ packages/core/src/SDKCore/SDKCore.ts | 1 + packages/core/src/UrlHelper/UrlHelper.test.ts | 11 +++++++ packages/core/src/UrlHelper/UrlHelper.ts | 21 ++++++++++--- packages/core/src/UrlHelper/UrlHelperTypes.ts | 2 ++ packages/sdk-react/CHANGES.md | 4 +++ packages/sdk-react/package.json | 4 +-- .../providers/FusionAuthProvider.test.tsx | 30 ++++++++++++++++++- .../providers/FusionAuthProvider.tsx | 2 ++ .../providers/FusionAuthProviderConfig.ts | 5 ++++ .../src/testing-tools/mocks/testConfig.ts | 9 ++++++ 11 files changed, 87 insertions(+), 7 deletions(-) diff --git a/packages/core/src/SDKConfig/SDKConfig.ts b/packages/core/src/SDKConfig/SDKConfig.ts index 2d2db95..32ef2fb 100644 --- a/packages/core/src/SDKConfig/SDKConfig.ts +++ b/packages/core/src/SDKConfig/SDKConfig.ts @@ -24,6 +24,11 @@ export interface SDKConfig { */ scope?: string; + /** + * Additional params passed to loginPath typically `/app/login/`, which redirects to `/oauth2/authorize`. Example of this might be loginParams = [{idp_hint:'44449786-3dff-42a6-aac6-1f1ceecb6c46'}] or any params found at https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/endpoints + */ + authParams?: { [key: string]: any }[]; + /** * The redirect URI for post-logout. Defaults the provided `redirectUri`. */ diff --git a/packages/core/src/SDKCore/SDKCore.ts b/packages/core/src/SDKCore/SDKCore.ts index d93f553..cdc1ec7 100644 --- a/packages/core/src/SDKCore/SDKCore.ts +++ b/packages/core/src/SDKCore/SDKCore.ts @@ -20,6 +20,7 @@ export class SDKCore { clientId: config.clientId, redirectUri: config.redirectUri, scope: config.scope, + authParams: config.authParams, mePath: config.mePath, loginPath: config.loginPath, registerPath: config.registerPath, diff --git a/packages/core/src/UrlHelper/UrlHelper.test.ts b/packages/core/src/UrlHelper/UrlHelper.test.ts index 6deed75..1775b63 100644 --- a/packages/core/src/UrlHelper/UrlHelper.test.ts +++ b/packages/core/src/UrlHelper/UrlHelper.test.ts @@ -8,6 +8,7 @@ describe('UrlHelper', () => { clientId: 'abc123', redirectUri: 'http://my-client', scope: 'openid email profile offline_access', + authParams: [{ idp_hint: '44449786-3dff-42a6-aac6-1f1ceecb6c46' }], postLogoutRedirectUri: 'http://example.com', }; @@ -31,6 +32,16 @@ describe('UrlHelper', () => { expect(loginUrl.searchParams.get('state')).toBe(stateValue); }); + it('login url authParams', () => { + const stateValue = 'login-state-value'; + const loginUrl = urlHelper.getLoginUrl(stateValue); + expect(loginUrl.origin).toBe(config.serverUrl); + expect(loginUrl.pathname).toBe('/app/login/'); + expect(loginUrl.searchParams.get('idp_hint')).toBe( + config.authParams?.at(0)?.idp_hint, + ); + }); + it('register url', () => { const stateValue = 'register-state-value'; const registerUrl = urlHelper.getRegisterUrl(stateValue); diff --git a/packages/core/src/UrlHelper/UrlHelper.ts b/packages/core/src/UrlHelper/UrlHelper.ts index 0d9c8c7..388950a 100644 --- a/packages/core/src/UrlHelper/UrlHelper.ts +++ b/packages/core/src/UrlHelper/UrlHelper.ts @@ -6,6 +6,7 @@ export class UrlHelper { clientId: string; redirectUri: string; scope?: string; + authParams?: { [key: string]: any }[]; mePath: string; loginPath: string; @@ -19,6 +20,7 @@ export class UrlHelper { this.clientId = config.clientId; this.redirectUri = config.redirectUri; this.scope = config.scope; + this.authParams = config.authParams; this.postLogoutRedirectUri = config.postLogoutRedirectUri; this.mePath = config.mePath ?? '/app/me/'; @@ -37,6 +39,7 @@ export class UrlHelper { client_id: this.clientId, redirect_uri: this.redirectUri, scope: this.scope, + authParams: this.authParams, state, }); } @@ -46,6 +49,7 @@ export class UrlHelper { client_id: this.clientId, redirect_uri: this.redirectUri, scope: this.scope, + authParams: this.authParams, state, }); } @@ -85,13 +89,22 @@ export class UrlHelper { params: UrlHelperQueryParams, ): URLSearchParams { const urlSearchParams = new URLSearchParams(); + this.appendParams(params, urlSearchParams); + return urlSearchParams; + } + private appendParams( + params: Record, // or a more specific type if known + urlSearchParams: URLSearchParams, + ): void { Object.entries(params).forEach(([key, value]) => { - if (value) { - urlSearchParams.append(key, value); + if ((value && typeof value === 'object') || Array.isArray(value)) { + // Recursively handle nested objects + this.appendParams(value, urlSearchParams); + } else if (value !== undefined && value !== null) { + // Append primitive values + urlSearchParams.append(key, String(value)); } }); - - return urlSearchParams; } } diff --git a/packages/core/src/UrlHelper/UrlHelperTypes.ts b/packages/core/src/UrlHelper/UrlHelperTypes.ts index 8f40cc0..5f1dd4d 100644 --- a/packages/core/src/UrlHelper/UrlHelperTypes.ts +++ b/packages/core/src/UrlHelper/UrlHelperTypes.ts @@ -12,6 +12,7 @@ export type UrlHelperConfig = Pick< | 'logoutPath' | 'tokenRefreshPath' | 'scope' + | 'authParams' | 'postLogoutRedirectUri' >; @@ -21,5 +22,6 @@ export type UrlHelperQueryParams = { redirect_uri?: string; post_logout_redirect_uri?: string; scope?: string; + authParams?: { [key: string]: any }[]; state?: string; }; diff --git a/packages/sdk-react/CHANGES.md b/packages/sdk-react/CHANGES.md index a545dbd..04b7115 100644 --- a/packages/sdk-react/CHANGES.md +++ b/packages/sdk-react/CHANGES.md @@ -1,5 +1,9 @@ fusionauth-react-sdk Changes +Changes in 2.5.0 + +- Allows for available paramaters to be passed for the `/oauth2/authorize` redirect that occurs after the hosted backend login from `/app/login`. Addresses issue [164](https://github.com/FusionAuth/fusionauth-javascript-sdk/issues/164) + Changes in 2.4.3 - Correctly address issues with config and `isLoggedIn` in [169](https://github.com/FusionAuth/fusionauth-javascript-sdk/pull/169) diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 6407699..fcbb47f 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -1,6 +1,6 @@ { "name": "@fusionauth/react-sdk", - "version": "2.4.3", + "version": "2.5.0", "private": false, "description": "FusionAuth solves the problem of building essential security without adding risk or distracting from your primary application", "type": "module", @@ -63,4 +63,4 @@ "vite-plugin-dts": "^3.7.3", "vitest": "^1.3.1" } -} +} \ No newline at end of file diff --git a/packages/sdk-react/src/components/providers/FusionAuthProvider.test.tsx b/packages/sdk-react/src/components/providers/FusionAuthProvider.test.tsx index ca66076..e364035 100644 --- a/packages/sdk-react/src/components/providers/FusionAuthProvider.test.tsx +++ b/packages/sdk-react/src/components/providers/FusionAuthProvider.test.tsx @@ -14,7 +14,10 @@ import { removeAt_expCookie, mockWindowLocation, } from '@fusionauth-sdk/core'; -import { TEST_CONFIG } from '#testing-tools/mocks/testConfig'; +import { + TEST_CONFIG, + TEST_AUTHPARAM_CONFIG, +} from '#testing-tools/mocks/testConfig'; function renderWithWrapper(config: FusionAuthProviderConfig) { return renderHook(() => useFusionAuth(), { @@ -49,6 +52,31 @@ describe('FusionAuthProvider', () => { expect(mockedLocation.assign).toHaveBeenCalledWith(expectedUrl); }); + test('Redirects to the correct login url with idp_hint', () => { + const mockedLocation = mockWindowLocation(vi); + + const { result } = renderWithWrapper(TEST_AUTHPARAM_CONFIG); + + const stateValue = 'state-value'; + result.current.startLogin(stateValue); + + const expectedUrl = new URL(TEST_AUTHPARAM_CONFIG.serverUrl); + expectedUrl.pathname = '/app/login/'; + expectedUrl.searchParams.set('client_id', TEST_AUTHPARAM_CONFIG.clientId); + expectedUrl.searchParams.set( + 'redirect_uri', + TEST_AUTHPARAM_CONFIG.redirectUri, + ); + expectedUrl.searchParams.set('scope', TEST_AUTHPARAM_CONFIG.scope!); + expectedUrl.searchParams.set( + 'idp_hint', + TEST_AUTHPARAM_CONFIG?.authParams?.at(0)?.idp_hint, + ); + expectedUrl.searchParams.set('state', stateValue); + + expect(mockedLocation.assign).toHaveBeenCalledWith(expectedUrl); + }); + test('Redirects to the correct logout url', () => { const mockedLocation = mockWindowLocation(vi); diff --git a/packages/sdk-react/src/components/providers/FusionAuthProvider.tsx b/packages/sdk-react/src/components/providers/FusionAuthProvider.tsx index 327a580..70a8f1f 100644 --- a/packages/sdk-react/src/components/providers/FusionAuthProvider.tsx +++ b/packages/sdk-react/src/components/providers/FusionAuthProvider.tsx @@ -28,6 +28,7 @@ function FusionAuthProvider( clientId: props.clientId, redirectUri: props.redirectUri, scope: props.scope, + authParams: props.authParams, postLogoutRedirectUri: props.postLogoutRedirectUri, shouldAutoRefresh: props.shouldAutoRefresh, shouldAutoFetchUserInfo: props.shouldAutoFetchUserInfo, @@ -46,6 +47,7 @@ function FusionAuthProvider( props.clientId, props.redirectUri, props.scope, + props.authParams, props.postLogoutRedirectUri, props.shouldAutoRefresh, props.shouldAutoFetchUserInfo, diff --git a/packages/sdk-react/src/components/providers/FusionAuthProviderConfig.ts b/packages/sdk-react/src/components/providers/FusionAuthProviderConfig.ts index a5ffc6d..6161bb3 100644 --- a/packages/sdk-react/src/components/providers/FusionAuthProviderConfig.ts +++ b/packages/sdk-react/src/components/providers/FusionAuthProviderConfig.ts @@ -24,6 +24,11 @@ export interface FusionAuthProviderConfig { */ scope?: string; + /** + * Additional params passed to loginPath typically `/app/login/`, which redirects to `/oauth2/authorize`. Example of this might be loginParams = [{idp_hint:'44449786-3dff-42a6-aac6-1f1ceecb6c46'}] or any params found at https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/endpoints + */ + authParams?: { [key: string]: any }[]; + /** * The redirect URI for post-logout. Defaults the provided `redirectUri`. */ diff --git a/packages/sdk-react/src/testing-tools/mocks/testConfig.ts b/packages/sdk-react/src/testing-tools/mocks/testConfig.ts index 733607e..60b354a 100644 --- a/packages/sdk-react/src/testing-tools/mocks/testConfig.ts +++ b/packages/sdk-react/src/testing-tools/mocks/testConfig.ts @@ -7,3 +7,12 @@ export const TEST_CONFIG: FusionAuthProviderConfig = { scope: 'openid email profile offline_access', postLogoutRedirectUri: 'http://localhost', }; + +export const TEST_AUTHPARAM_CONFIG: FusionAuthProviderConfig = { + clientId: '85a03867-dccf-4882-adde-1a79aeec50df', + serverUrl: 'http://localhost:9000', + redirectUri: 'http://localhost', + scope: 'openid email profile offline_access', + authParams: [{ idp_hint: '44449786-3dff-42a6-aac6-1f1ceecb6c46' }], + postLogoutRedirectUri: 'http://localhost', +};