From 1bcbae432d054ef6b19d8086375f040e6edbfccf Mon Sep 17 00:00:00 2001 From: Jim Blanchard Date: Wed, 3 Jan 2024 19:16:03 -0600 Subject: [PATCH] fix: Recreate Analytics clients when credentials change (#12789) --- .../kinesis-firehose/utils/getEventBuffer.ts | 39 ++++++++++++----- .../providers/kinesis/utils/getEventBuffer.ts | 42 +++++++++++++----- .../personalize/utils/getEventBuffer.ts | 43 ++++++++++++++----- packages/analytics/tsconfig.json | 2 +- packages/aws-amplify/package.json | 2 +- .../utils/haveCredentialsChanged.test.ts | 42 ++++++++++++++++++ packages/core/src/libraryUtils.ts | 1 + .../core/src/utils/haveCredentialsChanged.ts | 17 ++++++++ 8 files changed, 154 insertions(+), 34 deletions(-) create mode 100644 packages/core/__tests__/utils/haveCredentialsChanged.test.ts create mode 100644 packages/core/src/utils/haveCredentialsChanged.ts diff --git a/packages/analytics/src/providers/kinesis-firehose/utils/getEventBuffer.ts b/packages/analytics/src/providers/kinesis-firehose/utils/getEventBuffer.ts index 843459d1428..bae9ef617c5 100644 --- a/packages/analytics/src/providers/kinesis-firehose/utils/getEventBuffer.ts +++ b/packages/analytics/src/providers/kinesis-firehose/utils/getEventBuffer.ts @@ -1,11 +1,19 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { EventBuffer, groupBy, IAnalyticsClient } from '../../../utils'; +import { + AWSCredentials, + haveCredentialsChanged +} from '@aws-amplify/core/internals/utils'; import { FirehoseClient, PutRecordBatchCommand, } from '@aws-sdk/client-firehose'; +import { + EventBuffer, + groupBy, + IAnalyticsClient +} from '../../../utils'; import { KinesisFirehoseBufferEvent, KinesisFirehoseEventBufferConfig, @@ -23,7 +31,7 @@ const eventBufferMap: Record< string, EventBuffer > = {}; -const cachedClients: Record = {}; +const cachedClients: Record = {}; const createPutRecordsBatchCommand = ( streamName: string, @@ -76,18 +84,29 @@ export const getEventBuffer = ({ userAgentValue, }: KinesisFirehoseEventBufferConfig): EventBuffer => { const sessionIdentityKey = [region, identityId].filter(id => !!id).join('-'); + const [ cachedClient, cachedCredentials ] = cachedClients[sessionIdentityKey] ?? []; + let credentialsHaveChanged = false; - if (!eventBufferMap[sessionIdentityKey]) { + // Check if credentials have changed for the cached client + if (cachedClient) { + credentialsHaveChanged = haveCredentialsChanged(cachedCredentials, credentials); + } + + if (!eventBufferMap[sessionIdentityKey] || credentialsHaveChanged) { const getClient = (): IAnalyticsClient => { - if (!cachedClients[sessionIdentityKey]) { - cachedClients[sessionIdentityKey] = new FirehoseClient({ - region, - credentials, - customUserAgent: userAgentValue, - }); + if (!cachedClient || credentialsHaveChanged) { + cachedClients[sessionIdentityKey] = [ + new FirehoseClient({ + region, + credentials, + customUserAgent: userAgentValue, + }), + credentials + ]; } - const firehoseClient = cachedClients[sessionIdentityKey]; + const [ firehoseClient ] = cachedClients[sessionIdentityKey]; + return events => submitEvents(events, firehoseClient, resendLimit); }; diff --git a/packages/analytics/src/providers/kinesis/utils/getEventBuffer.ts b/packages/analytics/src/providers/kinesis/utils/getEventBuffer.ts index 51dbfef8d82..e88262e5c8d 100644 --- a/packages/analytics/src/providers/kinesis/utils/getEventBuffer.ts +++ b/packages/analytics/src/providers/kinesis/utils/getEventBuffer.ts @@ -1,9 +1,17 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { KinesisBufferEvent, KinesisEventBufferConfig } from '../types'; -import { EventBuffer, groupBy, IAnalyticsClient } from '../../../utils'; +import { + AWSCredentials, + haveCredentialsChanged +} from '@aws-amplify/core/internals/utils'; import { KinesisClient, PutRecordsCommand } from '@aws-sdk/client-kinesis'; +import { KinesisBufferEvent, KinesisEventBufferConfig } from '../types'; +import { + EventBuffer, + groupBy, + IAnalyticsClient +} from '../../../utils'; /** * These Records hold cached event buffers and AWS clients. @@ -14,7 +22,7 @@ import { KinesisClient, PutRecordsCommand } from '@aws-sdk/client-kinesis'; * When a new session is initiated, the previous ones should be released. * */ const eventBufferMap: Record> = {}; -const cachedClients: Record = {}; +const cachedClients: Record = {}; const createKinesisPutRecordsCommand = ( streamName: string, @@ -67,19 +75,31 @@ export const getEventBuffer = ({ userAgentValue, }: KinesisEventBufferConfig): EventBuffer => { const sessionIdentityKey = [region, identityId].filter(x => !!x).join('-'); + const [ cachedClient, cachedCredentials ] = cachedClients[sessionIdentityKey] ?? []; + let credentialsHaveChanged = false; - if (!eventBufferMap[sessionIdentityKey]) { + // Check if credentials have changed for the cached client + if (cachedClient) { + credentialsHaveChanged = haveCredentialsChanged(cachedCredentials, credentials); + } + + if (!eventBufferMap[sessionIdentityKey] || credentialsHaveChanged) { const getKinesisClient = (): IAnalyticsClient => { - if (!cachedClients[sessionIdentityKey]) { - cachedClients[sessionIdentityKey] = new KinesisClient({ - credentials, - region, - customUserAgent: userAgentValue, - }); + if (!cachedClient || credentialsHaveChanged) { + cachedClients[sessionIdentityKey] = [ + new KinesisClient({ + credentials, + region, + customUserAgent: userAgentValue, + }), + credentials + ]; } + const [ kinesisClient ] = cachedClients[sessionIdentityKey]; + return events => - submitEvents(events, cachedClients[sessionIdentityKey], resendLimit); + submitEvents(events, kinesisClient, resendLimit); }; // create new session diff --git a/packages/analytics/src/providers/personalize/utils/getEventBuffer.ts b/packages/analytics/src/providers/personalize/utils/getEventBuffer.ts index a0e64f9a969..4591317f48d 100644 --- a/packages/analytics/src/providers/personalize/utils/getEventBuffer.ts +++ b/packages/analytics/src/providers/personalize/utils/getEventBuffer.ts @@ -1,12 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { EventBuffer, groupBy, IAnalyticsClient } from '../../../utils'; -import { PersonalizeBufferConfig, PersonalizeBufferEvent } from '../types'; +import { + AWSCredentials, + haveCredentialsChanged +} from '@aws-amplify/core/internals/utils'; import { PersonalizeEventsClient, PutEventsCommand, } from '@aws-sdk/client-personalize-events'; +import { + EventBuffer, + groupBy, + IAnalyticsClient +} from '../../../utils'; +import { PersonalizeBufferConfig, PersonalizeBufferEvent } from '../types'; /** * These Records hold cached event buffers and AWS clients. @@ -17,7 +25,7 @@ import { * When a new session is initiated, the previous ones should be released. * */ const eventBufferMap: Record> = {}; -const cachedClients: Record = {}; +const cachedClients: Record = {}; const DELIMITER = '#'; @@ -71,17 +79,30 @@ export const getEventBuffer = ({ userAgentValue, }: PersonalizeBufferConfig): EventBuffer => { const sessionIdentityKey = [region, identityId].filter(x => !!x).join('-'); + const [ cachedClient, cachedCredentials ] = cachedClients[sessionIdentityKey] ?? []; + let credentialsHaveChanged = false; + + // Check if credentials have changed for the cached client + if (cachedClient) { + credentialsHaveChanged = haveCredentialsChanged(cachedCredentials, credentials); + } - if (!eventBufferMap[sessionIdentityKey]) { + if (!eventBufferMap[sessionIdentityKey] || credentialsHaveChanged) { const getClient = (): IAnalyticsClient => { - if (!cachedClients[sessionIdentityKey]) { - cachedClients[sessionIdentityKey] = new PersonalizeEventsClient({ - region, - credentials, - customUserAgent: userAgentValue, - }); + if (!cachedClient || credentialsHaveChanged) { + cachedClients[sessionIdentityKey] = [ + new PersonalizeEventsClient({ + region, + credentials, + customUserAgent: userAgentValue, + }), + credentials + ]; } - return events => submitEvents(events, cachedClients[sessionIdentityKey]); + + const [ personalizeClient ] = cachedClients[sessionIdentityKey]; + + return events => submitEvents(events, personalizeClient); }; eventBufferMap[sessionIdentityKey] = diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json index f907c964821..0a497e00826 100644 --- a/packages/analytics/tsconfig.json +++ b/packages/analytics/tsconfig.json @@ -5,5 +5,5 @@ "strict": true, "noImplicitAny": true }, - "include": ["./src"] + "include": ["./src", "../core/src/utils/haveCredentialsChanged.ts"] } diff --git a/packages/aws-amplify/package.json b/packages/aws-amplify/package.json index 8ba9c196002..a8332acb8b8 100644 --- a/packages/aws-amplify/package.json +++ b/packages/aws-amplify/package.json @@ -297,7 +297,7 @@ "name": "[Analytics] record (Kinesis)", "path": "./dist/esm/analytics/kinesis/index.mjs", "import": "{ record }", - "limit": "44.40 kB" + "limit": "44.47 kB" }, { "name": "[Analytics] record (Kinesis Firehose)", diff --git a/packages/core/__tests__/utils/haveCredentialsChanged.test.ts b/packages/core/__tests__/utils/haveCredentialsChanged.test.ts new file mode 100644 index 00000000000..1dc42f6f72b --- /dev/null +++ b/packages/core/__tests__/utils/haveCredentialsChanged.test.ts @@ -0,0 +1,42 @@ +import { haveCredentialsChanged } from '../../src/utils/haveCredentialsChanged'; + +const MOCK_AWS_CREDS = { + accessKeyId: 'mock-access-key', + secretAccessKey: 'mock-secret-key', + sessionToken: 'mock-session-token' +}; + +describe('haveCredentialsChanged', () => { + it('returns true if access key has changed', () => { + const credentialsHaveChanged = haveCredentialsChanged(MOCK_AWS_CREDS, { + ...MOCK_AWS_CREDS, + secretAccessKey: 'mock-secret-key-alt', + }); + + expect(credentialsHaveChanged).toBe(true); + }); + + it('returns true if access key has changed', () => { + const credentialsHaveChanged = haveCredentialsChanged(MOCK_AWS_CREDS, { + ...MOCK_AWS_CREDS, + accessKeyId: 'mock-access-key-alt', + }); + + expect(credentialsHaveChanged).toBe(true); + }); + + it('returns true if session token has changed', () => { + const credentialsHaveChanged = haveCredentialsChanged(MOCK_AWS_CREDS, { + ...MOCK_AWS_CREDS, + sessionToken: 'mock-session-token-alt', + }); + + expect(credentialsHaveChanged).toBe(true); + }); + + it('returns false if credentials have not changed', () => { + const credentialsHaveChanged = haveCredentialsChanged(MOCK_AWS_CREDS, MOCK_AWS_CREDS); + + expect(credentialsHaveChanged).toBe(false); + }); +}) diff --git a/packages/core/src/libraryUtils.ts b/packages/core/src/libraryUtils.ts index 58b6e879146..2c04e24d300 100644 --- a/packages/core/src/libraryUtils.ts +++ b/packages/core/src/libraryUtils.ts @@ -53,6 +53,7 @@ export { AuthVerifiableAttributeKey, AWSCredentials, } from './singleton/Auth/types'; +export { haveCredentialsChanged } from './utils/haveCredentialsChanged'; // Platform & user-agent utilities export { diff --git a/packages/core/src/utils/haveCredentialsChanged.ts b/packages/core/src/utils/haveCredentialsChanged.ts new file mode 100644 index 00000000000..c227b9fe9fb --- /dev/null +++ b/packages/core/src/utils/haveCredentialsChanged.ts @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AWSCredentials } from '../libraryUtils'; + +/** + * Utility for determining if credentials have changed. + * + * @internal + */ +export const haveCredentialsChanged = (cachedCredentials: AWSCredentials, credentials: AWSCredentials) => { + return ( + cachedCredentials.accessKeyId !== credentials.accessKeyId || + cachedCredentials.sessionToken !== credentials.sessionToken || + cachedCredentials.secretAccessKey !== credentials.secretAccessKey + ); +};