Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/callable-docs-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ jobs:
run: |
git checkout -b $TEMP_BRANCH_NAME
git push origin $TEMP_BRANCH_NAME
gh pr create -B main -H $TEMP_BRANCH_NAME --title 'chore: amplify-js api references update' --body 'Merge the api references changes from the most recent release.'
gh pr create -B pre-prod/main -H $TEMP_BRANCH_NAME --title 'chore: amplify-js api references update' --body 'Merge the api references changes from the most recent release.'
4 changes: 2 additions & 2 deletions .github/workflows/on-schedule-canary-test.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
on:
# Tests scheduled at 4pm(UTC) / 9am(PDT) everyday
# Tests scheduled at 3pm(UTC) / 8am(PDT) everyday
# default supported timezone is UTC
schedule:
- cron: '0 16 * * *'
- cron: '0 15 * * *'

jobs:
canaries:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ xcuserdata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
**/xcshareddata/WorkspaceSettings.xcsettings
**/.xcode.env.local

### Coverage ###
coverage/
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,4 @@
"tar": "6.2.1",
"cross-spawn": "7.0.5"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ describe('createUrlSearchParamsForSignInSignUp', () => {
'redirect_uri=https%3A%2F%2Fexample.com%2Fsignin&response_type=code&client_id=userPoolClientId&scope=openid&state=state&code_challenge=code_challenge&code_challenge_method=S256&identity_provider=Google',
);
});

it('returns URLSearchParams with the correct values when lang query parameter is supplied', () => {
const url = 'https://example.com?lang=es';

const result = createUrlSearchParamsForSignInSignUp({
url,
oAuthConfig,
userPoolClientId,
state,
origin,
codeVerifier,
});

expect(result.toString()).toBe(
'redirect_uri=https%3A%2F%2Fexample.com%2Fsignin&response_type=code&client_id=userPoolClientId&scope=openid&state=state&code_challenge=code_challenge&code_challenge_method=S256&lang=es',
);
});
});

describe('createUrlSearchParamsForTokenExchange', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { generateCodeVerifier } from 'aws-amplify/adapter-core';

import { resolveIdentityProviderFromUrl } from './resolveIdentityProviderFromUrl';
import { resolveRedirectSignInUrl } from './resolveRedirectUrl';
import { getSearchParamValueFromUrl } from './getSearchParamValueFromUrl';

export const createUrlSearchParamsForSignInSignUp = ({
url,
Expand All @@ -23,6 +24,7 @@ export const createUrlSearchParamsForSignInSignUp = ({
codeVerifier: ReturnType<typeof generateCodeVerifier>;
}): URLSearchParams => {
const resolvedProvider = resolveIdentityProviderFromUrl(url);
const lang = getSearchParamValueFromUrl(url, 'lang');

const redirectUrlSearchParams = new URLSearchParams({
redirect_uri: resolveRedirectSignInUrl(origin, oAuthConfig),
Expand All @@ -38,6 +40,10 @@ export const createUrlSearchParamsForSignInSignUp = ({
redirectUrlSearchParams.append('identity_provider', resolvedProvider);
}

if (lang) {
redirectUrlSearchParams.append('lang', lang);
}

return redirectUrlSearchParams;
};

Expand Down
76 changes: 47 additions & 29 deletions packages/api-graphql/__tests__/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ describe('Events client', () => {
});
});

const authModes: GraphQLAuthMode[] = [
'apiKey',
'userPool',
'oidc',
'iam',
'lambda',
'none',
const authModeConfigs: { authMode: GraphQLAuthMode, apiKey?: string, authToken?: string }[] = [
{ authMode: 'apiKey', apiKey: 'testAPIKey' },
{ authMode: 'userPool', authToken: 'userPoolToken' },
{ authMode: 'oidc', authToken: 'oidcToken' },
{ authMode: 'iam', authToken: 'iamToken' },
{ authMode: 'lambda', authToken: 'lambdaToken' },
{ authMode: 'none' },
];

describe('channel', () => {
Expand All @@ -124,12 +124,16 @@ describe('Events client', () => {
mockProvider = AppSyncEventProvider;
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
await events.connect('/', { authMode });
for (const authConfig of authModeConfigs) {
const {authMode: authenticationType, ...config} = authConfig
test(`connect auth override: ${authConfig.authMode}`, async () => {
const channel = await events.connect('/', authConfig);

expect(mockProvider.connect).toHaveBeenCalledWith(
expect.objectContaining({ authenticationType: authMode }),
expect.objectContaining({
authenticationType,
...config
}),
);
});
}
Expand All @@ -153,20 +157,24 @@ describe('Events client', () => {
mockProvider = AppSyncEventProvider;
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
const channel = await events.connect('/');
for (const authConfig of authModeConfigs) {
const {authMode: authenticationType, ...config} = authConfig

channel.subscribe(
test(`subscription auth override: ${authConfig.authMode}`, async () => {
const channel = await events.connect('/');
channel.subscribe(
{
next: data => void data,
error: error => void error,
},
{ authMode },
);

expect(mockSubscribeObservable).toHaveBeenCalledWith(
expect.objectContaining({ authenticationType: authMode }),
authConfig
)

expect(mockProvider.subscribe).toHaveBeenCalledWith(
expect.objectContaining({
authenticationType,
...config
}),
);
});
}
Expand Down Expand Up @@ -195,14 +203,21 @@ describe('Events client', () => {
mockProvider = AppSyncEventProvider;
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
const channel = await events.connect('/');
for (const authConfig of authModeConfigs) {
const {authMode: authenticationType, ...config} = authConfig

channel.publish({ some: 'data' }, { authMode });
test(`publish auth override: ${authConfig.authMode}`, async () => {
const channel = await events.connect('/');
channel.publish(
"Test message",
authConfig
)

expect(mockProvider.publish).toHaveBeenCalledWith(
expect.objectContaining({ authenticationType: authMode }),
expect.objectContaining({
authenticationType,
...config
}),
);
});
}
Expand Down Expand Up @@ -230,16 +245,19 @@ describe('Events client', () => {
);
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
await events.post('/', { test: 'data' }, { authMode });
for (const authConfig of authModeConfigs) {
const {authMode: authenticationType, ...config} = authConfig

test(`auth override: ${authenticationType}`, async () => {
await events.post('/', { test: 'data' }, authConfig);

expect(mockReq).toHaveBeenCalledWith(
Amplify,
expect.objectContaining({
query: '/',
variables: ['{"test":"data"}'],
authenticationType: authMode,
authenticationType,
...config
}),
{},
abortController,
Expand Down
16 changes: 13 additions & 3 deletions packages/api-graphql/src/internals/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { configure, normalizeAuth, serializeEvents } from './utils';
import type {
EventsChannel,
EventsOptions,
ProviderOptions,
PublishResponse,
PublishedEvent,
SubscriptionObserver,
Expand Down Expand Up @@ -44,12 +45,14 @@ async function connect(
channel: string,
options?: EventsOptions,
): Promise<EventsChannel> {
const providerOptions = configure();
const providerOptions: ProviderOptions = configure();

providerOptions.authenticationType = normalizeAuth(
options?.authMode,
providerOptions.authenticationType,
);
providerOptions.apiKey = options?.apiKey || providerOptions.apiKey;
providerOptions.authToken = options?.authToken || providerOptions.authToken;

await eventProvider.connect(providerOptions);

Expand All @@ -70,6 +73,9 @@ async function connect(
subOptions?.authMode,
subscribeOptions.authenticationType,
);
subscribeOptions.apiKey = subOptions?.apiKey || subscribeOptions.apiKey;
subscribeOptions.authToken =
subOptions?.authToken || subscribeOptions.authToken;

_subscription = eventProvider
.subscribe(subscribeOptions)
Expand All @@ -94,6 +100,9 @@ async function connect(
pubOptions?.authMode,
publishOptions.authenticationType,
);
publishOptions.apiKey = pubOptions?.apiKey || publishOptions.apiKey;
publishOptions.authToken =
pubOptions?.authToken || publishOptions.authToken;

return eventProvider.publish(publishOptions);
};
Expand Down Expand Up @@ -141,11 +150,13 @@ async function post(
event: DocumentType | DocumentType[],
options?: EventsOptions,
): Promise<void | PublishedEvent[]> {
const providerOptions = configure();
const providerOptions: ProviderOptions = configure();
providerOptions.authenticationType = normalizeAuth(
options?.authMode,
providerOptions.authenticationType,
);
providerOptions.apiKey = options?.apiKey || providerOptions.apiKey;
providerOptions.authToken = options?.authToken || providerOptions.authToken;

// trailing slash required in publish
const normalizedChannelName = channel[0] === '/' ? channel : `/${channel}`;
Expand All @@ -154,7 +165,6 @@ async function post(
...providerOptions,
query: normalizedChannelName,
variables: serializeEvents(event),
authToken: options?.authToken,
};

const abortController = new AbortController();
Expand Down
12 changes: 12 additions & 0 deletions packages/api-graphql/src/internals/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export type ResolvedGraphQLAuthModes = Exclude<GraphQLAuthMode, 'identityPool'>;
export interface EventsOptions {
authMode?: GraphQLAuthMode;
authToken?: string;
apiKey?: string;
}

export interface PublishedEvent {
Expand All @@ -95,3 +96,14 @@ export interface PublishResponse {
failed: PublishedEvent[];
successful: PublishedEvent[];
}

interface EventsConfigure {
appSyncGraphqlEndpoint: string;
region?: string;
authenticationType: ResolvedGraphQLAuthModes;
apiKey?: string;
}

export type ProviderOptions = EventsConfigure & {
authToken?: string;
};
28 changes: 26 additions & 2 deletions packages/aws-amplify/__tests__/initSingleton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import { AmplifyOutputs } from '@aws-amplify/core/internals/utils';

import {
CognitoAWSCredentialsAndIdentityIdProvider,
DefaultIdentityIdStore,
cognitoCredentialsProvider,
cognitoUserPoolsTokenProvider,
} from '../src/auth/cognito';
Expand All @@ -23,6 +25,8 @@ jest.mock('../src/auth/cognito', () => ({
setKeyValueStorage: jest.fn(),
},
cognitoCredentialsProvider: jest.fn(),
DefaultIdentityIdStore: jest.fn(),
CognitoAWSCredentialsAndIdentityIdProvider: jest.fn(),
}));

const mockCognitoUserPoolsTokenProviderSetAuthConfig =
Expand All @@ -32,6 +36,10 @@ const mockCognitoUserPoolsTokenProviderSetKeyValueStorage =
const mockAmplifySingletonConfigure = AmplifySingleton.configure as jest.Mock;
const mockAmplifySingletonGetConfig = AmplifySingleton.getConfig as jest.Mock;
const MockCookieStorage = CookieStorage as jest.Mock;
const MockDefaultIdentityIdStore = jest.mocked(DefaultIdentityIdStore);
const MockCognitoAWSCredentialsAndIdentityIdProvider = jest.mocked(
CognitoAWSCredentialsAndIdentityIdProvider,
);

const mockResourceConfig: ResourcesConfig = {
Auth: {
Expand All @@ -50,8 +58,16 @@ const mockResourceConfig: ResourcesConfig = {

describe('initSingleton (DefaultAmplify)', () => {
const mockCookieStorageInstance = {};
const mockCognitoAWSCredentialsAndIdentityIdProviderInstance = {} as any;
const mockDefaultIdentityIdStoreInstance = {} as any;
beforeAll(() => {
MockCookieStorage.mockImplementation(() => mockCookieStorageInstance);
MockDefaultIdentityIdStore.mockImplementation(
() => mockDefaultIdentityIdStoreInstance,
);
MockCognitoAWSCredentialsAndIdentityIdProvider.mockImplementation(
() => mockCognitoAWSCredentialsAndIdentityIdProviderInstance,
);
});
beforeEach(() => {
mockAmplifySingletonConfigure.mockImplementation((_, libraryOptions) => {
Expand All @@ -64,6 +80,8 @@ describe('initSingleton (DefaultAmplify)', () => {

afterEach(() => {
MockCookieStorage.mockClear();
MockCognitoAWSCredentialsAndIdentityIdProvider.mockClear();
MockDefaultIdentityIdStore.mockClear();
mockCognitoUserPoolsTokenProviderSetAuthConfig.mockReset();
mockCognitoUserPoolsTokenProviderSetKeyValueStorage.mockReset();
mockAmplifySingletonConfigure.mockReset();
Expand Down Expand Up @@ -252,13 +270,20 @@ describe('initSingleton (DefaultAmplify)', () => {
expect(
mockCognitoUserPoolsTokenProviderSetKeyValueStorage,
).toHaveBeenCalledWith(mockCookieStorageInstance);
expect(MockDefaultIdentityIdStore).toHaveBeenCalledWith(
mockCookieStorageInstance,
);
expect(
MockCognitoAWSCredentialsAndIdentityIdProvider,
).toHaveBeenCalledWith(mockDefaultIdentityIdStoreInstance);
expect(mockAmplifySingletonConfigure).toHaveBeenCalledWith(
mockResourceConfig,
{
...libraryOptions,
Auth: {
tokenProvider: cognitoUserPoolsTokenProvider,
credentialsProvider: cognitoCredentialsProvider,
credentialsProvider:
mockCognitoAWSCredentialsAndIdentityIdProviderInstance,
},
},
);
Expand Down Expand Up @@ -345,7 +370,6 @@ describe('initSingleton (DefaultAmplify)', () => {
expect(
mockCognitoUserPoolsTokenProviderSetAuthConfig,
).not.toHaveBeenCalled();
expect(MockCookieStorage).not.toHaveBeenCalled();
expect(
mockCognitoUserPoolsTokenProviderSetKeyValueStorage,
).not.toHaveBeenCalled();
Expand Down
Loading
Loading