diff --git a/packages/api-graphql/__tests__/GraphQLAPI.test.ts b/packages/api-graphql/__tests__/GraphQLAPI.test.ts index 1dcec2bfc23..43779fee5d6 100644 --- a/packages/api-graphql/__tests__/GraphQLAPI.test.ts +++ b/packages/api-graphql/__tests__/GraphQLAPI.test.ts @@ -3,7 +3,9 @@ import { graphql, cancel, isCancelError } from '../src/internals/v6'; import { Amplify } from 'aws-amplify'; import { Amplify as AmplifyCore } from '@aws-amplify/core'; import * as typedQueries from './fixtures/with-types/queries'; +import * as typedSubscriptions from './fixtures/with-types/subscriptions'; import { expectGet } from './utils/expects'; +import { InternalGraphQLAPIClass } from '../src/internals/InternalGraphQLAPI'; import { __amplify, @@ -668,6 +670,75 @@ describe('API test', () => { ); }); + test('multi-auth default case api-key, using identityPool as auth mode', async () => { + Amplify.configure({ + API: { + GraphQL: { + defaultAuthMode: 'apiKey', + apiKey: 'FAKE-KEY', + endpoint: 'https://localhost/graphql', + region: 'local-host-h4x', + }, + }, + }); + + const threadToGet = { + id: 'some-id', + topic: 'something reasonably interesting', + }; + + const graphqlVariables = { id: 'some-id' }; + + const graphqlResponse = { + data: { + getThread: { + __typename: 'Thread', + ...serverManagedFields, + ...threadToGet, + }, + }, + }; + + const spy = jest + .spyOn((raw.GraphQLAPI as any)._api, 'post') + .mockReturnValue({ + body: { + json: () => graphqlResponse, + }, + }); + + const result: GraphQLResult = await client.graphql({ + query: typedQueries.getThread, + variables: graphqlVariables, + authMode: 'identityPool', + }); + + const thread: GetThreadQuery['getThread'] = result.data?.getThread; + const errors = result.errors; + + expect(errors).toBe(undefined); + expect(thread).toEqual(graphqlResponse.data.getThread); + + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + Auth: expect.any(Object), + configure: expect.any(Function), + getConfig: expect.any(Function), + }), + { + abortController: expect.any(AbortController), + url: new URL('https://localhost/graphql'), + options: expect.objectContaining({ + headers: expect.not.objectContaining({ 'X-Api-Key': 'FAKE-KEY' }), + signingServiceInfo: expect.objectContaining({ + region: 'local-host-h4x', + service: 'appsync', + }), + }), + }, + ); + }); + test('multi-auth default case api-key, using AWS_LAMBDA as auth mode', async () => { Amplify.configure({ API: { @@ -1395,5 +1466,96 @@ describe('API test', () => { }), ); }); + + test('identityPool alias with query', async () => { + Amplify.configure({ + API: { + GraphQL: { + defaultAuthMode: 'apiKey', + apiKey: 'FAKE-KEY', + endpoint: 'https://localhost/graphql', + region: 'local-host-h4x', + }, + }, + }); + + const graphqlVariables = { id: 'some-id' }; + + const graphqlResponse = { + data: { + getThread: {}, + }, + }; + + const spy = jest.spyOn( + InternalGraphQLAPIClass.prototype as any, + '_headerBasedAuth', + ); + + const spy2 = jest + .spyOn((raw.GraphQLAPI as any)._api, 'post') + .mockReturnValue({ + body: { + json: () => graphqlResponse, + }, + }); + + await client.graphql({ + query: typedQueries.getThread, + variables: graphqlVariables, + authMode: 'identityPool', + }); + + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + Auth: expect.any(Object), + configure: expect.any(Function), + getConfig: expect.any(Function), + }), + 'iam', + {}, + ); + }); + + test('identityPool alias with subscription', async () => { + Amplify.configure({ + API: { + GraphQL: { + defaultAuthMode: 'apiKey', + apiKey: 'FAKE-KEY', + endpoint: 'https://localhost/graphql', + region: 'local-host-h4x', + }, + }, + }); + + const graphqlResponse = { + data: { + getThread: {}, + }, + }; + + const spy = jest.spyOn(AWSAppSyncRealTimeProvider.prototype, 'subscribe'); + + const _spy2 = jest + .spyOn((raw.GraphQLAPI as any)._api, 'post') + .mockReturnValue({ + body: { + json: () => graphqlResponse, + }, + }); + + await client.graphql({ + query: typedSubscriptions.onCreateThread, + authMode: 'identityPool', + }); + + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + authenticationType: 'iam', + }), + expect.objectContaining({}), + ); + }); }); }); diff --git a/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts b/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts index 3abde3f469d..37cafbf719e 100644 --- a/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts +++ b/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts @@ -61,6 +61,8 @@ const dispatchApiEvent = (payload: HubPayload) => { Hub.dispatch('api', payload, 'PubSub', AMPLIFY_SYMBOL); }; +// resolved/actual AuthMode values. identityPool gets resolves to IAM upstream in InternalGraphQLAPI._graphqlSubscribe +type ResolvedGraphQLAuthModes = Exclude; export interface ObserverQuery { observer: PubSubContentObserver; query: string; @@ -96,7 +98,7 @@ interface ParsedMessagePayload { export interface AWSAppSyncRealTimeProviderOptions { appSyncGraphqlEndpoint?: string; - authenticationType?: GraphQLAuthMode; + authenticationType?: ResolvedGraphQLAuthModes; query?: string; variables?: Record; apiKey?: string; @@ -935,7 +937,7 @@ export class AWSAppSyncRealTimeProvider { Record | undefined > { const headerHandler: { - [key in GraphQLAuthMode]: ( + [key in ResolvedGraphQLAuthModes]: ( arg0: AWSAppSyncRealTimeAuthInput, ) => Promise> | Record; } = { diff --git a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts index 75dfb2e0f4e..15b89670314 100644 --- a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts +++ b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts @@ -259,7 +259,10 @@ export class InternalGraphQLAPIClass { defaultAuthMode, } = resolveConfig(amplify); - const authMode = explicitAuthMode || defaultAuthMode || 'iam'; + const initialAuthMode = explicitAuthMode || defaultAuthMode || 'iam'; + // identityPool is an alias for iam. TODO: remove 'iam' in v7 + const authMode = + initialAuthMode === 'identityPool' ? 'iam' : initialAuthMode; /** * Retrieve library options from Amplify configuration. @@ -425,13 +428,19 @@ export class InternalGraphQLAPIClass { private _graphqlSubscribe( amplify: AmplifyClassV6, - { query, variables, authMode }: GraphQLOptions, + { query, variables, authMode: explicitAuthMode }: GraphQLOptions, additionalHeaders: CustomHeaders = {}, customUserAgentDetails?: CustomUserAgentDetails, authToken?: string, ): Observable { const config = resolveConfig(amplify); + const initialAuthMode = + explicitAuthMode || config?.defaultAuthMode || 'iam'; + // identityPool is an alias for iam. TODO: remove 'iam' in v7 + const authMode = + initialAuthMode === 'identityPool' ? 'iam' : initialAuthMode; + /** * Retrieve library options from Amplify configuration. * `libraryConfigHeaders` are from the Amplify configuration options, @@ -449,7 +458,7 @@ export class InternalGraphQLAPIClass { variables, appSyncGraphqlEndpoint: config?.endpoint, region: config?.region, - authenticationType: authMode || config?.defaultAuthMode, + authenticationType: authMode, apiKey: config?.apiKey, additionalHeaders, authToken, diff --git a/packages/core/src/singleton/API/types.ts b/packages/core/src/singleton/API/types.ts index 418b2f65a0e..20dd5bd3dfc 100644 --- a/packages/core/src/singleton/API/types.ts +++ b/packages/core/src/singleton/API/types.ts @@ -81,7 +81,9 @@ export type GraphQLAuthMode = | 'apiKey' | 'oidc' | 'userPool' + // @deprecated; use 'identityPool' | 'iam' + | 'identityPool' | 'lambda' | 'none';