Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(auth): custom userPoolEndpoint cannot be applied on the server-side #13739

Merged
merged 15 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AmplifyUrl } from '@aws-amplify/core/internals/utils';

import { cognitoUserPoolEndpointResolver } from '../../src/foundation/cognitoUserPoolEndpointResolver';
import { COGNITO_IDP_SERVICE_NAME } from '../../src/foundation/constants';

describe('cognitoUserPoolEndpointResolver', () => {
it('should return the Cognito User Pool endpoint', () => {
const region = 'us-west-2';
const { url } = cognitoUserPoolEndpointResolver({ region });

expect(url instanceof AmplifyUrl).toBe(true);
expect(url.toString()).toEqual(
`https://${COGNITO_IDP_SERVICE_NAME}.us-west-2.amazonaws.com/`,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { composeServiceApi } from '@aws-amplify/core/internals/aws-client-utils/composers';

import * as serviceClients from '../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider';
import { DEFAULT_SERVICE_CLIENT_API_CONFIG } from '../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/constants';

import { mockServiceClientAPIConfig } from './testUtils/data';

jest.mock('@aws-amplify/core/internals/aws-client-utils/composers', () => ({
...jest.requireActual(
'@aws-amplify/core/internals/aws-client-utils/composers',
),
composeServiceApi: jest.fn(),
}));

export const mockComposeServiceApi = jest.mocked(composeServiceApi);

describe('service clients', () => {
const serviceClientFactories = Object.keys(serviceClients);

afterEach(() => {
mockComposeServiceApi.mockClear();
});

test.each(serviceClientFactories)(
'factory `%s` should invoke composeServiceApi with expected parameters',
serviceClientFactory => {
// eslint-disable-next-line import/namespace
serviceClients[serviceClientFactory](mockServiceClientAPIConfig);

expect(mockComposeServiceApi).toHaveBeenCalledWith(
expect.any(Function),
expect.any(Function),
expect.any(Function),
expect.objectContaining({
...DEFAULT_SERVICE_CLIENT_API_CONFIG,
...mockServiceClientAPIConfig,
}),
);
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { unauthenticatedHandler } from '@aws-amplify/core/internals/aws-client-utils';
import { composeTransferHandler } from '@aws-amplify/core/internals/aws-client-utils/composers';

import { cognitoUserPoolTransferHandler } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/handler';

jest.mock('@aws-amplify/core/internals/aws-client-utils/composers');
jest.mock('@aws-amplify/core/internals/aws-client-utils');

const mockComposeTransferHandler = jest.mocked(composeTransferHandler);
const mockUnauthenticatedHandler = jest.mocked(unauthenticatedHandler);

describe('cognitoUserPoolTransferHandler', () => {
beforeAll(() => {
// need to make sure cognitoUserPoolTransferHandler is imported and used in
// the scope of the test
const _ = cognitoUserPoolTransferHandler;
});

it('adds the disableCacheMiddlewareFactory at module loading', () => {
expect(mockComposeTransferHandler).toHaveBeenCalledTimes(1);

const [core, middleware] = mockComposeTransferHandler.mock.calls[0];

expect(core).toStrictEqual(mockUnauthenticatedHandler);
expect(middleware).toHaveLength(1);

const disableCacheMiddlewareFactory = middleware[0] as any;
const disableCacheMiddlewarePendingNext = disableCacheMiddlewareFactory();

const mockNext = jest.fn();
const disableCacheMiddleware = disableCacheMiddlewarePendingNext(mockNext);
const mockRequest = {
headers: {},
};

disableCacheMiddleware(mockRequest);

expect(mockNext).toHaveBeenCalledWith(mockRequest);
expect(mockRequest.headers).toEqual({
'cache-control': 'no-store',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
HttpResponse,
parseJsonError,
} from '@aws-amplify/core/internals/aws-client-utils';

import { createEmptyResponseDeserializer } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/serde/createEmptyResponseDeserializer';
import { AuthError } from '../../../../../../../src/errors/AuthError';

jest.mock('@aws-amplify/core/internals/aws-client-utils');

const mockParseJsonError = jest.mocked(parseJsonError);

describe('createEmptyResponseDeserializer created response deserializer', () => {
const deserializer = createEmptyResponseDeserializer();

it('returns undefined for 2xx status code', async () => {
const response: HttpResponse = {
statusCode: 200,
body: {
json: () => Promise.resolve({}),
blob: () => Promise.resolve(new Blob()),
text: () => Promise.resolve(''),
},
headers: {},
};
const output = await deserializer(response);

expect(output).toBeUndefined();
});

it('throws AuthError for 4xx status code', async () => {
const expectedErrorName = 'TestError';
const expectedErrorMessage = 'TestErrorMessage';
const expectedError = new Error(expectedErrorMessage);
expectedError.name = expectedErrorName;

mockParseJsonError.mockReturnValueOnce(expectedError as any);
const response: HttpResponse = {
statusCode: 400,
body: {
json: () => Promise.resolve({}),
blob: () => Promise.resolve(new Blob()),
text: () => Promise.resolve(''),
},
headers: {},
};

expect(deserializer(response as any)).rejects.toThrow(
new AuthError({
name: expectedErrorName,
message: expectedErrorMessage,
}),
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
HttpResponse,
parseJsonBody,
parseJsonError,
} from '@aws-amplify/core/internals/aws-client-utils';

import { createUserPoolDeserializer } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/serde/createUserPoolDeserializer';
import { AuthError } from '../../../../../../../src/errors/AuthError';

jest.mock('@aws-amplify/core/internals/aws-client-utils');

const mockParseJsonBody = jest.mocked(parseJsonBody);
const mockParseJsonError = jest.mocked(parseJsonError);

describe('buildUserPoolDeserializer created response deserializer', () => {
const deserializer = createUserPoolDeserializer();

it('returns body for 2xx status code', async () => {
const expectedBody = { test: 'test' };
mockParseJsonBody.mockResolvedValueOnce(expectedBody);
const response: HttpResponse = {
statusCode: 200,
body: {
json: () => Promise.resolve({}),
blob: () => Promise.resolve(new Blob()),
text: () => Promise.resolve(''),
},
headers: {},
};
const output = await deserializer(response);

expect(output).toStrictEqual(expectedBody);
});

it('throws AuthError for 4xx status code', async () => {
const expectedErrorName = 'TestError';
const expectedErrorMessage = 'TestErrorMessage';
const expectedError = new Error(expectedErrorMessage);
expectedError.name = expectedErrorName;

mockParseJsonError.mockReturnValueOnce(expectedError as any);
const response: HttpResponse = {
statusCode: 400,
body: {
json: () => Promise.resolve({}),
blob: () => Promise.resolve(new Blob()),
text: () => Promise.resolve(''),
},
headers: {},
};

expect(deserializer(response as any)).rejects.toThrow(
new AuthError({
name: expectedErrorName,
message: expectedErrorMessage,
}),
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { AmplifyUrl } from '@aws-amplify/core/internals/utils';

import { createUserPoolSerializer } from '../../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/shared/serde/createUserPoolSerializer';

describe('createUserPoolSerializer created request serializer', () => {
test.each(['SignUp', 'InitiateAuth', 'RevokeToken'] as const)(
`it serializes requests from operation %s`,
operation => {
const testInput = { testBody: 'testBody' };
const testEndpoint = {
url: new AmplifyUrl('http://test.com'),
};
const serializer = createUserPoolSerializer(operation);
const result = serializer(testInput, testEndpoint);

expect(result).toEqual({
method: 'POST',
url: testEndpoint.url,
headers: {
'content-type': 'application/x-amz-json-1.1',
'x-amz-target': `AWSCognitoIdentityProviderService.${operation}`,
},
body: JSON.stringify(testInput),
});
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ServiceClientFactoryInput } from '../../../../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/types';

export const mockServiceClientAPIConfig: ServiceClientFactoryInput = {
endpointResolver: jest.fn() as jest.MockedFunction<
ServiceClientFactoryInput['endpointResolver']
>,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { AuthError } from '../../../src/errors/AuthError';
import {
getRegionFromIdentityPoolId,
getRegionFromUserPoolId,
} from '../../../src/foundation/parsers/regionParsers';

describe('getRegionFromIdentityPoolId()', () => {
it('returns the region from the identity pool id', () => {
const identityPoolId = 'us-west-2:12345678-1234-1234-1234-123456789012';
const region = getRegionFromIdentityPoolId(identityPoolId);
expect(region).toEqual('us-west-2');
});

test.each([undefined, 'invalid-id-123'])(
`throws an error when the identity pool id is invalid as %p`,
identityPoolId => {
expect(() => getRegionFromIdentityPoolId(identityPoolId)).toThrow(
new AuthError({
name: 'InvalidIdentityPoolIdException',
message: 'Invalid identity pool id provided.',
recoverySuggestion:
'Make sure a valid identityPoolId is given in the config.',
}),
);
},
);
});

describe('getRegionFromUserPoolId()', () => {
it('should return the region from the user pool id', () => {
const userPoolId = 'us-west-2_12345678';
const region = getRegionFromUserPoolId(userPoolId);
expect(region).toEqual('us-west-2');
});

test.each([undefined, 'invalid-id-123'])(
`throws an error when the user pool id is invalid as %p`,
userPoolId => {
expect(() => getRegionFromUserPoolId(userPoolId)).toThrow(
new AuthError({
name: 'InvalidUserPoolId',
message: 'Invalid user pool id provided.',
}),
);
},
);
});
37 changes: 20 additions & 17 deletions packages/auth/__tests__/providers/cognito/autoSignIn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ import {
signUp,
} from '../../../src/providers/cognito';
import { autoSignIn } from '../../../src/providers/cognito/apis/autoSignIn';
import * as signUpClient from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider';
import {
RespondToAuthChallengeCommandOutput,
SignUpCommandOutput,
} from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider/types';
import * as initiateAuthHelpers from '../../../src/providers/cognito/utils/signInHelpers';
import { AuthError } from '../../../src/errors/AuthError';
import { createSignUpClient } from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider';
import { RespondToAuthChallengeCommandOutput } from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/types';

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

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

const authConfig = {
Cognito: {
Expand All @@ -35,27 +35,30 @@ Amplify.configure({
Auth: authConfig,
});
describe('Auto sign-in API Happy Path Cases:', () => {
let signUpSpy;
let handleUserSRPAuthflowSpy;
let handleUserSRPAuthFlowSpy: jest.SpyInstance;

const mockSignUp = jest.fn();
const mockCreateSignUpClient = jest.mocked(createSignUpClient);

const { user1 } = authAPITestParams;
beforeEach(async () => {
signUpSpy = jest
.spyOn(signUpClient, 'signUp')
.mockImplementationOnce(
async () => ({ UserConfirmed: true }) as SignUpCommandOutput,
);
mockSignUp.mockResolvedValueOnce({ UserConfirmed: true });
mockCreateSignUpClient.mockReturnValueOnce(mockSignUp);

handleUserSRPAuthflowSpy = jest
handleUserSRPAuthFlowSpy = jest
.spyOn(initiateAuthHelpers, 'handleUserSRPAuthFlow')
.mockImplementationOnce(
async (): Promise<RespondToAuthChallengeCommandOutput> =>
authAPITestParams.RespondToAuthChallengeCommandOutput,
);
});

afterEach(() => {
signUpSpy.mockClear();
handleUserSRPAuthflowSpy.mockClear();
mockSignUp.mockClear();
mockCreateSignUpClient.mockClear();
handleUserSRPAuthFlowSpy.mockClear();
});

test('signUp should enable autoSignIn and return COMPLETE_AUTO_SIGN_IN step', async () => {
const resp = await signUp({
username: user1.username,
Expand All @@ -71,13 +74,13 @@ describe('Auto sign-in API Happy Path Cases:', () => {
signUpStep: 'COMPLETE_AUTO_SIGN_IN',
},
});
expect(signUpSpy).toHaveBeenCalledTimes(1);
expect(mockSignUp).toHaveBeenCalledTimes(1);
});

test('Auto sign-in should resolve to a signIn output', async () => {
const signInOutput = await autoSignIn();
expect(signInOutput).toEqual(authAPITestParams.signInResult());
expect(handleUserSRPAuthflowSpy).toHaveBeenCalledTimes(1);
expect(handleUserSRPAuthFlowSpy).toHaveBeenCalledTimes(1);
});
});

Expand Down
Loading
Loading