Skip to content

Commit

Permalink
Merge branch 'main' into 04-consecutive-updates-take2
Browse files Browse the repository at this point in the history
  • Loading branch information
stocaaro committed Jan 4, 2024
2 parents 23b2ac0 + 44fdfe8 commit b669961
Show file tree
Hide file tree
Showing 65 changed files with 1,742 additions and 767 deletions.
9 changes: 9 additions & 0 deletions .github/integ-config/integ-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,15 @@ tests:
browser: [chrome]
timeout_minutes: 45
retry_count: 10
- test_name: integ_next_sign_in_with_oauth
desc: 'Sign-in with the OAuth flow'
framework: next
category: auth
sample_name: [sign-in-with-oauth]
spec: sign-in-with-oauth
browser: [chrome]
timeout_minutes: 45
retry_count: 10

# DISABLED Angular/Vue tests:
# TODO: delete tests or add custom ui logic to support them.
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -23,7 +31,7 @@ const eventBufferMap: Record<
string,
EventBuffer<KinesisFirehoseBufferEvent>
> = {};
const cachedClients: Record<string, FirehoseClient> = {};
const cachedClients: Record<string, [FirehoseClient, AWSCredentials]> = {};

const createPutRecordsBatchCommand = (
streamName: string,
Expand Down Expand Up @@ -76,18 +84,29 @@ export const getEventBuffer = ({
userAgentValue,
}: KinesisFirehoseEventBufferConfig): EventBuffer<KinesisFirehoseBufferEvent> => {
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<KinesisFirehoseBufferEvent> => {
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);
};

Expand Down
42 changes: 31 additions & 11 deletions packages/analytics/src/providers/kinesis/utils/getEventBuffer.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<string, EventBuffer<KinesisBufferEvent>> = {};
const cachedClients: Record<string, KinesisClient> = {};
const cachedClients: Record<string, [KinesisClient, AWSCredentials]> = {};

const createKinesisPutRecordsCommand = (
streamName: string,
Expand Down Expand Up @@ -67,19 +75,31 @@ export const getEventBuffer = ({
userAgentValue,
}: KinesisEventBufferConfig): EventBuffer<KinesisBufferEvent> => {
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<KinesisBufferEvent> => {
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,7 +25,7 @@ import {
* When a new session is initiated, the previous ones should be released.
* */
const eventBufferMap: Record<string, EventBuffer<PersonalizeBufferEvent>> = {};
const cachedClients: Record<string, PersonalizeEventsClient> = {};
const cachedClients: Record<string, [PersonalizeEventsClient, AWSCredentials]> = {};

const DELIMITER = '#';

Expand Down Expand Up @@ -71,17 +79,30 @@ export const getEventBuffer = ({
userAgentValue,
}: PersonalizeBufferConfig): EventBuffer<PersonalizeBufferEvent> => {
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<PersonalizeBufferEvent> => {
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] =
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"strict": true,
"noImplicitAny": true
},
"include": ["./src"]
"include": ["./src", "../core/src/utils/haveCredentialsChanged.ts"]
}
19 changes: 19 additions & 0 deletions packages/api-rest/__tests__/apis/common/internalPost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,25 @@ describe('internal post', () => {
expect(mockAuthenticatedHandler).not.toHaveBeenCalled();
});

it('should call unauthenticatedHandler if credential is not set', async () => {
mockFetchAuthSession.mockClear();
mockFetchAuthSession.mockRejectedValue(
new Error('Mock error as credentials not configured')
);
await post(mockAmplifyInstance, {
url: apiGatewayUrl,
});
expect(mockUnauthenticatedHandler).toHaveBeenCalledWith(
{
url: apiGatewayUrl,
method: 'POST',
headers: {},
},
expect.anything()
);
expect(mockAuthenticatedHandler).not.toHaveBeenCalled();
});

it('should abort request when cancel is called', async () => {
expect.assertions(4);
let underLyingHandlerReject;
Expand Down
54 changes: 30 additions & 24 deletions packages/api-rest/__tests__/apis/common/publicApis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { AmplifyClassV6 } from '@aws-amplify/core';
import {
authenticatedHandler,
unauthenticatedHandler,
parseJsonError,
} from '@aws-amplify/core/internals/aws-client-utils';

Expand All @@ -25,6 +26,7 @@ import {
jest.mock('@aws-amplify/core/internals/aws-client-utils');

const mockAuthenticatedHandler = authenticatedHandler as jest.Mock;
const mockUnauthenticatedHandler = unauthenticatedHandler as jest.Mock;
const mockFetchAuthSession = jest.fn();
let mockConfig = {
API: {
Expand Down Expand Up @@ -60,24 +62,27 @@ const credentials = {
sessionToken: 'sessionToken',
secretAccessKey: 'secretAccessKey',
};
const mockSuccessResponse = {
statusCode: 200,
headers: {
'response-header': 'response-header-value',
},
body: {
blob: jest.fn(),
json: jest.fn(),
text: jest.fn(),
},
};

describe('public APIs', () => {
beforeEach(() => {
jest.resetAllMocks();
mockFetchAuthSession.mockResolvedValue({
credentials,
});
mockAuthenticatedHandler.mockResolvedValue({
statusCode: 200,
headers: {
'response-header': 'response-header-value',
},
body: {
blob: jest.fn(),
json: jest.fn().mockResolvedValue({ foo: 'bar' }),
text: jest.fn(),
},
});
mockSuccessResponse.body.json.mockResolvedValue({ foo: 'bar' });
mockAuthenticatedHandler.mockResolvedValue(mockSuccessResponse);
mockUnauthenticatedHandler.mockResolvedValue(mockSuccessResponse);
mockGetConfig.mockReturnValue(mockConfig);
});
const APIs = [
Expand Down Expand Up @@ -266,20 +271,21 @@ describe('public APIs', () => {
}
});

it('should throw if credentials are not available', async () => {
expect.assertions(2);
it('should use unauthenticated request if credentials are not available', async () => {
expect.assertions(1);
mockFetchAuthSession.mockResolvedValueOnce({});
try {
await fn(mockAmplifyInstance, {
apiName: 'restApi1',
path: '/items',
}).response;
} catch (error) {
expect(error).toBeInstanceOf(RestApiError);
expect(error).toMatchObject(
validationErrorMap[RestApiValidationErrorCode.NoCredentials]
);
}
await fn(mockAmplifyInstance, {
apiName: 'restApi1',
path: '/items',
}).response;
expect(mockUnauthenticatedHandler).toHaveBeenCalledWith(
expect.objectContaining({
url: new URL(
'https://123.execute-api.us-west-2.amazonaws.com/development/items/123'
),
}),
expect.anything()
);
});

it('should throw when response is not ok', async () => {
Expand Down
25 changes: 21 additions & 4 deletions packages/api-rest/src/apis/common/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import {
jitteredBackoff,
authenticatedHandler,
} from '@aws-amplify/core/internals/aws-client-utils';
import { DocumentType } from '@aws-amplify/core/internals/utils';
import {
AWSCredentials,
DocumentType,
} from '@aws-amplify/core/internals/utils';

import {
logger,
parseRestApiServiceError,
parseSigningInfo,
resolveCredentials,
} from '../../utils';
import { resolveHeaders } from '../../utils/resolveHeaders';
import { RestApiResponse } from '../../types';
Expand Down Expand Up @@ -67,13 +70,13 @@ export const transferHandler = async (

const isIamAuthApplicable = iamAuthApplicable(request, signingServiceInfo);
let response: RestApiResponse;
if (isIamAuthApplicable) {
const credentials = await resolveCredentials(amplify);
if (isIamAuthApplicable && credentials) {
const signingInfoFromUrl = parseSigningInfo(url);
const signingService =
signingServiceInfo?.service ?? signingInfoFromUrl.service;
const signingRegion =
signingServiceInfo?.region ?? signingInfoFromUrl.region;
const credentials = await resolveCredentials(amplify);
response = await authenticatedHandler(request, {
...baseOptions,
credentials,
Expand All @@ -97,3 +100,17 @@ const iamAuthApplicable = (
{ headers }: HttpRequest,
signingServiceInfo?: SigningServiceInfo
) => !headers.authorization && !headers['x-api-key'] && !!signingServiceInfo;

const resolveCredentials = async (
amplify: AmplifyClassV6
): Promise<AWSCredentials | null> => {
try {
const { credentials } = await amplify.Auth.fetchAuthSession();
if (credentials) {
return credentials;
}
} catch (e) {
logger.debug('No credentials available, the request will be unsigned.');
}
return null;
};
Loading

0 comments on commit b669961

Please sign in to comment.