From 2dfebd986ee70bdb9aa86b0701d7c394de545533 Mon Sep 17 00:00:00 2001 From: Waldemar Hummer Date: Sun, 30 Jun 2024 17:32:08 +0200 Subject: [PATCH 1/4] add identityPoolEndpoint config to allow using a custom Cognito-Identity endpoint --- .../src/awsClients/cognitoIdentity/base.ts | 18 +++++++++++++----- packages/core/src/singleton/Auth/types.ts | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/core/src/awsClients/cognitoIdentity/base.ts b/packages/core/src/awsClients/cognitoIdentity/base.ts index cb6cfacb32b..1c4a08b2704 100644 --- a/packages/core/src/awsClients/cognitoIdentity/base.ts +++ b/packages/core/src/awsClients/cognitoIdentity/base.ts @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { Amplify } from '@aws-amplify/core'; + import { Endpoint, EndpointResolverOptions, @@ -29,11 +31,17 @@ const SERVICE_NAME = 'cognito-identity'; /** * The endpoint resolver function that returns the endpoint URL for a given region. */ -const endpointResolver = ({ region }: EndpointResolverOptions) => ({ - url: new AmplifyUrl( - `https://cognito-identity.${region}.${getDnsSuffix(region)}`, - ), -}); +const endpointResolver = ({ region }: EndpointResolverOptions) => { + const authConfig = Amplify.getConfig().Auth?.Cognito; + const customURL = authConfig?.identityPoolEndpoint; + const defaultURL = new AmplifyUrl( + `https://${SERVICE_NAME}.${region}.${getDnsSuffix(region)}`, + ); + + return { + url: customURL ? new AmplifyUrl(customURL) : defaultURL, + }; +}; /** * A Cognito Identity-specific middleware that disables caching for all requests. diff --git a/packages/core/src/singleton/Auth/types.ts b/packages/core/src/singleton/Auth/types.ts index 239810e8771..5e38f34f157 100644 --- a/packages/core/src/singleton/Auth/types.ts +++ b/packages/core/src/singleton/Auth/types.ts @@ -135,12 +135,14 @@ export interface AuthIdentityPoolConfig { export interface CognitoIdentityPoolConfig { identityPoolId: string; + identityPoolEndpoint?: string; allowGuestAccess?: boolean; } export interface AuthUserPoolConfig { Cognito: CognitoUserPoolConfig & { identityPoolId?: never; + identityPoolEndpoint?: never; allowGuestAccess?: never; }; } From 78ee7dfad9d3d4eed0b2716c074fd832047c3181 Mon Sep 17 00:00:00 2001 From: Hui Zhao Date: Wed, 2 Oct 2024 16:59:52 -0700 Subject: [PATCH 2/4] chore(core): refactor cognito identtiy clients to be factories --- .../getCredentialsForIdentity.test.ts | 20 +++- .../awsClients/cognitoIdentity/getId.test.ts | 19 ++- .../src/awsClients/cognitoIdentity/base.ts | 109 ------------------ .../getCredentialsForIdentity.ts | 77 ------------- .../src/awsClients/cognitoIdentity/getId.ts | 61 ---------- .../src/awsClients/cognitoIdentity/index.ts | 9 -- .../createDisableCacheMiddleware.ts | 18 +++ .../foundation/factories/middleware/index.ts | 4 + .../cognitoIdentityPoolEndpointResolver.ts | 15 +++ .../cognitoIdentity/constants.ts | 20 ++++ .../createGetCredentialsForIdentityClient.ts | 62 ++++++++++ .../cognitoIdentity/createGetIdClient.ts | 48 ++++++++ .../handler/cognitoIdentityTransferHandler.ts | 23 ++++ .../cognitoIdentity/handler/index.ts | 4 + .../serviceClients/cognitoIdentity/index.ts | 6 + .../serde/createClientSerializer.ts | 29 +++++ .../cognitoIdentity/serde/index.ts | 4 + .../cognitoIdentity/types/index.ts | 9 ++ .../cognitoIdentity/types/sdk.ts} | 2 - .../cognitoIdentity/types/serviceClient.ts | 8 ++ packages/core/src/index.ts | 11 +- scripts/dts-bundler/dts-bundler.config.js | 17 ++- 22 files changed, 302 insertions(+), 273 deletions(-) delete mode 100644 packages/core/src/awsClients/cognitoIdentity/base.ts delete mode 100644 packages/core/src/awsClients/cognitoIdentity/getCredentialsForIdentity.ts delete mode 100644 packages/core/src/awsClients/cognitoIdentity/getId.ts delete mode 100644 packages/core/src/awsClients/cognitoIdentity/index.ts create mode 100644 packages/core/src/foundation/factories/middleware/createDisableCacheMiddleware.ts create mode 100644 packages/core/src/foundation/factories/middleware/index.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/cognitoIdentityPoolEndpointResolver.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/constants.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetCredentialsForIdentityClient.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetIdClient.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/cognitoIdentityTransferHandler.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/index.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/index.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/createClientSerializer.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/index.ts create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/index.ts rename packages/core/src/{awsClients/cognitoIdentity/types.ts => foundation/factories/serviceClients/cognitoIdentity/types/sdk.ts} (99%) create mode 100644 packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/serviceClient.ts diff --git a/packages/core/__tests__/awsClients/cognitoIdentity/getCredentialsForIdentity.test.ts b/packages/core/__tests__/awsClients/cognitoIdentity/getCredentialsForIdentity.test.ts index e7f820549e2..d1ebb95e017 100644 --- a/packages/core/__tests__/awsClients/cognitoIdentity/getCredentialsForIdentity.test.ts +++ b/packages/core/__tests__/awsClients/cognitoIdentity/getCredentialsForIdentity.test.ts @@ -1,9 +1,10 @@ +import { createGetCredentialsForIdentityClient } from '../../../src'; import { fetchTransferHandler } from '../../../src/clients/handlers/fetch'; import { GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput, - getCredentialsForIdentity, -} from '../../../src/awsClients/cognitoIdentity'; +} from '../../../src/foundation/factories/serviceClients/cognitoIdentity/types'; +import { AmplifyUrl } from '../../../src/libraryUtils'; import { cognitoIdentityHandlerOptions, mockCredentials, @@ -60,6 +61,14 @@ describe('CognitoIdentity - getCredentialsForIdentity', () => { (fetchTransferHandler as jest.Mock).mockResolvedValue( mockJsonResponse(succeedResponse), ); + const getCredentialsForIdentity = createGetCredentialsForIdentityClient({ + endpointResolver: jest.fn(() => ({ + url: new AmplifyUrl( + 'https://cognito-identity.us-east-1.amazonaws.com/', + ), + })), + }); + const response = await getCredentialsForIdentity( cognitoIdentityHandlerOptions, params, @@ -91,6 +100,13 @@ describe('CognitoIdentity - getCredentialsForIdentity', () => { mockJsonResponse(failureResponse), ); expect.assertions(1); + const getCredentialsForIdentity = createGetCredentialsForIdentityClient({ + endpointResolver: jest.fn(() => ({ + url: new AmplifyUrl( + 'https://cognito-identity.us-east-1.amazonaws.com/', + ), + })), + }); try { await getCredentialsForIdentity(cognitoIdentityHandlerOptions, params); fail('test should fail'); diff --git a/packages/core/__tests__/awsClients/cognitoIdentity/getId.test.ts b/packages/core/__tests__/awsClients/cognitoIdentity/getId.test.ts index fceb0508ad7..c01fbdaa162 100644 --- a/packages/core/__tests__/awsClients/cognitoIdentity/getId.test.ts +++ b/packages/core/__tests__/awsClients/cognitoIdentity/getId.test.ts @@ -1,9 +1,10 @@ +import { createGetIdClient } from '../../../src'; import { fetchTransferHandler } from '../../../src/clients/handlers/fetch'; import { GetIdInput, GetIdOutput, - getId, -} from '../../../src/awsClients/cognitoIdentity'; +} from '../../../src/foundation/factories/serviceClients/cognitoIdentity/types'; +import { AmplifyUrl } from '../../../src/libraryUtils'; import { cognitoIdentityHandlerOptions, mockIdentityId, @@ -57,6 +58,13 @@ describe('CognitoIdentity - getId', () => { (fetchTransferHandler as jest.Mock).mockResolvedValue( mockJsonResponse(succeedResponse), ); + const getId = createGetIdClient({ + endpointResolver: jest.fn(() => ({ + url: new AmplifyUrl( + 'https://cognito-identity.us-east-1.amazonaws.com/', + ), + })), + }); const response = await getId(cognitoIdentityHandlerOptions, params); expect(response).toEqual(expectedOutput); expect(fetchTransferHandler).toHaveBeenCalledWith( @@ -85,6 +93,13 @@ describe('CognitoIdentity - getId', () => { mockJsonResponse(failureResponse), ); expect.assertions(1); + const getId = createGetIdClient({ + endpointResolver: jest.fn(() => ({ + url: new AmplifyUrl( + 'https://cognito-identity.us-east-1.amazonaws.com/', + ), + })), + }); try { await getId(cognitoIdentityHandlerOptions, params); fail('test should fail'); diff --git a/packages/core/src/awsClients/cognitoIdentity/base.ts b/packages/core/src/awsClients/cognitoIdentity/base.ts deleted file mode 100644 index 1c4a08b2704..00000000000 --- a/packages/core/src/awsClients/cognitoIdentity/base.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Amplify } from '@aws-amplify/core'; - -import { - Endpoint, - EndpointResolverOptions, - Headers, - HttpRequest, - HttpResponse, - Middleware, - getDnsSuffix, - parseJsonError, - unauthenticatedHandler, -} from '../../clients'; -import { composeTransferHandler } from '../../clients/internal/composeTransferHandler'; -import { - getRetryDecider, - jitteredBackoff, -} from '../../clients/middleware/retry'; -import { getAmplifyUserAgent } from '../../Platform'; -import { observeFrameworkChanges } from '../../Platform/detectFramework'; -import { AmplifyUrl } from '../../utils/amplifyUrl'; - -/** - * The service name used to sign requests if the API requires authentication. - */ -const SERVICE_NAME = 'cognito-identity'; - -/** - * The endpoint resolver function that returns the endpoint URL for a given region. - */ -const endpointResolver = ({ region }: EndpointResolverOptions) => { - const authConfig = Amplify.getConfig().Auth?.Cognito; - const customURL = authConfig?.identityPoolEndpoint; - const defaultURL = new AmplifyUrl( - `https://${SERVICE_NAME}.${region}.${getDnsSuffix(region)}`, - ); - - return { - url: customURL ? new AmplifyUrl(customURL) : defaultURL, - }; -}; - -/** - * A Cognito Identity-specific middleware that disables caching for all requests. - */ -const disableCacheMiddlewareFactory: Middleware< - HttpRequest, - HttpResponse, - Record -> = () => next => - async function disableCacheMiddleware(request) { - request.headers['cache-control'] = 'no-store'; - - return next(request); - }; - -/** - * A Cognito Identity-specific transfer handler that does NOT sign requests, and - * disables caching. - * - * @internal - */ -export const cognitoIdentityTransferHandler = composeTransferHandler< - [Parameters[0]], - HttpRequest, - HttpResponse, - typeof unauthenticatedHandler ->(unauthenticatedHandler, [disableCacheMiddlewareFactory]); - -/** - * @internal - */ -export const defaultConfig = { - service: SERVICE_NAME, - endpointResolver, - retryDecider: getRetryDecider(parseJsonError), - computeDelay: jitteredBackoff, - userAgentValue: getAmplifyUserAgent(), - cache: 'no-store', -}; - -observeFrameworkChanges(() => { - defaultConfig.userAgentValue = getAmplifyUserAgent(); -}); - -/** - * @internal - */ -export const getSharedHeaders = (operation: string): Headers => ({ - 'content-type': 'application/x-amz-json-1.1', - 'x-amz-target': `AWSCognitoIdentityService.${operation}`, -}); - -/** - * @internal - */ -export const buildHttpRpcRequest = ( - { url }: Endpoint, - headers: Headers, - body: any, -): HttpRequest => ({ - headers, - url, - body, - method: 'POST', -}); diff --git a/packages/core/src/awsClients/cognitoIdentity/getCredentialsForIdentity.ts b/packages/core/src/awsClients/cognitoIdentity/getCredentialsForIdentity.ts deleted file mode 100644 index a8050ee8058..00000000000 --- a/packages/core/src/awsClients/cognitoIdentity/getCredentialsForIdentity.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - Endpoint, - HttpRequest, - HttpResponse, - parseJsonBody, - parseJsonError, - parseMetadata, -} from '../../clients'; -import { composeServiceApi } from '../../clients/internal'; - -import { - buildHttpRpcRequest, - cognitoIdentityTransferHandler, - defaultConfig, - getSharedHeaders, -} from './base'; -import type { - Credentials, - GetCredentialsForIdentityCommandInput as GetCredentialsForIdentityInput, - GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput, -} from './types'; - -export type { GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput }; - -const getCredentialsForIdentitySerializer = ( - input: GetCredentialsForIdentityInput, - endpoint: Endpoint, -): HttpRequest => { - const headers = getSharedHeaders('GetCredentialsForIdentity'); - const body = JSON.stringify(input); - - return buildHttpRpcRequest(endpoint, headers, body); -}; - -const getCredentialsForIdentityDeserializer = async ( - response: HttpResponse, -): Promise => { - if (response.statusCode >= 300) { - const error = await parseJsonError(response); - throw error; - } else { - const body = await parseJsonBody(response); - - return { - IdentityId: body.IdentityId, - Credentials: deserializeCredentials(body.Credentials), - $metadata: parseMetadata(response), - }; - } -}; - -const deserializeCredentials = ({ - AccessKeyId, - SecretKey, - SessionToken, - Expiration, -}: Credentials = {}): Credentials => { - return { - AccessKeyId, - SecretKey, - SessionToken, - Expiration: Expiration && new Date((Expiration as any) * 1000), - }; -}; - -/** - * @internal - */ -export const getCredentialsForIdentity = composeServiceApi( - cognitoIdentityTransferHandler, - getCredentialsForIdentitySerializer, - getCredentialsForIdentityDeserializer, - defaultConfig, -); diff --git a/packages/core/src/awsClients/cognitoIdentity/getId.ts b/packages/core/src/awsClients/cognitoIdentity/getId.ts deleted file mode 100644 index 20a81f4f1fc..00000000000 --- a/packages/core/src/awsClients/cognitoIdentity/getId.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - Endpoint, - HttpRequest, - HttpResponse, - parseJsonBody, - parseJsonError, - parseMetadata, -} from '../../clients'; -import { composeServiceApi } from '../../clients/internal'; - -import { - buildHttpRpcRequest, - cognitoIdentityTransferHandler, - defaultConfig, - getSharedHeaders, -} from './base'; -import { - GetIdCommandInput as GetIdInput, - GetIdCommandOutput as GetIdOutput, -} from './types'; - -export type { GetIdInput, GetIdOutput }; - -const getIdSerializer = ( - input: GetIdInput, - endpoint: Endpoint, -): HttpRequest => { - const headers = getSharedHeaders('GetId'); - const body = JSON.stringify(input); - - return buildHttpRpcRequest(endpoint, headers, body); -}; - -const getIdDeserializer = async ( - response: HttpResponse, -): Promise => { - if (response.statusCode >= 300) { - const error = await parseJsonError(response); - throw error; - } else { - const body = await parseJsonBody(response); - - return { - IdentityId: body.IdentityId, - $metadata: parseMetadata(response), - }; - } -}; - -/** - * @internal - */ -export const getId = composeServiceApi( - cognitoIdentityTransferHandler, - getIdSerializer, - getIdDeserializer, - defaultConfig, -); diff --git a/packages/core/src/awsClients/cognitoIdentity/index.ts b/packages/core/src/awsClients/cognitoIdentity/index.ts deleted file mode 100644 index 8c30ac96c50..00000000000 --- a/packages/core/src/awsClients/cognitoIdentity/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -export { getId, GetIdInput, GetIdOutput } from './getId'; -export { - getCredentialsForIdentity, - GetCredentialsForIdentityInput, - GetCredentialsForIdentityOutput, -} from './getCredentialsForIdentity'; diff --git a/packages/core/src/foundation/factories/middleware/createDisableCacheMiddleware.ts b/packages/core/src/foundation/factories/middleware/createDisableCacheMiddleware.ts new file mode 100644 index 00000000000..b5f2988aa70 --- /dev/null +++ b/packages/core/src/foundation/factories/middleware/createDisableCacheMiddleware.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { HttpRequest, HttpResponse, Middleware } from '../../../clients'; + +/** + * A Cognito Identity-specific middleware that disables caching for all requests. + */ +export const createDisableCacheMiddleware: Middleware< + HttpRequest, + HttpResponse, + Record +> = () => next => + async function disableCacheMiddleware(request) { + request.headers['cache-control'] = 'no-store'; + + return next(request); + }; diff --git a/packages/core/src/foundation/factories/middleware/index.ts b/packages/core/src/foundation/factories/middleware/index.ts new file mode 100644 index 00000000000..a59feeabeed --- /dev/null +++ b/packages/core/src/foundation/factories/middleware/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { createDisableCacheMiddleware } from './createDisableCacheMiddleware'; diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/cognitoIdentityPoolEndpointResolver.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/cognitoIdentityPoolEndpointResolver.ts new file mode 100644 index 00000000000..33bfbc88227 --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/cognitoIdentityPoolEndpointResolver.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { EndpointResolverOptions, getDnsSuffix } from '../../../../clients'; +import { AmplifyUrl } from '../../../../libraryUtils'; + +import { COGNITO_IDENTITY_SERVICE_NAME } from './constants'; + +export const cognitoIdentityPoolEndpointResolver = ({ + region, +}: EndpointResolverOptions) => ({ + url: new AmplifyUrl( + `https://${COGNITO_IDENTITY_SERVICE_NAME}.${region}.${getDnsSuffix(region)}`, + ), +}); diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/constants.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/constants.ts new file mode 100644 index 00000000000..02f2f737cbc --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/constants.ts @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + getRetryDecider, + jitteredBackoff, + parseJsonError, +} from '../../../../clients'; + +/** + * The service name used to sign requests if the API requires authentication. + */ +export const COGNITO_IDENTITY_SERVICE_NAME = 'cognito-identity'; + +export const DEFAULT_SERVICE_CLIENT_API_CONFIG = { + service: COGNITO_IDENTITY_SERVICE_NAME, + retryDecider: getRetryDecider(parseJsonError), + computeDelay: jitteredBackoff, + cache: 'no-store', +}; diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetCredentialsForIdentityClient.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetCredentialsForIdentityClient.ts new file mode 100644 index 00000000000..ab0ba8d0b7f --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetCredentialsForIdentityClient.ts @@ -0,0 +1,62 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + HttpResponse, + parseJsonBody, + parseJsonError, + parseMetadata, +} from '../../../../clients'; +import { composeServiceApi } from '../../../../clients/internal'; +import { getAmplifyUserAgent } from '../../../../Platform'; + +import { DEFAULT_SERVICE_CLIENT_API_CONFIG } from './constants'; +import { cognitoIdentityTransferHandler } from './handler'; +import { createClientSerializer } from './serde'; +import { + Credentials, + GetCredentialsForIdentityCommandOutput, + GetCredentialsForIdentityInput, + ServiceClientFactoryInput, +} from './types'; + +export const createGetCredentialsForIdentityClient = ( + config: ServiceClientFactoryInput, +) => + composeServiceApi( + cognitoIdentityTransferHandler, + createClientSerializer( + 'GetCredentialsForIdentity', + ), + getCredentialsForIdentityDeserializer, + { + ...DEFAULT_SERVICE_CLIENT_API_CONFIG, + ...config, + userAgentValue: getAmplifyUserAgent(), + }, + ); + +const getCredentialsForIdentityDeserializer = async ( + response: HttpResponse, +): Promise => { + if (response.statusCode >= 300) { + const error = await parseJsonError(response); + throw error; + } else { + const body = await parseJsonBody(response); + + return { + IdentityId: body.IdentityId, + Credentials: deserializeCredentials(body.Credentials), + $metadata: parseMetadata(response), + }; + } +}; + +const deserializeCredentials = ({ + Expiration, + ...rest +}: Credentials = {}): Credentials => ({ + ...rest, + Expiration: Expiration && new Date((Expiration as any) * 1000), +}); diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetIdClient.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetIdClient.ts new file mode 100644 index 00000000000..90b1b496e8a --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/createGetIdClient.ts @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + HttpResponse, + parseJsonBody, + parseJsonError, + parseMetadata, +} from '../../../../clients'; +import { composeServiceApi } from '../../../../clients/internal'; +import { getAmplifyUserAgent } from '../../../../Platform'; + +import { DEFAULT_SERVICE_CLIENT_API_CONFIG } from './constants'; +import { cognitoIdentityTransferHandler } from './handler'; +import { createClientSerializer } from './serde'; +import { + GetIdCommandInput, + GetIdCommandOutput, + ServiceClientFactoryInput, +} from './types'; + +export const createGetIdClient = (config: ServiceClientFactoryInput) => + composeServiceApi( + cognitoIdentityTransferHandler, + createClientSerializer('GetId'), + getIdDeserializer, + { + ...DEFAULT_SERVICE_CLIENT_API_CONFIG, + ...config, + userAgentValue: getAmplifyUserAgent(), + }, + ); + +const getIdDeserializer = async ( + response: HttpResponse, +): Promise => { + if (response.statusCode >= 300) { + const error = await parseJsonError(response); + throw error; + } else { + const body = await parseJsonBody(response); + + return { + IdentityId: body.IdentityId, + $metadata: parseMetadata(response), + }; + } +}; diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/cognitoIdentityTransferHandler.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/cognitoIdentityTransferHandler.ts new file mode 100644 index 00000000000..7e56abe4e27 --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/cognitoIdentityTransferHandler.ts @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + HttpRequest, + HttpResponse, + unauthenticatedHandler, +} from '../../../../../clients'; +import { composeTransferHandler } from '../../../../../clients/internal'; +import { createDisableCacheMiddleware } from '../../../middleware'; + +/** + * A Cognito Identity-specific transfer handler that does NOT sign requests, and + * disables caching. + * + * @internal + */ +export const cognitoIdentityTransferHandler = composeTransferHandler< + [Parameters[0]], + HttpRequest, + HttpResponse, + typeof unauthenticatedHandler +>(unauthenticatedHandler, [createDisableCacheMiddleware]); diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/index.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/index.ts new file mode 100644 index 00000000000..23c2078c69e --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/handler/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { cognitoIdentityTransferHandler } from './cognitoIdentityTransferHandler'; diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/index.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/index.ts new file mode 100644 index 00000000000..f96c7192f8b --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { createGetCredentialsForIdentityClient } from './createGetCredentialsForIdentityClient'; +export { createGetIdClient } from './createGetIdClient'; +export { cognitoIdentityPoolEndpointResolver } from './cognitoIdentityPoolEndpointResolver'; diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/createClientSerializer.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/createClientSerializer.ts new file mode 100644 index 00000000000..250e7b4bc7d --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/createClientSerializer.ts @@ -0,0 +1,29 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Endpoint, Headers, HttpRequest } from '../../../../../clients'; + +export const createClientSerializer = + (operation: string) => + (input: Input, endpoint: Endpoint): HttpRequest => { + const headers = getSharedHeaders(operation); + const body = JSON.stringify(input); + + return buildHttpRpcRequest(endpoint, headers, body); + }; + +const getSharedHeaders = (operation: string): Headers => ({ + 'content-type': 'application/x-amz-json-1.1', + 'x-amz-target': `AWSCognitoIdentityService.${operation}`, +}); + +export const buildHttpRpcRequest = ( + { url }: Endpoint, + headers: Headers, + body: any, +): HttpRequest => ({ + headers, + url, + body, + method: 'POST', +}); diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/index.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/index.ts new file mode 100644 index 00000000000..ef692f8cc5f --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/serde/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { createClientSerializer } from './createClientSerializer'; diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/index.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/index.ts new file mode 100644 index 00000000000..ff33b6f7b99 --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/index.ts @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './sdk'; +export { + GetCredentialsForIdentityCommandOutput as GetCredentialsForIdentityOutput, + GetIdCommandOutput as GetIdOutput, +} from './sdk'; +export * from './serviceClient'; diff --git a/packages/core/src/awsClients/cognitoIdentity/types.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/sdk.ts similarity index 99% rename from packages/core/src/awsClients/cognitoIdentity/types.ts rename to packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/sdk.ts index 0a324decc1c..2ccbfba6461 100644 --- a/packages/core/src/awsClients/cognitoIdentity/types.ts +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/sdk.ts @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// Generated by scripts/dts-bundler/README.md - import { MetadataBearer as __MetadataBearer } from '@aws-sdk/types'; /** diff --git a/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/serviceClient.ts b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/serviceClient.ts new file mode 100644 index 00000000000..a6122f7c8bf --- /dev/null +++ b/packages/core/src/foundation/factories/serviceClients/cognitoIdentity/types/serviceClient.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { EndpointResolverOptions } from '../../../../../clients'; + +export interface ServiceClientFactoryInput { + endpointResolver(options: EndpointResolverOptions): { url: URL }; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f17968928c6..5aeff99c724 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -43,13 +43,12 @@ export { clearCredentials, } from './singleton'; -// AWSClients exports +// Cognito Identity service client factories export { - getCredentialsForIdentity, - getId, - GetCredentialsForIdentityInput, - GetCredentialsForIdentityOutput, -} from './awsClients/cognitoIdentity'; + createGetCredentialsForIdentityClient, + createGetIdClient, + cognitoIdentityPoolEndpointResolver, +} from './foundation/factories/serviceClients/cognitoIdentity'; // Amplify-wide constructs export { UserProfile } from './types'; diff --git a/scripts/dts-bundler/dts-bundler.config.js b/scripts/dts-bundler/dts-bundler.config.js index 90210b58cee..4ab381c670c 100644 --- a/scripts/dts-bundler/dts-bundler.config.js +++ b/scripts/dts-bundler/dts-bundler.config.js @@ -13,8 +13,7 @@ const baseTsConfigPath = join( __dirname, '..', '..', - 'packages', - 'tsconfig.base.json', + 'tsconfig.json', ); const corePackageSrcClientsPath = join( __dirname, @@ -26,6 +25,16 @@ const corePackageSrcClientsPath = join( 'awsClients', ); +const corePackageSrcFoundationPath = join( + __dirname, + '..', + '..', + 'packages', + 'core', + 'src', + 'foundation' +) + const storagePackageSrcClientsPath = join( __dirname, '..', @@ -51,8 +60,6 @@ const authPackageSrcClientsPath = join( 'types', ); -// packages/auth/src/foundation/factories/serviceClients/cognitoIdentityProvider/types/Sdk.ts - /** @type import('dts-bundle-generator/config-schema').BundlerConfig */ const config = { compilationOptions: { @@ -69,7 +76,7 @@ const config = { }, { filePath: './cognito-identity.d.ts', - outFile: join(corePackageSrcClientsPath, 'CognitoIdentity', 'types.ts'), + outFile: join(corePackageSrcFoundationPath, 'factories', 'serviceClients', 'cognitoIdentity', 'types', 'sdk.ts'), libraries: { inlinedLibraries: ['@aws-sdk/client-cognito-identity'], }, From 022f28d3cbb3fe6cadc5a0849be941161afbb9ca Mon Sep 17 00:00:00 2001 From: Hui Zhao Date: Wed, 2 Oct 2024 17:00:47 -0700 Subject: [PATCH 3/4] feat(auth): support custom identity pool endpoint --- .../cognito/credentialsProvider.test.ts | 81 +++++++++++-------- ...ognitoIdentityPoolEndpointResolver.test.ts | 57 +++++++++++++ .../cognito/identityIdProvider.test.ts | 45 ++++++----- .../credentialsProvider/IdentityIdProvider.ts | 16 +++- .../credentialsProvider.ts | 15 +++- ...eateCognitoIdentityPoolEndpointResolver.ts | 15 ++++ .../src/providers/cognito/factories/index.ts | 1 + 7 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 packages/auth/__tests__/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.test.ts create mode 100644 packages/auth/src/providers/cognito/factories/createCognitoIdentityPoolEndpointResolver.ts 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'; From f991f68759212a17c383432705020b3529f8cca4 Mon Sep 17 00:00:00 2001 From: Hui Zhao Date: Tue, 29 Oct 2024 11:21:42 -0700 Subject: [PATCH 4/4] chore: adding warnings regarding using custom endpoints --- .../__tests__/singleton/Auth/index.test.ts | 52 +++++++++++++++++++ packages/core/src/singleton/Auth/index.ts | 20 +++++++ packages/core/src/singleton/Auth/types.ts | 6 +++ 3 files changed, 78 insertions(+) create mode 100644 packages/core/__tests__/singleton/Auth/index.test.ts diff --git a/packages/core/__tests__/singleton/Auth/index.test.ts b/packages/core/__tests__/singleton/Auth/index.test.ts new file mode 100644 index 00000000000..e08f838cfe5 --- /dev/null +++ b/packages/core/__tests__/singleton/Auth/index.test.ts @@ -0,0 +1,52 @@ +import { ConsoleLogger } from '../../../src/Logger'; +import { AuthClass } from '../../../src/singleton/Auth'; + +jest.mock('../../../src/Logger', () => { + const warn = jest.fn(); + + return { + ConsoleLogger: jest.fn(() => ({ + warn, + })), + }; +}); + +describe('Auth', () => { + const auth = new AuthClass(); + const logger = new ConsoleLogger('Auth'); + const mockedWarn = logger.warn as jest.Mock; + + describe('configure', () => { + const mockConfig = { + userPoolClientId: 'userPoolClientId', + userPoolId: 'userPoolId', + }; + + it('prints warning when use custom endpoint for Cognito User Pool', () => { + auth.configure({ + Cognito: { + ...mockConfig, + userPoolEndpoint: 'https://custom-endpoint.com', + }, + }); + + expect(mockedWarn).toHaveBeenCalledWith( + expect.stringContaining('Amazon Cognito User Pool'), + ); + }); + + it('prints warning when use custom endpoint for Cognito Identity Pool', () => { + auth.configure({ + Cognito: { + ...mockConfig, + identityPoolId: 'identityPoolId', + identityPoolEndpoint: 'https://custom-endpoint.com', + }, + }); + + expect(mockedWarn).toHaveBeenCalledWith( + expect.stringContaining('Amazon Cognito Identity Pool'), + ); + }); + }); +}); diff --git a/packages/core/src/singleton/Auth/index.ts b/packages/core/src/singleton/Auth/index.ts index 245cc704b0c..d7b87dc2533 100644 --- a/packages/core/src/singleton/Auth/index.ts +++ b/packages/core/src/singleton/Auth/index.ts @@ -1,5 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { ConsoleLogger } from '../../Logger'; + import { AuthConfig, AuthSession, @@ -9,6 +11,8 @@ import { LibraryAuthOptions, } from './types'; +const logger = new ConsoleLogger('Auth'); + export function isTokenExpired({ expiresAt, clockDrift, @@ -41,6 +45,19 @@ export class AuthClass { ): void { this.authConfig = authResourcesConfig; this.authOptions = authOptions; + + if (authResourcesConfig && authResourcesConfig.Cognito?.userPoolEndpoint) { + logger.warn(getCustomEndpointWarningMessage('Amazon Cognito User Pool')); + } + + if ( + authResourcesConfig && + authResourcesConfig.Cognito?.identityPoolEndpoint + ) { + logger.warn( + getCustomEndpointWarningMessage('Amazon Cognito Identity Pool'), + ); + } } /** @@ -106,3 +123,6 @@ export class AuthClass { ); } } + +const getCustomEndpointWarningMessage = (target: string): string => + `You are using a custom Amazon ${target} endpoint, ensure the endpoint is correct.`; diff --git a/packages/core/src/singleton/Auth/types.ts b/packages/core/src/singleton/Auth/types.ts index acd61f8d91d..8eb903c0487 100644 --- a/packages/core/src/singleton/Auth/types.ts +++ b/packages/core/src/singleton/Auth/types.ts @@ -135,6 +135,9 @@ export interface AuthIdentityPoolConfig { export interface CognitoIdentityPoolConfig { identityPoolId: string; + /** + * Use this field to specify a custom endpoint for the Amazon Cognito identity pool. Ensure this endpoint is correct and valid. + */ identityPoolEndpoint?: string; allowGuestAccess?: boolean; } @@ -152,6 +155,9 @@ export type CognitoUserPoolConfigMfaStatus = 'on' | 'off' | 'optional'; export interface CognitoUserPoolConfig { userPoolClientId: string; userPoolId: string; + /** + * Use this field to specify a custom endpoint for the Amazon Cognito user pool. Ensure this endpoint is correct and valid. + */ userPoolEndpoint?: string; signUpVerificationMethod?: 'code' | 'link'; loginWith?: {