diff --git a/.changeset/fix-init-singleton-auth-reconfigure.md b/.changeset/fix-init-singleton-auth-reconfigure.md new file mode 100644 index 00000000000..e164a8b8ce0 --- /dev/null +++ b/.changeset/fix-init-singleton-auth-reconfigure.md @@ -0,0 +1,5 @@ +--- +'aws-amplify': patch +--- + +fix(aws-amplify): refresh default Cognito auth config on DefaultAmplify reconfigure. diff --git a/packages/aws-amplify/__tests__/initSingleton.integration.test.ts b/packages/aws-amplify/__tests__/initSingleton.integration.test.ts new file mode 100644 index 00000000000..46352c48395 --- /dev/null +++ b/packages/aws-amplify/__tests__/initSingleton.integration.test.ts @@ -0,0 +1,103 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Integration-style tests using the real @aws-amplify/core Amplify singleton + * (initSingleton.test.ts mocks core). + */ +import { Amplify as CoreAmplify, ResourcesConfig } from '@aws-amplify/core'; + +import { cognitoUserPoolsTokenProvider } from '../src/auth/cognito'; +import { Amplify as DefaultAmplify } from '../src'; + +const poolAConfig: ResourcesConfig = { + Auth: { + Cognito: { + userPoolClientId: 'client-a', + userPoolId: 'pool-a', + }, + }, +}; + +const poolBConfig: ResourcesConfig = { + Auth: { + Cognito: { + userPoolClientId: 'client-b', + userPoolId: 'pool-b', + }, + }, +}; + +describe('DefaultAmplify.configure integration', () => { + let setAuthConfigSpy: jest.SpyInstance; + let setKeyValueStorageSpy: jest.SpyInstance; + + beforeEach(() => { + CoreAmplify.libraryOptions = {}; + CoreAmplify.resourcesConfig = {}; + setAuthConfigSpy = jest.spyOn( + cognitoUserPoolsTokenProvider, + 'setAuthConfig', + ); + setKeyValueStorageSpy = jest.spyOn( + cognitoUserPoolsTokenProvider, + 'setKeyValueStorage', + ); + }); + + afterEach(() => { + setAuthConfigSpy.mockRestore(); + setKeyValueStorageSpy.mockRestore(); + CoreAmplify.libraryOptions = {}; + CoreAmplify.resourcesConfig = {}; + }); + + it('refreshes Cognito auth config on reconfigure with partial libraryOptions', () => { + DefaultAmplify.configure(poolAConfig); + + expect(setAuthConfigSpy).toHaveBeenCalledWith(poolAConfig.Auth); + + setAuthConfigSpy.mockClear(); + setKeyValueStorageSpy.mockClear(); + + DefaultAmplify.configure(poolBConfig, { ssr: false }); + + expect(setAuthConfigSpy).toHaveBeenCalledWith(poolBConfig.Auth); + expect(setKeyValueStorageSpy).toHaveBeenCalled(); + expect(CoreAmplify.getConfig().Auth?.Cognito?.userPoolClientId).toBe( + 'client-b', + ); + }); + + it('passes through when libraryOptions.Auth is provided', () => { + DefaultAmplify.configure(poolAConfig); + + setAuthConfigSpy.mockClear(); + + DefaultAmplify.configure(poolBConfig, { + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: + CoreAmplify.libraryOptions.Auth!.credentialsProvider!, + }, + }); + + expect(setAuthConfigSpy).not.toHaveBeenCalled(); + expect(CoreAmplify.getConfig().Auth?.Cognito?.userPoolClientId).toBe( + 'client-b', + ); + }); + + it('refreshes Cognito auth config when only resource config is passed', () => { + DefaultAmplify.configure(poolAConfig); + + setAuthConfigSpy.mockClear(); + setKeyValueStorageSpy.mockClear(); + + DefaultAmplify.configure(poolBConfig); + + expect(setAuthConfigSpy).toHaveBeenCalledWith(poolBConfig.Auth); + expect(setKeyValueStorageSpy).toHaveBeenCalled(); + expect(CoreAmplify.getConfig().Auth?.Cognito?.userPoolId).toBe('pool-b'); + }); +}); diff --git a/packages/aws-amplify/__tests__/initSingleton.test.ts b/packages/aws-amplify/__tests__/initSingleton.test.ts index 5d021b36743..0e6ad875a50 100644 --- a/packages/aws-amplify/__tests__/initSingleton.test.ts +++ b/packages/aws-amplify/__tests__/initSingleton.test.ts @@ -246,9 +246,10 @@ describe('initSingleton (DefaultAmplify)', () => { }); describe('when ResourcesConfig.Auth is defined', () => { - it('should just configure with the provided config and options when libraryOptions.Auth is defined', () => { + it('should pass through when libraryOptions.Auth is defined', () => { + const customTokenProvider = { getTokens: jest.fn() }; const libraryOptions = { - Auth: { tokenProvider: { getTokens: jest.fn() } }, + Auth: { tokenProvider: customTokenProvider }, }; Amplify.configure(mockResourceConfig, libraryOptions); @@ -256,6 +257,9 @@ describe('initSingleton (DefaultAmplify)', () => { mockResourceConfig, libraryOptions, ); + expect( + mockCognitoUserPoolsTokenProviderSetAuthConfig, + ).not.toHaveBeenCalled(); }); describe('when the singleton libraryOptions have not yet been configured with Auth', () => { @@ -322,13 +326,13 @@ describe('initSingleton (DefaultAmplify)', () => { }; }); - it('should preserve current auth providers (default or otherwise) and configure provider with a new CookieStorage instance', () => { + it('should refresh default Cognito auth config and configure provider with a new CookieStorage instance on reconfigure', () => { const libraryOptions = { ssr: true }; Amplify.configure(mockResourceConfig, libraryOptions); expect( mockCognitoUserPoolsTokenProviderSetAuthConfig, - ).not.toHaveBeenCalled(); + ).toHaveBeenCalledWith(mockResourceConfig.Auth); expect(MockCookieStorage).toHaveBeenCalledWith({ sameSite: 'lax' }); expect( mockCognitoUserPoolsTokenProviderSetKeyValueStorage, @@ -336,32 +340,39 @@ describe('initSingleton (DefaultAmplify)', () => { expect(mockAmplifySingletonConfigure).toHaveBeenCalledWith( mockResourceConfig, { - Auth: AmplifySingleton.libraryOptions.Auth, ...libraryOptions, + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: + mockCognitoAWSCredentialsAndIdentityIdProviderInstance, + }, }, ); }); - it('should preserve current auth providers (default or otherwise) and configure provider with defaultStorage', () => { + it('should refresh default Cognito auth config and configure provider with defaultStorage on reconfigure', () => { const libraryOptions = { ssr: false }; Amplify.configure(mockResourceConfig, libraryOptions); expect( mockCognitoUserPoolsTokenProviderSetAuthConfig, - ).not.toHaveBeenCalled(); + ).toHaveBeenCalledWith(mockResourceConfig.Auth); expect( mockCognitoUserPoolsTokenProviderSetKeyValueStorage, ).toHaveBeenCalledWith(defaultStorage); expect(mockAmplifySingletonConfigure).toHaveBeenCalledWith( mockResourceConfig, { - Auth: AmplifySingleton.libraryOptions.Auth, ...libraryOptions, + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: cognitoCredentialsProvider, + }, }, ); }); - it('should preserve current auth providers (default or otherwise)', () => { + it('should refresh default Cognito auth config when reconfiguring with non-Auth libraryOptions', () => { const libraryOptions = { Storage: { S3: { isObjectLockEnabled: true } }, }; @@ -369,24 +380,66 @@ describe('initSingleton (DefaultAmplify)', () => { expect( mockCognitoUserPoolsTokenProviderSetAuthConfig, - ).not.toHaveBeenCalled(); + ).toHaveBeenCalledWith(mockResourceConfig.Auth); expect( mockCognitoUserPoolsTokenProviderSetKeyValueStorage, - ).not.toHaveBeenCalled(); + ).toHaveBeenCalledWith(defaultStorage); expect(mockAmplifySingletonConfigure).toHaveBeenCalledWith( mockResourceConfig, { - Auth: AmplifySingleton.libraryOptions.Auth, ...libraryOptions, + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: cognitoCredentialsProvider, + }, }, ); }); - it('should just configure without touching libraryOptions', () => { + it('should refresh default Cognito auth config when reconfiguring with resource config only', () => { Amplify.configure(mockResourceConfig); + expect( + mockCognitoUserPoolsTokenProviderSetAuthConfig, + ).toHaveBeenCalledWith(mockResourceConfig.Auth); + expect( + mockCognitoUserPoolsTokenProviderSetKeyValueStorage, + ).toHaveBeenCalledWith(defaultStorage); expect(mockAmplifySingletonConfigure).toHaveBeenCalledWith( mockResourceConfig, + { + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: cognitoCredentialsProvider, + }, + }, + ); + }); + + it('should pass through when libraryOptions.Auth is provided on reconfigure', () => { + const updatedResourceConfig: ResourcesConfig = { + Auth: { + Cognito: { + userPoolClientId: 'newClientId', + userPoolId: 'newPoolId', + }, + }, + }; + const libraryOptions = { + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: cognitoCredentialsProvider, + }, + }; + + Amplify.configure(updatedResourceConfig, libraryOptions); + + expect( + mockCognitoUserPoolsTokenProviderSetAuthConfig, + ).not.toHaveBeenCalled(); + expect(mockAmplifySingletonConfigure).toHaveBeenCalledWith( + updatedResourceConfig, + libraryOptions, ); }); }); diff --git a/packages/aws-amplify/src/initSingleton.ts b/packages/aws-amplify/src/initSingleton.ts index 6168cc25b51..bfcc9960610 100644 --- a/packages/aws-amplify/src/initSingleton.ts +++ b/packages/aws-amplify/src/initSingleton.ts @@ -48,66 +48,25 @@ export const DefaultAmplify = { ) : cognitoCredentialsProvider; - // If no Auth config is provided, no special handling will be required, configure as is. - // Otherwise, we can assume an Auth config is provided from here on. - if (!resolvedResourceConfig.Auth) { + if (!resolvedResourceConfig.Auth || libraryOptions?.Auth) { Amplify.configure(resolvedResourceConfig, libraryOptions); return; } - // If Auth options are provided, always just configure as is. - // Otherwise, we can assume no Auth libraryOptions were provided from here on. - if (libraryOptions?.Auth) { - Amplify.configure(resolvedResourceConfig, libraryOptions); - - return; - } - - // If no Auth libraryOptions were previously configured, then always add default providers. - if (!Amplify.libraryOptions.Auth) { - cognitoUserPoolsTokenProvider.setAuthConfig(resolvedResourceConfig.Auth); - cognitoUserPoolsTokenProvider.setKeyValueStorage( - // TODO: allow configure with a public interface - resolvedKeyValueStorage, - ); - - Amplify.configure(resolvedResourceConfig, { - ...libraryOptions, - Auth: { - tokenProvider: cognitoUserPoolsTokenProvider, - credentialsProvider: resolvedCredentialsProvider, - }, - }); - - return; - } - - // At this point, Auth libraryOptions would have been previously configured and no overriding - // Auth options were given, so we should preserve the currently configured Auth libraryOptions. - if (libraryOptions) { - const authLibraryOptions = Amplify.libraryOptions.Auth; - // If ssr is provided through libraryOptions, we should respect the intentional reconfiguration. - if (libraryOptions.ssr !== undefined) { - cognitoUserPoolsTokenProvider.setKeyValueStorage( - // TODO: allow configure with a public interface - resolvedKeyValueStorage, - ); - - authLibraryOptions.credentialsProvider = resolvedCredentialsProvider; - } - - Amplify.configure(resolvedResourceConfig, { - Auth: authLibraryOptions, - ...libraryOptions, - }); - - return; - } + cognitoUserPoolsTokenProvider.setAuthConfig(resolvedResourceConfig.Auth); + cognitoUserPoolsTokenProvider.setKeyValueStorage( + // TODO: allow configure with a public interface + resolvedKeyValueStorage, + ); - // Finally, if there were no libraryOptions given at all, we should simply not touch the currently - // configured libraryOptions. - Amplify.configure(resolvedResourceConfig); + Amplify.configure(resolvedResourceConfig, { + ...libraryOptions, + Auth: { + tokenProvider: cognitoUserPoolsTokenProvider, + credentialsProvider: resolvedCredentialsProvider, + }, + }); }, /** * Returns the {@link ResourcesConfig} object passed in as the `resourceConfig` parameter when calling