Skip to content

Commit a76b594

Browse files
authored
fix(auth): custom userPoolEndpoint cannot be applied on the server-side (#13739)
* chore(auth): add the service client factories into foundation * refactor(auth): use service clients in foundation * chore(auth): refactor existing unit test suites * chore(auth): add unit tests for newly added modules * chore(auth): add missing license header * chore(repo): update sdk type extractio destination for the auth package * chore: resolve most of the comments from cshfang * chore: resolve most of the comments from cshfang cont. * chore: resolve most of the comments from cshfang cont. 2 * chore: flatten out the /core from foundation * chore: update outdate test file name to match the src changes
1 parent 1fea052 commit a76b594

File tree

117 files changed

+2775
-987
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+2775
-987
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { AmplifyUrl } from '@aws-amplify/core/internals/utils';
2+
3+
import { cognitoUserPoolEndpointResolver } from '../../src/foundation/cognitoUserPoolEndpointResolver';
4+
import { COGNITO_IDP_SERVICE_NAME } from '../../src/foundation/constants';
5+
6+
describe('cognitoUserPoolEndpointResolver', () => {
7+
it('should return the Cognito User Pool endpoint', () => {
8+
const region = 'us-west-2';
9+
const { url } = cognitoUserPoolEndpointResolver({ region });
10+
11+
expect(url instanceof AmplifyUrl).toBe(true);
12+
expect(url.toString()).toEqual(
13+
`https://${COGNITO_IDP_SERVICE_NAME}.us-west-2.amazonaws.com/`,
14+
);
15+
});
16+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { composeServiceApi } from '@aws-amplify/core/internals/aws-client-utils/composers';
2+
3+
import * as serviceClients from '../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider';
4+
import { DEFAULT_SERVICE_CLIENT_API_CONFIG } from '../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/constants';
5+
6+
import { mockServiceClientAPIConfig } from './testUtils/data';
7+
8+
jest.mock('@aws-amplify/core/internals/aws-client-utils/composers', () => ({
9+
...jest.requireActual(
10+
'@aws-amplify/core/internals/aws-client-utils/composers',
11+
),
12+
composeServiceApi: jest.fn(),
13+
}));
14+
15+
export const mockComposeServiceApi = jest.mocked(composeServiceApi);
16+
17+
describe('service clients', () => {
18+
const serviceClientFactories = Object.keys(serviceClients);
19+
20+
afterEach(() => {
21+
mockComposeServiceApi.mockClear();
22+
});
23+
24+
test.each(serviceClientFactories)(
25+
'factory `%s` should invoke composeServiceApi with expected parameters',
26+
serviceClientFactory => {
27+
// eslint-disable-next-line import/namespace
28+
serviceClients[serviceClientFactory](mockServiceClientAPIConfig);
29+
30+
expect(mockComposeServiceApi).toHaveBeenCalledWith(
31+
expect.any(Function),
32+
expect.any(Function),
33+
expect.any(Function),
34+
expect.objectContaining({
35+
...DEFAULT_SERVICE_CLIENT_API_CONFIG,
36+
...mockServiceClientAPIConfig,
37+
}),
38+
);
39+
},
40+
);
41+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { unauthenticatedHandler } from '@aws-amplify/core/internals/aws-client-utils';
2+
import { composeTransferHandler } from '@aws-amplify/core/internals/aws-client-utils/composers';
3+
4+
import { cognitoUserPoolTransferHandler } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/handler';
5+
6+
jest.mock('@aws-amplify/core/internals/aws-client-utils/composers');
7+
jest.mock('@aws-amplify/core/internals/aws-client-utils');
8+
9+
const mockComposeTransferHandler = jest.mocked(composeTransferHandler);
10+
const mockUnauthenticatedHandler = jest.mocked(unauthenticatedHandler);
11+
12+
describe('cognitoUserPoolTransferHandler', () => {
13+
beforeAll(() => {
14+
// need to make sure cognitoUserPoolTransferHandler is imported and used in
15+
// the scope of the test
16+
const _ = cognitoUserPoolTransferHandler;
17+
});
18+
19+
it('adds the disableCacheMiddlewareFactory at module loading', () => {
20+
expect(mockComposeTransferHandler).toHaveBeenCalledTimes(1);
21+
22+
const [core, middleware] = mockComposeTransferHandler.mock.calls[0];
23+
24+
expect(core).toStrictEqual(mockUnauthenticatedHandler);
25+
expect(middleware).toHaveLength(1);
26+
27+
const disableCacheMiddlewareFactory = middleware[0] as any;
28+
const disableCacheMiddlewarePendingNext = disableCacheMiddlewareFactory();
29+
30+
const mockNext = jest.fn();
31+
const disableCacheMiddleware = disableCacheMiddlewarePendingNext(mockNext);
32+
const mockRequest = {
33+
headers: {},
34+
};
35+
36+
disableCacheMiddleware(mockRequest);
37+
38+
expect(mockNext).toHaveBeenCalledWith(mockRequest);
39+
expect(mockRequest.headers).toEqual({
40+
'cache-control': 'no-store',
41+
});
42+
});
43+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
HttpResponse,
3+
parseJsonError,
4+
} from '@aws-amplify/core/internals/aws-client-utils';
5+
6+
import { createEmptyResponseDeserializer } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/serde/createEmptyResponseDeserializer';
7+
import { AuthError } from '../../../../../../../src/errors/AuthError';
8+
9+
jest.mock('@aws-amplify/core/internals/aws-client-utils');
10+
11+
const mockParseJsonError = jest.mocked(parseJsonError);
12+
13+
describe('createEmptyResponseDeserializer created response deserializer', () => {
14+
const deserializer = createEmptyResponseDeserializer();
15+
16+
it('returns undefined for 2xx status code', async () => {
17+
const response: HttpResponse = {
18+
statusCode: 200,
19+
body: {
20+
json: () => Promise.resolve({}),
21+
blob: () => Promise.resolve(new Blob()),
22+
text: () => Promise.resolve(''),
23+
},
24+
headers: {},
25+
};
26+
const output = await deserializer(response);
27+
28+
expect(output).toBeUndefined();
29+
});
30+
31+
it('throws AuthError for 4xx status code', async () => {
32+
const expectedErrorName = 'TestError';
33+
const expectedErrorMessage = 'TestErrorMessage';
34+
const expectedError = new Error(expectedErrorMessage);
35+
expectedError.name = expectedErrorName;
36+
37+
mockParseJsonError.mockReturnValueOnce(expectedError as any);
38+
const response: HttpResponse = {
39+
statusCode: 400,
40+
body: {
41+
json: () => Promise.resolve({}),
42+
blob: () => Promise.resolve(new Blob()),
43+
text: () => Promise.resolve(''),
44+
},
45+
headers: {},
46+
};
47+
48+
expect(deserializer(response as any)).rejects.toThrow(
49+
new AuthError({
50+
name: expectedErrorName,
51+
message: expectedErrorMessage,
52+
}),
53+
);
54+
});
55+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
HttpResponse,
3+
parseJsonBody,
4+
parseJsonError,
5+
} from '@aws-amplify/core/internals/aws-client-utils';
6+
7+
import { createUserPoolDeserializer } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/serde/createUserPoolDeserializer';
8+
import { AuthError } from '../../../../../../../src/errors/AuthError';
9+
10+
jest.mock('@aws-amplify/core/internals/aws-client-utils');
11+
12+
const mockParseJsonBody = jest.mocked(parseJsonBody);
13+
const mockParseJsonError = jest.mocked(parseJsonError);
14+
15+
describe('buildUserPoolDeserializer created response deserializer', () => {
16+
const deserializer = createUserPoolDeserializer();
17+
18+
it('returns body for 2xx status code', async () => {
19+
const expectedBody = { test: 'test' };
20+
mockParseJsonBody.mockResolvedValueOnce(expectedBody);
21+
const response: HttpResponse = {
22+
statusCode: 200,
23+
body: {
24+
json: () => Promise.resolve({}),
25+
blob: () => Promise.resolve(new Blob()),
26+
text: () => Promise.resolve(''),
27+
},
28+
headers: {},
29+
};
30+
const output = await deserializer(response);
31+
32+
expect(output).toStrictEqual(expectedBody);
33+
});
34+
35+
it('throws AuthError for 4xx status code', async () => {
36+
const expectedErrorName = 'TestError';
37+
const expectedErrorMessage = 'TestErrorMessage';
38+
const expectedError = new Error(expectedErrorMessage);
39+
expectedError.name = expectedErrorName;
40+
41+
mockParseJsonError.mockReturnValueOnce(expectedError as any);
42+
const response: HttpResponse = {
43+
statusCode: 400,
44+
body: {
45+
json: () => Promise.resolve({}),
46+
blob: () => Promise.resolve(new Blob()),
47+
text: () => Promise.resolve(''),
48+
},
49+
headers: {},
50+
};
51+
52+
expect(deserializer(response as any)).rejects.toThrow(
53+
new AuthError({
54+
name: expectedErrorName,
55+
message: expectedErrorMessage,
56+
}),
57+
);
58+
});
59+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { AmplifyUrl } from '@aws-amplify/core/internals/utils';
2+
3+
import { createUserPoolSerializer } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/serde/createUserPoolSerializer';
4+
5+
describe('createUserPoolSerializer created request serializer', () => {
6+
test.each(['SignUp', 'InitiateAuth', 'RevokeToken'] as const)(
7+
`it serializes requests from operation %s`,
8+
operation => {
9+
const testInput = { testBody: 'testBody' };
10+
const testEndpoint = {
11+
url: new AmplifyUrl('http://test.com'),
12+
};
13+
const serializer = createUserPoolSerializer(operation);
14+
const result = serializer(testInput, testEndpoint);
15+
16+
expect(result).toEqual({
17+
method: 'POST',
18+
url: testEndpoint.url,
19+
headers: {
20+
'content-type': 'application/x-amz-json-1.1',
21+
'x-amz-target': `AWSCognitoIdentityProviderService.${operation}`,
22+
},
23+
body: JSON.stringify(testInput),
24+
});
25+
},
26+
);
27+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ServiceClientFactoryInput } from '../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/types';
2+
3+
export const mockServiceClientAPIConfig: ServiceClientFactoryInput = {
4+
endpointResolver: jest.fn() as jest.MockedFunction<
5+
ServiceClientFactoryInput['endpointResolver']
6+
>,
7+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { AuthError } from '../../../src/errors/AuthError';
2+
import {
3+
getRegionFromIdentityPoolId,
4+
getRegionFromUserPoolId,
5+
} from '../../../src/foundation/parsers/regionParsers';
6+
7+
describe('getRegionFromIdentityPoolId()', () => {
8+
it('returns the region from the identity pool id', () => {
9+
const identityPoolId = 'us-west-2:12345678-1234-1234-1234-123456789012';
10+
const region = getRegionFromIdentityPoolId(identityPoolId);
11+
expect(region).toEqual('us-west-2');
12+
});
13+
14+
test.each([undefined, 'invalid-id-123'])(
15+
`throws an error when the identity pool id is invalid as %p`,
16+
identityPoolId => {
17+
expect(() => getRegionFromIdentityPoolId(identityPoolId)).toThrow(
18+
new AuthError({
19+
name: 'InvalidIdentityPoolIdException',
20+
message: 'Invalid identity pool id provided.',
21+
recoverySuggestion:
22+
'Make sure a valid identityPoolId is given in the config.',
23+
}),
24+
);
25+
},
26+
);
27+
});
28+
29+
describe('getRegionFromUserPoolId()', () => {
30+
it('should return the region from the user pool id', () => {
31+
const userPoolId = 'us-west-2_12345678';
32+
const region = getRegionFromUserPoolId(userPoolId);
33+
expect(region).toEqual('us-west-2');
34+
});
35+
36+
test.each([undefined, 'invalid-id-123'])(
37+
`throws an error when the user pool id is invalid as %p`,
38+
userPoolId => {
39+
expect(() => getRegionFromUserPoolId(userPoolId)).toThrow(
40+
new AuthError({
41+
name: 'InvalidUserPoolId',
42+
message: 'Invalid user pool id provided.',
43+
}),
44+
);
45+
},
46+
);
47+
});

packages/auth/__tests__/providers/cognito/autoSignIn.test.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@ import {
88
signUp,
99
} from '../../../src/providers/cognito';
1010
import { autoSignIn } from '../../../src/providers/cognito/apis/autoSignIn';
11-
import * as signUpClient from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider';
12-
import {
13-
RespondToAuthChallengeCommandOutput,
14-
SignUpCommandOutput,
15-
} from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider/types';
1611
import * as initiateAuthHelpers from '../../../src/providers/cognito/utils/signInHelpers';
1712
import { AuthError } from '../../../src/errors/AuthError';
13+
import { createSignUpClient } from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider';
14+
import { RespondToAuthChallengeCommandOutput } from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/types';
1815

1916
import { authAPITestParams } from './testUtils/authApiTestParams';
2017

@@ -23,6 +20,9 @@ jest.mock('@aws-amplify/core/internals/utils', () => ({
2320
...jest.requireActual('@aws-amplify/core/internals/utils'),
2421
isBrowser: jest.fn(() => false),
2522
}));
23+
jest.mock(
24+
'../../../src/foundation/factories/serviceClients/cognitoIdentityProvider',
25+
);
2626

2727
const authConfig = {
2828
Cognito: {
@@ -35,27 +35,30 @@ Amplify.configure({
3535
Auth: authConfig,
3636
});
3737
describe('Auto sign-in API Happy Path Cases:', () => {
38-
let signUpSpy;
39-
let handleUserSRPAuthflowSpy;
38+
let handleUserSRPAuthFlowSpy: jest.SpyInstance;
39+
40+
const mockSignUp = jest.fn();
41+
const mockCreateSignUpClient = jest.mocked(createSignUpClient);
42+
4043
const { user1 } = authAPITestParams;
4144
beforeEach(async () => {
42-
signUpSpy = jest
43-
.spyOn(signUpClient, 'signUp')
44-
.mockImplementationOnce(
45-
async () => ({ UserConfirmed: true }) as SignUpCommandOutput,
46-
);
45+
mockSignUp.mockResolvedValueOnce({ UserConfirmed: true });
46+
mockCreateSignUpClient.mockReturnValueOnce(mockSignUp);
4747

48-
handleUserSRPAuthflowSpy = jest
48+
handleUserSRPAuthFlowSpy = jest
4949
.spyOn(initiateAuthHelpers, 'handleUserSRPAuthFlow')
5050
.mockImplementationOnce(
5151
async (): Promise<RespondToAuthChallengeCommandOutput> =>
5252
authAPITestParams.RespondToAuthChallengeCommandOutput,
5353
);
5454
});
55+
5556
afterEach(() => {
56-
signUpSpy.mockClear();
57-
handleUserSRPAuthflowSpy.mockClear();
57+
mockSignUp.mockClear();
58+
mockCreateSignUpClient.mockClear();
59+
handleUserSRPAuthFlowSpy.mockClear();
5860
});
61+
5962
test('signUp should enable autoSignIn and return COMPLETE_AUTO_SIGN_IN step', async () => {
6063
const resp = await signUp({
6164
username: user1.username,
@@ -71,13 +74,13 @@ describe('Auto sign-in API Happy Path Cases:', () => {
7174
signUpStep: 'COMPLETE_AUTO_SIGN_IN',
7275
},
7376
});
74-
expect(signUpSpy).toHaveBeenCalledTimes(1);
77+
expect(mockSignUp).toHaveBeenCalledTimes(1);
7578
});
7679

7780
test('Auto sign-in should resolve to a signIn output', async () => {
7881
const signInOutput = await autoSignIn();
7982
expect(signInOutput).toEqual(authAPITestParams.signInResult());
80-
expect(handleUserSRPAuthflowSpy).toHaveBeenCalledTimes(1);
83+
expect(handleUserSRPAuthFlowSpy).toHaveBeenCalledTimes(1);
8184
});
8285
});
8386

0 commit comments

Comments
 (0)