Skip to content

Commit 1390ec7

Browse files
authored
feat(auth): migrate auth APIs to AmplifyContext (#14836)
* feat(auth): migrate auth APIs to AmplifyContext (B-auth split) - Add AmplifyContext overloads to all public auth APIs (signIn, signUp, signOut, getCurrentUser, fetchUserAttributes, confirmSignIn, etc.) - Public APIs use resolveCtxArgs with getGlobalContext() fallback - Server wrappers (server/getCurrentUser, server/fetchUserAttributes) take AmplifyContext explicitly — no global fallback - Internal signIn variants take (ctx, input) directly - Utilities (assertUserNotAuthenticated, dispatchSignedInHubEvent) accept ctx - Replace Amplify.getConfig() with ctx.resourcesConfig throughout - Replace AmplifyClassV6 with AmplifyContext in internal APIs - Add createMockAmplifyContext test utility - Update affected test files to use mock context * fix: use resolveCtxArgs<[]> for no-input overloads resolveCtxArgs generic constraint requires T extends unknown[]. undefined does not satisfy this - use empty tuple [] instead. * fix: use ctx.fetchAuthSession() instead of singleton fetchAuthSession() The fetchAuthSession helper from core/internals/utils expects AmplifyClass, not AmplifyContext. Use the context's own fetchAuthSession method directly. * fix: server wrappers accept both AmplifyContext and ContextSpec Maintain backward compatibility with adapter-nextjs which still uses runWithAmplifyServerContext + ContextSpec pattern. Server wrappers now detect whether they received an AmplifyContext or ContextSpec and resolve accordingly. * fix: resolve lint and formatting issues - Remove unused Amplify imports from test files - Fix prettier formatting (line length, indentation) * fix: remove tsconfig.tsbuildinfo build artifact * fix: update auth tests to use AmplifyContext directly Migrate test mocking from Amplify.getConfig() singleton to passing mockCtx directly via createMockAmplifyContext(). This aligns tests with the new resolveCtxArgs pattern where APIs accept an optional AmplifyContext as their first argument. - Remove jest.mock('@aws-amplify/core') in favor of direct ctx passing - Replace fetchAuthSession singleton mock with ctx.fetchAuthSession - Use customCtx for endpoint override tests - Fix autoSignInUserConfirmed to match updated signUpHelpers routing - Add Amplify.configure() for tests that reach getSignInResult (still uses singleton internally) * fix(auth): address PR review comments - Restore USER_AUTH branch in signUpHelpers auto-sign-in: route directly to signInWithUserAuth(getGlobalContext(), input) instead of signIn(), which called resetAutoSignIn() and dropped the primed autoSignInStore session (regression). Re-add the dropped session assertion in autoSignIn.test.ts and correct autoSignInUserConfirmed.test.ts to assert the restored behavior. - Extract duplicated server context resolution into resolveServerContext shared util, used by server/getCurrentUser and server/fetchUserAttributes. - Add clearGlobalContext() cleanup in afterAll across test files that set a global context; make the Cognito ASF suite self-contained instead of relying on cross-describe context leakage. * fix(auth): correct import order in autoSignInUserConfirmed test Move @aws-amplify/core/internals/utils import before relative src imports to satisfy eslint import/order. * test(rtn-passkeys): use iPhone 17 simulator for iOS unit tests macos-latest runner's Xcode no longer ships the iPhone 16 simulator by default; target iPhone 17 so the xcodebuild test destination resolves. * fix(auth): address review feedback on context threading - Thread ctx:AmplifyContext through getSignInResult and handleWebAuthnSignInResult (incl. WEB_AUTHN recursion) so the MFA_SETUP/TOTP associateSoftwareToken call uses the same context as the rest of the sign-in flow instead of the global singleton config. Updated all 6 callers to pass ctx. - signOut: use ctx.clearCredentials() instead of the singleton in the non-OAuth branch so per-request credential state is cleared. - signUpHelpers: pass getGlobalContext() explicitly in the non-USER_AUTH auto-sign-in branch for symmetry with the USER_AUTH branch. - Add focused unit test for resolveServerContext covering both the AmplifyContext and legacy ContextSpec branches. - Collapse verbose comment in resolveServerContext to one line.
1 parent 5b0f745 commit 1390ec7

78 files changed

Lines changed: 1216 additions & 794 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { getAmplifyServerContext } from '@aws-amplify/core/internals/adapter-core';
5+
6+
import { resolveServerContext } from '../../../../../src/providers/cognito/apis/server/resolveServerContext';
7+
import { createMockAmplifyContext } from '../../../../testUtils/mockAmplifyContext';
8+
9+
jest.mock('@aws-amplify/core/internals/adapter-core');
10+
11+
const mockGetAmplifyServerContext = getAmplifyServerContext as jest.Mock;
12+
13+
describe('resolveServerContext', () => {
14+
beforeEach(() => {
15+
jest.clearAllMocks();
16+
});
17+
18+
it('returns the AmplifyContext unchanged when it has a resourcesConfig', () => {
19+
const ctx = createMockAmplifyContext({
20+
Auth: {
21+
Cognito: {
22+
userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398',
23+
userPoolId: 'us-west-2_zzzzz',
24+
},
25+
},
26+
});
27+
28+
expect(resolveServerContext(ctx)).toBe(ctx);
29+
expect(mockGetAmplifyServerContext).not.toHaveBeenCalled();
30+
});
31+
32+
it('resolves a legacy ContextSpec via getAmplifyServerContext(...).amplify', () => {
33+
const sentinel = { resolved: 'amplify-server-context' } as any;
34+
mockGetAmplifyServerContext.mockReturnValue({ amplify: sentinel });
35+
const contextSpec = { token: { value: 'token' } } as any;
36+
37+
expect(resolveServerContext(contextSpec)).toBe(sentinel);
38+
expect(mockGetAmplifyServerContext).toHaveBeenCalledWith(contextSpec);
39+
});
40+
});

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { Amplify } from 'aws-amplify';
4+
import { Amplify } from '@aws-amplify/core';
5+
import { clearGlobalContext } from '@aws-amplify/core/internals/utils';
56

6-
import {
7-
cognitoUserPoolsTokenProvider,
8-
confirmSignUp,
9-
signUp,
10-
} from '../../../src/providers/cognito';
7+
import { confirmSignUp, signUp } from '../../../src/providers/cognito';
118
import {
129
autoSignIn,
1310
resetAutoSignIn,
@@ -45,10 +42,8 @@ const authConfig = {
4542
userPoolId: 'us-west-2_zzzzz',
4643
},
4744
};
48-
cognitoUserPoolsTokenProvider.setAuthConfig(authConfig);
49-
Amplify.configure({
50-
Auth: authConfig,
51-
});
45+
46+
Amplify.configure({ Auth: authConfig });
5247

5348
const { user1 } = authAPITestParams;
5449

@@ -73,6 +68,10 @@ describe('autoSignIn()', () => {
7368
// to get around debounce on autoSignIn() APIs
7469
jest.useFakeTimers();
7570

71+
afterAll(() => {
72+
clearGlobalContext();
73+
});
74+
7675
describe('handleUserSRPAuthFlow', () => {
7776
beforeEach(() => {
7877
mockCreateSignUpClient.mockReturnValueOnce(mockSignUp);

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

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,23 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { Amplify } from '@aws-amplify/core';
5-
64
import { AuthError } from '../../../src/errors/AuthError';
75
import { AuthValidationErrorCode } from '../../../src/errors/types/validation';
86
import { confirmResetPassword } from '../../../src/providers/cognito';
97
import { ConfirmForgotPasswordException } from '../../../src/providers/cognito/types/errors';
108
import { createConfirmForgotPasswordClient } from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider';
119
import { createCognitoUserPoolEndpointResolver } from '../../../src/providers/cognito/factories';
10+
import { createMockAmplifyContext } from '../../testUtils/mockAmplifyContext';
1211

1312
import { authAPITestParams } from './testUtils/authApiTestParams';
1413
import { getMockError } from './testUtils/data';
15-
import { setUpGetConfig } from './testUtils/setUpGetConfig';
16-
17-
jest.mock('@aws-amplify/core', () => ({
18-
...(jest.createMockFromModule('@aws-amplify/core') as object),
19-
Amplify: { getConfig: jest.fn(() => ({})) },
20-
}));
21-
jest.mock('@aws-amplify/core/internals/utils', () => ({
22-
...jest.requireActual('@aws-amplify/core/internals/utils'),
23-
isBrowser: jest.fn(() => false),
24-
}));
14+
2515
jest.mock(
2616
'../../../src/foundation/factories/serviceClients/cognitoIdentityProvider',
2717
);
2818
jest.mock('../../../src/providers/cognito/factories');
2919

3020
describe('confirmResetPassword', () => {
31-
// assert mocks
3221
const mockConfirmForgotPassword = jest.fn();
3322
const mockCreateConfirmResetPasswordClient = jest.mocked(
3423
createConfirmForgotPasswordClient,
@@ -37,8 +26,14 @@ describe('confirmResetPassword', () => {
3726
createCognitoUserPoolEndpointResolver,
3827
);
3928

40-
beforeAll(() => {
41-
setUpGetConfig(Amplify);
29+
const mockCtx = createMockAmplifyContext({
30+
Auth: {
31+
Cognito: {
32+
userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398',
33+
userPoolId: 'us-west-2_zzzzz',
34+
identityPoolId: 'us-west-2:xxxxxx',
35+
},
36+
},
4237
});
4338

4439
beforeEach(() => {
@@ -58,14 +53,17 @@ describe('confirmResetPassword', () => {
5853

5954
it('should call the confirmForgotPassword and return void', async () => {
6055
await expect(
61-
confirmResetPassword(authAPITestParams.confirmResetPasswordRequest),
56+
confirmResetPassword(
57+
mockCtx,
58+
authAPITestParams.confirmResetPasswordRequest,
59+
),
6260
).resolves.toBeUndefined();
6361
expect(mockConfirmForgotPassword).toHaveBeenCalled();
6462
});
6563

6664
it('invokes createCognitoUserPoolEndpointResolver with expected endpointOverride', async () => {
6765
const expectedUserPoolEndpoint = 'https://my-custom-endpoint.com';
68-
jest.mocked(Amplify.getConfig).mockReturnValueOnce({
66+
const customCtx = createMockAmplifyContext({
6967
Auth: {
7068
Cognito: {
7169
userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398',
@@ -76,15 +74,18 @@ describe('confirmResetPassword', () => {
7674
},
7775
});
7876

79-
await confirmResetPassword(authAPITestParams.confirmResetPasswordRequest);
77+
await confirmResetPassword(
78+
customCtx,
79+
authAPITestParams.confirmResetPasswordRequest,
80+
);
8081

8182
expect(mockCreateCognitoUserPoolEndpointResolver).toHaveBeenCalledWith({
8283
endpointOverride: expectedUserPoolEndpoint,
8384
});
8485
});
8586

8687
it('should contain clientMetadata from request', async () => {
87-
await confirmResetPassword({
88+
await confirmResetPassword(mockCtx, {
8889
username: 'username',
8990
newPassword: 'password',
9091
confirmationCode: 'code',
@@ -107,7 +108,7 @@ describe('confirmResetPassword', () => {
107108
it('should throw an error when username is empty', async () => {
108109
expect.assertions(2);
109110
try {
110-
await confirmResetPassword({
111+
await confirmResetPassword(mockCtx, {
111112
username: '',
112113
newPassword: 'password',
113114
confirmationCode: 'code',
@@ -123,7 +124,7 @@ describe('confirmResetPassword', () => {
123124
it('should throw an error when newPassword is empty', async () => {
124125
expect.assertions(2);
125126
try {
126-
await confirmResetPassword({
127+
await confirmResetPassword(mockCtx, {
127128
username: 'username',
128129
newPassword: '',
129130
confirmationCode: 'code',
@@ -139,7 +140,7 @@ describe('confirmResetPassword', () => {
139140
it('should throw an error when confirmationCode is empty', async () => {
140141
expect.assertions(2);
141142
try {
142-
await confirmResetPassword({
143+
await confirmResetPassword(mockCtx, {
143144
username: 'username',
144145
newPassword: 'password',
145146
confirmationCode: '',
@@ -160,7 +161,10 @@ describe('confirmResetPassword', () => {
160161
);
161162
});
162163
try {
163-
await confirmResetPassword(authAPITestParams.confirmResetPasswordRequest);
164+
await confirmResetPassword(
165+
mockCtx,
166+
authAPITestParams.confirmResetPasswordRequest,
167+
);
164168
} catch (error: any) {
165169
expect(error).toBeInstanceOf(AuthError);
166170
expect(error.name).toBe(
@@ -176,7 +180,7 @@ describe('confirmResetPassword', () => {
176180
},
177181
};
178182

179-
await confirmResetPassword({
183+
await confirmResetPassword(mockCtx, {
180184
username: 'username',
181185
newPassword: 'password',
182186
confirmationCode: 'code',

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
1-
import { Amplify } from '@aws-amplify/core';
2-
31
import { AuthError } from '../../../src/errors/AuthError';
42
import { AuthValidationErrorCode } from '../../../src/errors/types/validation';
53
import { confirmSignIn } from '../../../src/providers/cognito/apis/confirmSignIn';
64
import { RespondToAuthChallengeException } from '../../../src/providers/cognito/types/errors';
75
import { signInStore } from '../../../src/client/utils/store';
86
import { AuthErrorCodes } from '../../../src/common/AuthErrorStrings';
97
import { createRespondToAuthChallengeClient } from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider';
8+
import { createMockAmplifyContext } from '../../testUtils/mockAmplifyContext';
109

1110
import { getMockError } from './testUtils/data';
12-
import { setUpGetConfig } from './testUtils/setUpGetConfig';
1311
import { authAPITestParams } from './testUtils/authApiTestParams';
1412

15-
jest.mock('@aws-amplify/core', () => ({
16-
...(jest.createMockFromModule('@aws-amplify/core') as object),
17-
Amplify: { getConfig: jest.fn(() => ({})) },
18-
}));
1913
jest.mock('../../../src/client/utils/store');
2014
jest.mock(
2115
'../../../src/foundation/factories/serviceClients/cognitoIdentityProvider',
2216
);
2317
jest.mock('../../../src/providers/cognito/factories');
2418

19+
const mockCtx = createMockAmplifyContext({
20+
Auth: {
21+
Cognito: {
22+
userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398',
23+
userPoolId: 'us-west-2_zzzzz',
24+
identityPoolId: 'us-west-2:xxxxxx',
25+
},
26+
},
27+
});
28+
2529
describe('confirmSignIn API error path cases:', () => {
2630
const challengeName = 'SELECT_MFA_TYPE';
2731
const signInSession = '1234234232';
@@ -34,7 +38,6 @@ describe('confirmSignIn API error path cases:', () => {
3438
);
3539

3640
beforeAll(() => {
37-
setUpGetConfig(Amplify);
3841
mockStoreGetState.mockReturnValue({
3942
username,
4043
challengeName,
@@ -56,7 +59,7 @@ describe('confirmSignIn API error path cases:', () => {
5659
it('confirmSignIn API should throw an error when challengeResponse is empty', async () => {
5760
expect.assertions(2);
5861
try {
59-
await confirmSignIn({ challengeResponse: '' });
62+
await confirmSignIn(mockCtx, { challengeResponse: '' });
6063
} catch (error: any) {
6164
expect(error).toBeInstanceOf(AuthError);
6265
expect(error.name).toBe(AuthValidationErrorCode.EmptyChallengeResponse);
@@ -66,7 +69,7 @@ describe('confirmSignIn API error path cases:', () => {
6669
it('should throw an error when sign-in step is CONTINUE_SIGN_IN_WITH_MFA_SELECTION and challengeResponse is not "SMS", "TOTP", or "EMAIL"', async () => {
6770
expect.assertions(2);
6871
try {
69-
await confirmSignIn({ challengeResponse: 'NO_SMS' });
72+
await confirmSignIn(mockCtx, { challengeResponse: 'NO_SMS' });
7073
} catch (error: any) {
7174
expect(error).toBeInstanceOf(AuthError);
7275
expect(error.name).toBe(AuthValidationErrorCode.IncorrectMFAMethod);
@@ -81,7 +84,7 @@ describe('confirmSignIn API error path cases:', () => {
8184
);
8285
});
8386
try {
84-
await confirmSignIn({ challengeResponse: 'TOTP' });
87+
await confirmSignIn(mockCtx, { challengeResponse: 'TOTP' });
8588
} catch (error: any) {
8689
expect(error).toBeInstanceOf(AuthError);
8790
expect(error.name).toBe(
@@ -99,7 +102,7 @@ describe('confirmSignIn API error path cases:', () => {
99102
});
100103

101104
try {
102-
await confirmSignIn({
105+
await confirmSignIn(mockCtx, {
103106
challengeResponse: 'SMS',
104107
});
105108
} catch (err: any) {

0 commit comments

Comments
 (0)