diff --git a/packages/auth/__tests__/providers/cognito/credentialsProvider.test.ts b/packages/auth/__tests__/providers/cognito/credentialsProvider.test.ts index 3390e14a052..15a9142b542 100644 --- a/packages/auth/__tests__/providers/cognito/credentialsProvider.test.ts +++ b/packages/auth/__tests__/providers/cognito/credentialsProvider.test.ts @@ -2,9 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { - GetCredentialsForIdentityOutput, ResourcesConfig, - getCredentialsForIdentity, + createGetCredentialsForIdentityClient, sharedInMemoryStorage, } from '@aws-amplify/core'; @@ -18,7 +17,7 @@ import { authAPITestParams } from './testUtils/authApiTestParams'; jest.mock('@aws-amplify/core', () => ({ ...jest.requireActual('@aws-amplify/core'), - getCredentialsForIdentity: jest.fn(), + createGetCredentialsForIdentityClient: jest.fn(), })); jest.mock( @@ -63,24 +62,38 @@ const disallowGuestAccessConfig: ResourcesConfig = { }, }; -const credentialsForIdentityIdSpy = getCredentialsForIdentity as jest.Mock; +const mockCreateGetIdentityForIdentityClient = jest.mocked( + createGetCredentialsForIdentityClient, +); + +const mockGetCredentialsForIdentity: jest.MockedFunction< + ReturnType +> = jest.fn( + async (_config, _params) => authAPITestParams.CredentialsForIdentityIdResult, +); describe('Guest Credentials', () => { let cognitoCredentialsProvider: CognitoAWSCredentialsAndIdentityIdProvider; + beforeAll(() => { + mockCreateGetIdentityForIdentityClient.mockReturnValue( + mockGetCredentialsForIdentity, + ); + }); + describe('Happy Path Cases:', () => { beforeEach(() => { cognitoCredentialsProvider = new CognitoAWSCredentialsAndIdentityIdProvider( new DefaultIdentityIdStore(sharedInMemoryStorage), ); - credentialsForIdentityIdSpy.mockImplementationOnce(async () => { - return authAPITestParams.CredentialsForIdentityIdResult as GetCredentialsForIdentityOutput; + mockGetCredentialsForIdentity.mockImplementationOnce(async () => { + return authAPITestParams.CredentialsForIdentityIdResult; }); }); afterEach(() => { cognitoCredentialsProvider.clearCredentials(); - credentialsForIdentityIdSpy?.mockReset(); + mockGetCredentialsForIdentity?.mockReset(); }); test('Should call identityIdClient with no logins to obtain guest creds', async () => { const res = await cognitoCredentialsProvider.getCredentialsAndIdentityId({ @@ -92,8 +105,8 @@ describe('Guest Credentials', () => { .AccessKeyId, ); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledWith( + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledWith( { region: 'us-east-1' }, { IdentityId: 'identity-id-test' }, ); @@ -107,7 +120,7 @@ describe('Guest Credentials', () => { authenticated: false, authConfig: validAuthConfig.Auth!, }); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); const res = await cognitoCredentialsProvider.getCredentialsAndIdentityId({ authenticated: false, authConfig: validAuthConfig.Auth!, @@ -117,7 +130,7 @@ describe('Guest Credentials', () => { .AccessKeyId, ); // expecting to be called only once becasue in-memory creds should be returned - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); }); }); @@ -127,8 +140,8 @@ describe('Guest Credentials', () => { new CognitoAWSCredentialsAndIdentityIdProvider( new DefaultIdentityIdStore(sharedInMemoryStorage), ); - credentialsForIdentityIdSpy.mockImplementationOnce(async () => { - return authAPITestParams.NoAccessKeyCredentialsForIdentityIdResult as GetCredentialsForIdentityOutput; + mockGetCredentialsForIdentity.mockImplementationOnce(async () => { + return authAPITestParams.NoAccessKeyCredentialsForIdentityIdResult; }); }); @@ -136,7 +149,7 @@ describe('Guest Credentials', () => { cognitoCredentialsProvider.clearCredentials(); }); afterAll(() => { - credentialsForIdentityIdSpy?.mockReset(); + mockGetCredentialsForIdentity?.mockReset(); }); test('Should not throw AuthError when allowGuestAccess is false in the config', async () => { expect( @@ -165,13 +178,13 @@ describe('Primary Credentials', () => { new CognitoAWSCredentialsAndIdentityIdProvider( new DefaultIdentityIdStore(sharedInMemoryStorage), ); - credentialsForIdentityIdSpy.mockImplementation(async () => { - return authAPITestParams.CredentialsForIdentityIdResult as GetCredentialsForIdentityOutput; + mockGetCredentialsForIdentity.mockImplementation(async () => { + return authAPITestParams.CredentialsForIdentityIdResult; }); }); afterEach(() => { cognitoCredentialsProvider.clearCredentials(); - credentialsForIdentityIdSpy?.mockReset(); + mockGetCredentialsForIdentity?.mockReset(); }); test('Should call identityIdClient with the logins map to obtain primary creds', async () => { const res = await cognitoCredentialsProvider.getCredentialsAndIdentityId({ @@ -184,7 +197,7 @@ describe('Primary Credentials', () => { .AccessKeyId, ); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); }); test('in-memory primary creds are returned if not expired and not past TTL', async () => { await cognitoCredentialsProvider.getCredentialsAndIdentityId({ @@ -192,13 +205,13 @@ describe('Primary Credentials', () => { authConfig: validAuthConfig.Auth!, tokens: authAPITestParams.ValidAuthTokens, }); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledWith( + expect(mockGetCredentialsForIdentity).toHaveBeenCalledWith( { region: authAPITestParams.CredentialsClientRequest.region, }, authAPITestParams.CredentialsClientRequest.withValidAuthToken, ); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); const res = await cognitoCredentialsProvider.getCredentialsAndIdentityId({ authenticated: true, @@ -210,7 +223,7 @@ describe('Primary Credentials', () => { .AccessKeyId, ); // expecting to be called only once becasue in-memory creds should be returned - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); }); test('Should get new credentials when tokens have changed', async () => { await cognitoCredentialsProvider.getCredentialsAndIdentityId({ @@ -218,26 +231,26 @@ describe('Primary Credentials', () => { authConfig: validAuthConfig.Auth!, tokens: authAPITestParams.ValidAuthTokens, }); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledWith( + expect(mockGetCredentialsForIdentity).toHaveBeenCalledWith( { region: authAPITestParams.CredentialsClientRequest.region, }, authAPITestParams.CredentialsClientRequest.withValidAuthToken, ); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(1); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(1); await cognitoCredentialsProvider.getCredentialsAndIdentityId({ authenticated: true, authConfig: validAuthConfig.Auth!, tokens: authAPITestParams.NewValidAuthTokens, }); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledWith( + expect(mockGetCredentialsForIdentity).toHaveBeenCalledWith( { region: authAPITestParams.CredentialsClientRequest.region, }, authAPITestParams.CredentialsClientRequest.withNewValidAuthToken, ); - expect(credentialsForIdentityIdSpy).toHaveBeenCalledTimes(2); + expect(mockGetCredentialsForIdentity).toHaveBeenCalledTimes(2); }); }); describe('Error Path Cases:', () => { @@ -251,11 +264,11 @@ describe('Primary Credentials', () => { cognitoCredentialsProvider.clearCredentials(); }); afterAll(() => { - credentialsForIdentityIdSpy?.mockReset(); + mockGetCredentialsForIdentity?.mockReset(); }); test('Should throw AuthError if either Credentials, accessKeyId or secretKey is absent in the response', async () => { - credentialsForIdentityIdSpy.mockImplementationOnce(async () => { - return authAPITestParams.NoAccessKeyCredentialsForIdentityIdResult as GetCredentialsForIdentityOutput; + mockGetCredentialsForIdentity.mockImplementationOnce(async () => { + return authAPITestParams.NoAccessKeyCredentialsForIdentityIdResult; }); expect( cognitoCredentialsProvider.getCredentialsAndIdentityId({ @@ -264,9 +277,9 @@ describe('Primary Credentials', () => { tokens: authAPITestParams.ValidAuthTokens, }), ).rejects.toThrow(AuthError); - credentialsForIdentityIdSpy.mockClear(); - credentialsForIdentityIdSpy.mockImplementationOnce(async () => { - return authAPITestParams.NoCredentialsForIdentityIdResult as GetCredentialsForIdentityOutput; + mockGetCredentialsForIdentity.mockClear(); + mockGetCredentialsForIdentity.mockImplementationOnce(async () => { + return authAPITestParams.NoCredentialsForIdentityIdResult; }); expect( cognitoCredentialsProvider.getCredentialsAndIdentityId({ @@ -275,9 +288,9 @@ describe('Primary Credentials', () => { tokens: authAPITestParams.ValidAuthTokens, }), ).rejects.toThrow(AuthError); - credentialsForIdentityIdSpy.mockClear(); - credentialsForIdentityIdSpy.mockImplementationOnce(async () => { - return authAPITestParams.NoSecretKeyInCredentialsForIdentityIdResult as GetCredentialsForIdentityOutput; + mockGetCredentialsForIdentity.mockClear(); + mockGetCredentialsForIdentity.mockImplementationOnce(async () => { + return authAPITestParams.NoSecretKeyInCredentialsForIdentityIdResult; }); expect( cognitoCredentialsProvider.getCredentialsAndIdentityId({ diff --git a/packages/auth/__tests__/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.test.ts b/packages/auth/__tests__/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.test.ts new file mode 100644 index 00000000000..cd79f563af3 --- /dev/null +++ b/packages/auth/__tests__/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.test.ts @@ -0,0 +1,57 @@ +import { AmplifyUrl } from '@aws-amplify/core/internals/utils'; +import { cognitoIdentityPoolEndpointResolver } from '@aws-amplify/core'; + +import { createCognitoIdentityPoolEndpointResolver } from '../../../../src/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver'; + +jest.mock('@aws-amplify/core'); + +const mockCognitoIdentityPoolEndpointResolver = jest.mocked( + cognitoIdentityPoolEndpointResolver, +); + +describe('createCognitoIdentityPoolEndpointResolver()', () => { + afterEach(() => { + mockCognitoIdentityPoolEndpointResolver.mockClear(); + }); + + describe('creating a resolver with overrideEndpoint as `undefined`', () => { + const resolver = createCognitoIdentityPoolEndpointResolver({ + endpointOverride: undefined, + }); + + it('invokes cognitoUserPoolEndpointResolver with the expected region', () => { + const expectedReturningUrl = { + url: new AmplifyUrl( + 'https://cognito-identity.us-west-2.amazonaws.com/', + ), + }; + mockCognitoIdentityPoolEndpointResolver.mockReturnValueOnce( + expectedReturningUrl, + ); + + const expectedRegion = 'us-west-2'; + const { url } = resolver({ region: expectedRegion }); + + expect(mockCognitoIdentityPoolEndpointResolver).toHaveBeenCalledWith({ + region: expectedRegion, + }); + expect(url).toStrictEqual(expectedReturningUrl.url); + }); + }); + + describe('creating a resolver with overrideEndpoint', () => { + const endpointOverride = 'https://cognito-identity.example.com'; + const resolver = createCognitoIdentityPoolEndpointResolver({ + endpointOverride, + }); + + it('returns the endpoint override', () => { + const expectedRegion = 'us-west-2'; + const { url } = resolver({ region: expectedRegion }); + expect(mockCognitoIdentityPoolEndpointResolver).not.toHaveBeenCalled(); + expect(url).toStrictEqual( + new AmplifyUrl('https://cognito-identity.example.com'), + ); + }); + }); +}); diff --git a/packages/auth/__tests__/providers/cognito/identityIdProvider.test.ts b/packages/auth/__tests__/providers/cognito/identityIdProvider.test.ts index 2b8620c680d..30d347ee7d9 100644 --- a/packages/auth/__tests__/providers/cognito/identityIdProvider.test.ts +++ b/packages/auth/__tests__/providers/cognito/identityIdProvider.test.ts @@ -1,11 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Amplify, Identity, ResourcesConfig, getId } from '@aws-amplify/core'; import { - GetIdInput, - GetIdOutput, -} from '@aws-amplify/core/internals/aws-clients/cognitoIdentity'; + Amplify, + Identity, + ResourcesConfig, + createGetIdClient, +} from '@aws-amplify/core'; import { CognitoIdentityPoolConfig } from '@aws-amplify/core/internals/utils'; import { DefaultIdentityIdStore } from '../../../src/providers/cognito/credentialsProvider/IdentityIdStore'; @@ -15,9 +16,8 @@ import { authAPITestParams } from './testUtils/authApiTestParams'; jest.mock('@aws-amplify/core', () => ({ ...jest.requireActual('@aws-amplify/core'), - getId: jest.fn(), + createGetIdClient: jest.fn(), })); -jest.mock('@aws-amplify/core/internals/aws-clients/cognitoIdentity'); jest.mock('../../../src/providers/cognito/credentialsProvider/IdentityIdStore'); const ampConfig: ResourcesConfig = { @@ -30,7 +30,7 @@ const ampConfig: ResourcesConfig = { }, }; -const mockGetId = getId as jest.Mock; +const mockCreateGetIdClient = jest.mocked(createGetIdClient); const mockKeyValueStorage = { setItem: jest.fn(), getItem: jest.fn(), @@ -43,23 +43,25 @@ describe('Cognito IdentityId Provider Happy Path Cases:', () => { const _ = new DefaultIdentityIdStore(mockKeyValueStorage); const mockDefaultIdentityIdStoreInstance = MockDefaultIdentityIdStore.mock.instances[0]; + const mockGetId: jest.MockedFunction> = + jest.fn(async (_config, params) => { + if (params.Logins && Object.keys(params.Logins).length === 0) { + return { + IdentityId: authAPITestParams.GuestIdentityId.id, + $metadata: {}, + }; + } else { + return { + IdentityId: authAPITestParams.PrimaryIdentityId.id, + $metadata: {}, + }; + } + }); beforeAll(() => { jest.spyOn(Amplify, 'getConfig').mockImplementationOnce(() => ampConfig); - mockGetId.mockImplementation( - async (_config: object, params: GetIdInput) => { - if (params.Logins && Object.keys(params.Logins).length === 0) { - return { - IdentityId: authAPITestParams.GuestIdentityId.id, - } as GetIdOutput; - } else { - return { - IdentityId: authAPITestParams.PrimaryIdentityId.id, - } as GetIdOutput; - } - }, - ); + mockCreateGetIdClient.mockReturnValue(mockGetId); }); afterEach(() => { @@ -80,6 +82,7 @@ describe('Cognito IdentityId Provider Happy Path Cases:', () => { ).toBe(authAPITestParams.GuestIdentityId.id); expect(mockGetId).toHaveBeenCalledTimes(0); }); + test('Should generate a guest identityId and return it', async () => { mockDefaultIdentityIdStoreInstance.loadIdentityId.mockImplementationOnce( async () => { @@ -102,6 +105,7 @@ describe('Cognito IdentityId Provider Happy Path Cases:', () => { ).toBe(authAPITestParams.GuestIdentityId.id); expect(mockGetId).toHaveBeenCalledTimes(1); }); + test('Should return stored primary identityId', async () => { mockDefaultIdentityIdStoreInstance.loadIdentityId.mockImplementationOnce( async () => { @@ -117,6 +121,7 @@ describe('Cognito IdentityId Provider Happy Path Cases:', () => { ).toBe(authAPITestParams.PrimaryIdentityId.id); expect(mockGetId).toHaveBeenCalledTimes(0); }); + test('Should generate a primary identityId and return it', async () => { mockDefaultIdentityIdStoreInstance.loadIdentityId.mockImplementationOnce( async () => { diff --git a/packages/auth/src/providers/cognito/credentialsProvider/IdentityIdProvider.ts b/packages/auth/src/providers/cognito/credentialsProvider/IdentityIdProvider.ts index b96adf08fbc..2c410a804b7 100644 --- a/packages/auth/src/providers/cognito/credentialsProvider/IdentityIdProvider.ts +++ b/packages/auth/src/providers/cognito/credentialsProvider/IdentityIdProvider.ts @@ -1,12 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { AuthTokens, ConsoleLogger, Identity, getId } from '@aws-amplify/core'; +import { + AuthTokens, + ConsoleLogger, + Identity, + createGetIdClient, +} from '@aws-amplify/core'; import { CognitoIdentityPoolConfig } from '@aws-amplify/core/internals/utils'; import { AuthError } from '../../../errors/AuthError'; import { getRegionFromIdentityPoolId } from '../../../foundation/parsers'; import { GetIdException } from '../types/errors'; +import { createCognitoIdentityPoolEndpointResolver } from '../factories'; import { IdentityIdStore } from './types'; import { formLoginsMap } from './utils'; @@ -82,10 +88,16 @@ async function generateIdentityId( const identityPoolId = authConfig?.identityPoolId; const region = getRegionFromIdentityPoolId(identityPoolId); + const getId = createGetIdClient({ + endpointResolver: createCognitoIdentityPoolEndpointResolver({ + endpointOverride: authConfig.identityPoolEndpoint, + }), + }); + // IdentityId is absent so get it using IdentityPoolId with Cognito's GetId API const idResult = // for a first-time user, this will return a brand new identity - // for a returning user, this will retrieve the previous identity assocaited with the logins + // for a returning user, this will retrieve the previous identity associated with the logins ( await getId( { diff --git a/packages/auth/src/providers/cognito/credentialsProvider/credentialsProvider.ts b/packages/auth/src/providers/cognito/credentialsProvider/credentialsProvider.ts index 6356ff0fd09..9d0db3dd143 100644 --- a/packages/auth/src/providers/cognito/credentialsProvider/credentialsProvider.ts +++ b/packages/auth/src/providers/cognito/credentialsProvider/credentialsProvider.ts @@ -7,7 +7,7 @@ import { CredentialsAndIdentityId, CredentialsAndIdentityIdProvider, GetCredentialsOptions, - getCredentialsForIdentity, + createGetCredentialsForIdentityClient, } from '@aws-amplify/core'; import { CognitoIdentityPoolConfig, @@ -17,6 +17,7 @@ import { import { AuthError } from '../../../errors/AuthError'; import { getRegionFromIdentityPoolId } from '../../../foundation/parsers'; import { assertIdTokenInAuthTokens } from '../utils/types'; +import { createCognitoIdentityPoolEndpointResolver } from '../factories'; import { IdentityIdStore } from './types'; import { cognitoIdentityIdProvider } from './IdentityIdProvider'; @@ -112,6 +113,12 @@ export class CognitoAWSCredentialsAndIdentityIdProvider const region = getRegionFromIdentityPoolId(authConfig.identityPoolId); + const getCredentialsForIdentity = createGetCredentialsForIdentityClient({ + endpointResolver: createCognitoIdentityPoolEndpointResolver({ + endpointOverride: authConfig.identityPoolEndpoint, + }), + }); + // use identityId to obtain guest credentials // save credentials in-memory // No logins params should be passed for guest creds: @@ -186,6 +193,12 @@ export class CognitoAWSCredentialsAndIdentityIdProvider const region = getRegionFromIdentityPoolId(authConfig.identityPoolId); + const getCredentialsForIdentity = createGetCredentialsForIdentityClient({ + endpointResolver: createCognitoIdentityPoolEndpointResolver({ + endpointOverride: authConfig.identityPoolEndpoint, + }), + }); + const clientResult = await getCredentialsForIdentity( { region }, { diff --git a/packages/auth/src/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.ts b/packages/auth/src/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.ts new file mode 100644 index 00000000000..2607a7b1c9a --- /dev/null +++ b/packages/auth/src/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { cognitoIdentityPoolEndpointResolver } from '@aws-amplify/core'; +import { EndpointResolverOptions } from '@aws-amplify/core/internals/aws-client-utils'; +import { AmplifyUrl } from '@aws-amplify/core/internals/utils'; + +export const createCognitoIdentityPoolEndpointResolver = + ({ endpointOverride }: { endpointOverride: string | undefined }) => + (input: EndpointResolverOptions): { url: URL } => { + if (endpointOverride) { + return { url: new AmplifyUrl(endpointOverride) }; + } + + return cognitoIdentityPoolEndpointResolver(input); + }; diff --git a/packages/auth/src/providers/cognito/factories/index.ts b/packages/auth/src/providers/cognito/factories/index.ts index 7f8050064d3..cb1a0cb7cd7 100644 --- a/packages/auth/src/providers/cognito/factories/index.ts +++ b/packages/auth/src/providers/cognito/factories/index.ts @@ -1,3 +1,4 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 export { createCognitoUserPoolEndpointResolver } from './createCognitoUserPoolEndpointResolver'; +export { createCognitoIdentityPoolEndpointResolver } from './createCognitoIdentityPoolEndpointResolver';