Skip to content

Commit f442277

Browse files
authored
release(required): Amplify JS release (#13753)
2 parents 080f565 + 7700105 commit f442277

File tree

23 files changed

+511
-38
lines changed

23 files changed

+511
-38
lines changed

packages/api-graphql/__tests__/GraphQLAPI.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,4 +1558,60 @@ describe('API test', () => {
15581558
);
15591559
});
15601560
});
1561+
1562+
test('request level custom headers are applied to query string', async () => {
1563+
Amplify.configure({
1564+
API: {
1565+
GraphQL: {
1566+
defaultAuthMode: 'lambda',
1567+
endpoint:
1568+
'https://testaccounturl123456789123.appsync-api.us-east-1.amazonaws.com/graphql',
1569+
region: 'local-host-h4x',
1570+
},
1571+
},
1572+
});
1573+
1574+
let done: Function;
1575+
const mockedFnHasBeenCalled = new Promise(res => (done = res));
1576+
1577+
const spyon_appsync_realtime = jest
1578+
.spyOn(
1579+
AWSAppSyncRealTimeProvider.prototype as any,
1580+
'_initializeRetryableHandshake',
1581+
)
1582+
.mockImplementation(
1583+
jest.fn(() => {
1584+
done(); // resolve promise when called
1585+
}) as any,
1586+
);
1587+
1588+
const query = /* GraphQL */ `
1589+
subscription SubscribeToEventComments {
1590+
subscribeToEventComments {
1591+
eventId
1592+
}
1593+
}
1594+
`;
1595+
1596+
const resolvedUrl =
1597+
'wss://testaccounturl123456789123.appsync-realtime-api.us-east-1.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoiYWJjMTIzNDUiLCJob3N0IjoidGVzdGFjY291bnR1cmwxMjM0NTY3ODkxMjMuYXBwc3luYy1hcGkudXMtZWFzdC0xLmFtYXpvbmF3cy5jb20ifQ==&payload=e30=&x-amz-user-agent=aws-amplify%2F6.4.0%20api%2F1%20framework%2F2&ex-machina=is%20a%20good%20movie';
1598+
1599+
(
1600+
client.graphql(
1601+
{ query },
1602+
{
1603+
'x-amz-user-agent': 'aws-amplify/6.4.0 api/1 framework/2',
1604+
'ex-machina': 'is a good movie',
1605+
// This should NOT get included in the querystring
1606+
Authorization: 'abc12345',
1607+
},
1608+
) as unknown as Observable<object>
1609+
).subscribe();
1610+
1611+
await mockedFnHasBeenCalled;
1612+
1613+
expect(spyon_appsync_realtime).toHaveBeenCalledTimes(1);
1614+
const subscribeOptions = spyon_appsync_realtime.mock.calls[0][0];
1615+
expect(subscribeOptions).toBe(resolvedUrl);
1616+
});
15611617
});

packages/api-graphql/__tests__/internals/generateClient.test.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { Amplify, AmplifyClassV6 } from '@aws-amplify/core';
33
import { generateClient } from '../../src/internals';
44
import configFixture from '../fixtures/modeled/amplifyconfiguration';
55
import { Schema } from '../fixtures/modeled/schema';
6-
import { from } from 'rxjs';
6+
import { Observable, from } from 'rxjs';
77
import {
88
normalizePostGraphqlCalls,
99
expectSubWithHeaders,
1010
expectSubWithHeadersFn,
1111
expectSubWithlibraryConfigHeaders,
1212
mockApiResponse,
1313
} from '../utils/index';
14+
import { AWSAppSyncRealTimeProvider } from '../../src/Providers/AWSAppSyncRealTimeProvider';
1415

1516
const serverManagedFields = {
1617
id: 'some-id',
@@ -332,6 +333,30 @@ describe('generateClient', () => {
332333
expect(normalizePostGraphqlCalls(spy)).toMatchSnapshot();
333334
});
334335

336+
test('with custom client headers - graphql', async () => {
337+
const headers = {
338+
'client-header': 'should exist',
339+
};
340+
341+
const client = generateClient<Schema>({
342+
amplify: Amplify,
343+
headers,
344+
});
345+
346+
await client.graphql({
347+
query: /* GraphQL */ `
348+
query listPosts {
349+
id
350+
}
351+
`,
352+
});
353+
354+
const receivedArgs = normalizePostGraphqlCalls(spy)[0][1];
355+
const receivedHeaders = receivedArgs.options.headers;
356+
357+
expect(receivedHeaders).toEqual(expect.objectContaining(headers));
358+
});
359+
335360
test('with custom client header functions', async () => {
336361
const client = generateClient<Schema>({
337362
amplify: Amplify,
@@ -495,6 +520,39 @@ describe('generateClient', () => {
495520
});
496521
});
497522

523+
test('with client-level custom headers', done => {
524+
const customHeaders = {
525+
'subscription-header': 'should-exist',
526+
};
527+
528+
const client = generateClient<Schema>({
529+
amplify: Amplify,
530+
headers: customHeaders,
531+
});
532+
533+
const spy = jest.fn(() => from([graphqlMessage]));
534+
(raw.GraphQLAPI as any).appSyncRealTime = { subscribe: spy };
535+
536+
client.models.Note.onCreate({
537+
filter: graphqlVariables.filter,
538+
}).subscribe({
539+
next(value) {
540+
expectSubWithHeaders(
541+
spy,
542+
'onCreateNote',
543+
graphqlVariables,
544+
customHeaders,
545+
);
546+
expect(value).toEqual(expect.objectContaining(noteToSend));
547+
done();
548+
},
549+
error(error) {
550+
expect(error).toBeUndefined();
551+
done('bad news!');
552+
},
553+
});
554+
});
555+
498556
test('with a custom header function', done => {
499557
const customHeaders = {
500558
'subscription-header-function': 'should-return-this-header',

packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,44 @@ export class AWSAppSyncRealTimeProvider {
712712
}
713713
}
714714

715+
/**
716+
* Strips out `Authorization` header if present
717+
*/
718+
private _extractNonAuthHeaders(
719+
headers?: AWSAppSyncRealTimeProviderOptions['additionalCustomHeaders'],
720+
): Record<string, string> {
721+
if (!headers) {
722+
return {};
723+
}
724+
725+
if ('Authorization' in headers) {
726+
const { Authorization: _, ...nonAuthHeaders } = headers;
727+
728+
return nonAuthHeaders;
729+
}
730+
731+
return headers;
732+
}
733+
734+
/**
735+
*
736+
* @param headers - http headers
737+
* @returns query string of uri-encoded parameters derived from custom headers
738+
*/
739+
private _queryStringFromCustomHeaders(
740+
headers?: AWSAppSyncRealTimeProviderOptions['additionalCustomHeaders'],
741+
): string {
742+
const nonAuthHeaders = this._extractNonAuthHeaders(headers);
743+
744+
const queryParams: string[] = Object.entries(nonAuthHeaders).map(
745+
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
746+
);
747+
748+
const queryString = queryParams.join('&');
749+
750+
return queryString;
751+
}
752+
715753
private _initializeWebSocketConnection({
716754
appSyncGraphqlEndpoint,
717755
authenticationType,
@@ -749,6 +787,10 @@ export class AWSAppSyncRealTimeProvider {
749787

750788
const payloadQs = base64Encoder.convert(payloadString);
751789

790+
const queryString = this._queryStringFromCustomHeaders(
791+
additionalCustomHeaders,
792+
);
793+
752794
let discoverableEndpoint = appSyncGraphqlEndpoint ?? '';
753795

754796
if (this.isCustomDomain(discoverableEndpoint)) {
@@ -766,7 +808,11 @@ export class AWSAppSyncRealTimeProvider {
766808
.replace('https://', protocol)
767809
.replace('http://', protocol);
768810

769-
const awsRealTimeUrl = `${discoverableEndpoint}?header=${headerQs}&payload=${payloadQs}`;
811+
let awsRealTimeUrl = `${discoverableEndpoint}?header=${headerQs}&payload=${payloadQs}`;
812+
813+
if (queryString !== '') {
814+
awsRealTimeUrl += `&${queryString}`;
815+
}
770816

771817
await this._initializeRetryableHandshake(awsRealTimeUrl);
772818

packages/api-graphql/src/internals/v6.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function graphql<
106106
const internals = getInternals(this as any);
107107
options.authMode = options.authMode || internals.authMode;
108108
options.authToken = options.authToken || internals.authToken;
109+
const headers = additionalHeaders || internals.headers;
109110

110111
/**
111112
* The correctness of these typings depends on correct string branding or overrides.
@@ -116,7 +117,7 @@ export function graphql<
116117
// TODO: move V6Client back into this package?
117118
internals.amplify as any,
118119
options,
119-
additionalHeaders,
120+
headers,
120121
);
121122

122123
return result as any;

packages/api-rest/__tests__/apis/common/internalPost.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,47 @@ describe('internal post', () => {
9191
expect.objectContaining({ region: 'us-west-2', service: 'lambda' }),
9292
);
9393
});
94+
95+
it('should call authenticatedHandler for appsync-api service with default signing name', async () => {
96+
const appsyncApiEndpoint = new URL(
97+
'https://123.appsync-api.us-west-2.amazonaws.com/graphql',
98+
);
99+
await post(mockAmplifyInstance, {
100+
url: appsyncApiEndpoint,
101+
options: {
102+
signingServiceInfo: { region: 'us-east-1' },
103+
},
104+
});
105+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
106+
{
107+
url: appsyncApiEndpoint,
108+
method: 'POST',
109+
headers: {},
110+
},
111+
expect.objectContaining({ region: 'us-east-1', service: 'appsync' }),
112+
);
113+
});
114+
115+
it('should call authenticatedHandler for appsync-api with specified service from signingServiceInfo', async () => {
116+
const appsyncApiEndpoint = new URL(
117+
'https://123.appsync-api.us-west-2.amazonaws.com/graphql',
118+
);
119+
await post(mockAmplifyInstance, {
120+
url: appsyncApiEndpoint,
121+
options: {
122+
signingServiceInfo: { service: 'appsync', region: 'us-east-1' },
123+
},
124+
});
125+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
126+
{
127+
url: appsyncApiEndpoint,
128+
method: 'POST',
129+
headers: {},
130+
},
131+
expect.objectContaining({ region: 'us-east-1', service: 'appsync' }),
132+
);
133+
});
134+
94135
it('should call authenticatedHandler with empty signingServiceInfo', async () => {
95136
await post(mockAmplifyInstance, {
96137
url: apiGatewayUrl,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { HttpRequest } from '@aws-amplify/core/internals/aws-client-utils';
5+
6+
import {
7+
isIamAuthApplicableForGraphQL,
8+
isIamAuthApplicableForRest,
9+
} from '../../src/utils/isIamAuthApplicable';
10+
11+
describe('iamAuthApplicable', () => {
12+
const url = new URL('https://url');
13+
const baseRequest: HttpRequest = {
14+
headers: {},
15+
url,
16+
method: 'put',
17+
};
18+
19+
describe('iamAuthApplicableForGraphQL', () => {
20+
it('should return true if there is no authorization header, no x-api-key header, and signingServiceInfo is provided', () => {
21+
const signingServiceInfo = {};
22+
expect(
23+
isIamAuthApplicableForGraphQL(baseRequest, signingServiceInfo),
24+
).toBe(true);
25+
});
26+
27+
it('should return false if there is an authorization header', () => {
28+
const request = {
29+
...baseRequest,
30+
headers: { authorization: 'SampleToken' },
31+
};
32+
const signingServiceInfo = {};
33+
expect(isIamAuthApplicableForGraphQL(request, signingServiceInfo)).toBe(
34+
false,
35+
);
36+
});
37+
38+
it('should return false if there is an x-api-key header', () => {
39+
const request = { ...baseRequest, headers: { 'x-api-key': 'key' } };
40+
const signingServiceInfo = {};
41+
expect(isIamAuthApplicableForGraphQL(request, signingServiceInfo)).toBe(
42+
false,
43+
);
44+
});
45+
46+
it('should return false if signingServiceInfo is not provided', () => {
47+
expect(isIamAuthApplicableForGraphQL(baseRequest)).toBe(false);
48+
});
49+
});
50+
51+
describe('iamAuthApplicableForPublic', () => {
52+
it('should return true if there is no authorization header and signingServiceInfo is provided', () => {
53+
const signingServiceInfo = {};
54+
expect(isIamAuthApplicableForRest(baseRequest, signingServiceInfo)).toBe(
55+
true,
56+
);
57+
});
58+
59+
it('should return false if there is an authorization header', () => {
60+
const request = {
61+
...baseRequest,
62+
headers: { authorization: 'SampleToken' },
63+
};
64+
const signingServiceInfo = {};
65+
expect(isIamAuthApplicableForRest(request, signingServiceInfo)).toBe(
66+
false,
67+
);
68+
});
69+
70+
it('should return false if signingServiceInfo is not provided', () => {
71+
expect(isIamAuthApplicableForRest(baseRequest)).toBe(false);
72+
});
73+
});
74+
});

0 commit comments

Comments
 (0)