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

feat(auth): Enable resumable SignIn #14073

Closed
wants to merge 68 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
e78c8e4
Resumable Sign In
Jun 7, 2024
49bb952
Add clear after rehydration
Jun 7, 2024
611357a
Synchronous Session Storage implementation
Jun 11, 2024
8d3944b
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Jun 11, 2024
814ccf3
Fix error dependency path
Jun 12, 2024
a413779
Modify init sequence
Jun 12, 2024
4532bc8
Bug fix
Jun 12, 2024
2dd0fd9
Move SyncSessionStorage to core
Jun 13, 2024
a54055e
Fix defaulting state
Jun 14, 2024
3816f2f
Fix calling wrong method
Jun 15, 2024
20cd812
Add Unittest for SyncSessionstorage
Jun 15, 2024
5f99aad
Adjust bundle size, fix exports
Jun 15, 2024
f040d10
Adjust bundle size, fix typo
Jun 15, 2024
c4642cf
Loosen jest cov for src/utils
Jun 15, 2024
8abe4d4
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Jun 15, 2024
8237695
ResumableSignIn Unit Tests
Jun 20, 2024
b6d666e
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Jun 20, 2024
e5a5b4d
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Jun 21, 2024
bf370d2
Modified signInStore implementation
Jun 24, 2024
8077bb4
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Jun 24, 2024
b91fec6
Complemented additional behaviors to address potential discrepancy du…
Jul 2, 2024
be3db29
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Jul 10, 2024
9f6c933
Modified State persistence behavior
Jul 10, 2024
82316a5
Update params
Jul 10, 2024
2c7600a
Add mock implementation to resolve test issue
Jul 10, 2024
08419ba
Changed expiration check logic
Jul 10, 2024
7e91d0f
Polish persistSignInState()
Jul 11, 2024
f22d805
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Aug 1, 2024
134e748
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Aug 9, 2024
e0eed74
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Aug 15, 2024
9a0d6d1
Adjust bundle size
Aug 15, 2024
363635d
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Aug 21, 2024
247ccc9
Add persisting action for session
Aug 21, 2024
539688b
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Aug 22, 2024
dea838e
Add spyOn checkpoints
Aug 22, 2024
deb0d9d
enable integ
Aug 23, 2024
aae163e
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Aug 23, 2024
dbe8d02
Revert jest config for aws-amplify package test
Aug 23, 2024
7d0527c
revert workflow
Aug 23, 2024
c71e4ea
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Aug 26, 2024
efb2331
Remove redundant features, improve efficiency
Aug 27, 2024
4899023
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Aug 28, 2024
c4b043a
Adjust bundle size limit
Aug 28, 2024
3e54290
Remove redundancy, fix annotation
Aug 28, 2024
9f488c5
Replace cleanActiveSignInState()
Aug 28, 2024
6168bcb
Enable CI for Resumable SMS MFA
Aug 29, 2024
ed930f6
Merge branch 'joonwonc/auth-resumable-smsmfa' into joonwonc/auth-resu…
joon-won Aug 29, 2024
d19f72d
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Aug 30, 2024
1f120ab
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Sep 12, 2024
92ce212
Modify dependency to foundations
Sep 12, 2024
bd5b099
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Sep 13, 2024
224f148
Update Integ
Sep 13, 2024
0b0c518
Revert
Sep 13, 2024
5d4f6b5
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Sep 20, 2024
a5326cc
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Sep 23, 2024
b04b260
Rebase resumablesignin with main
joon-won Nov 26, 2024
f59ba1f
Merge pull request #4 from joon-won/main
joon-won Nov 26, 2024
1760bc4
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Dec 4, 2024
7e128d8
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Dec 5, 2024
aa082ad
Merge branch 'aws-amplify:main' into joonwonc/auth-resumable-signin
joon-won Dec 5, 2024
fb76525
Rebase and add utility for ResumableSignIn
joon-won Dec 6, 2024
1aa12e2
Update resumablesignin integ test
joon-won Dec 11, 2024
d7a05a3
Move cleansigninstate after async
joon-won Dec 11, 2024
60717a9
Merge branch 'joonwonc/auth-resumable-signin' into joonwonc/auth-resu…
joon-won Dec 11, 2024
76c0fad
Merge ResumableSignIn (#14069)
joon-won Dec 11, 2024
c677dc5
Remove cleansigninstate
joon-won Dec 11, 2024
d4c45b9
Fix lint issue
joon-won Dec 11, 2024
04e9941
Merge branch 'main' into joonwonc/auth-resumable-signin
joon-won Dec 12, 2024
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
16 changes: 16 additions & 0 deletions .github/integ-config/integ-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,22 @@ tests:
sample_name: [subdomains]
spec: subdomains
browser: [chrome]
- test_name: integ_next_custom_auth
desc: 'Sign-in with Custom Auth flow'
framework: next
category: auth
sample_name: [custom-auth]
spec: custom-auth
browser: *minimal_browser_list
- test_name: integ_next_auth_sign_in_with_sms_mfa
desc: 'Resumable sign in with SMS MFA flow'
framework: next
category: auth
sample_name: [mfa]
spec: sign-in-resumable-mfa
browser: *minimal_browser_list
env:
NEXT_PUBLIC_BACKEND_CONFIG: resum-signin

# DISABLED Angular/Vue tests:
# TODO: delete tests or add custom ui logic to support them.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"bootstrap": "lerna bootstrap",
"test": "yarn test:no-datastore && yarn test:datastore && yarn test:license && yarn test:github-actions && yarn test:tsc-compliance",
"test:no-datastore": "lerna run test --stream --ignore @aws-amplify/datastore",
"test:auth": "lerna run test --stream --scope @aws-amplify/auth",
"test:datastore": "lerna run test --stream --scope @aws-amplify/datastore",
"test:size": "lerna run test:size --no-bail",
"test:duplicates": "./scripts/duplicates-yarn.sh",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jest.mock('@aws-amplify/core', () => ({
...(jest.createMockFromModule('@aws-amplify/core') as object),
Amplify: { getConfig: jest.fn(() => ({})) },
}));
jest.mock('../../../src/client/utils/store');
jest.mock('../../../src/client/utils/store/signInStore');
jest.mock(
'../../../src/foundation/factories/serviceClients/cognitoIdentityProvider',
);
Expand Down
268 changes: 268 additions & 0 deletions packages/auth/__tests__/providers/cognito/signInResumable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { Amplify, syncSessionStorage } from '@aws-amplify/core';

import {
setActiveSignInState,
signInStore,
} from '../../../src/client/utils/store/signInStore';
import { cognitoUserPoolsTokenProvider } from '../../../src/providers/cognito/tokenProvider';
import {
ChallengeName,
RespondToAuthChallengeCommandOutput,
} from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider/types';
import * as signInHelpers from '../../../src/providers/cognito/utils/signInHelpers';
import { signIn } from '../../../src/providers/cognito';

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

const signInStoreImplementation = require('../../../src/client/utils/store/signInStore');

jest.mock('@aws-amplify/core/internals/utils');
jest.mock('../../../src/providers/cognito/apis/getCurrentUser');
jest.mock('@aws-amplify/core', () => ({
...(jest.createMockFromModule('@aws-amplify/core') as object),
Amplify: {
getConfig: jest.fn(() => ({})),
ADD_OAUTH_LISTENER: jest.fn(() => ({})),
},
syncSessionStorage: {
setItem: jest.fn((key, value) => {
window.sessionStorage.setItem(key, value);
}),
getItem: jest.fn((key: string) => {
return window.sessionStorage.getItem(key);
}),
removeItem: jest.fn((key: string) => {
window.sessionStorage.removeItem(key);
}),
},
}));

const signInStateKeys: Record<string, string> = {
username: 'CognitoSignInState.username',
challengeName: 'CognitoSignInState.challengeName',
signInSession: 'CognitoSignInState.signInSession',
expiry: 'CognitoSignInState.expiry',
};

const user1: Record<string, string> = {
username: 'joonchoi',
challengeName: 'CUSTOM_CHALLENGE',
signInSession: '888577-ltfgo-42d8-891d-666l858766g7',
expiry: '1234567',
};

const populateValidTestSyncStorage = () => {
syncSessionStorage.setItem(signInStateKeys.username, user1.username);
syncSessionStorage.setItem(
signInStateKeys.signInSession,
user1.signInSession,
);
syncSessionStorage.setItem(
signInStateKeys.challengeName,
user1.challengeName,
);
syncSessionStorage.setItem(
signInStateKeys.expiry,
(new Date().getTime() + 9999999).toString(),
);

signInStore.dispatch({
type: 'SET_INITIAL_STATE',
});
};

const populateInvalidTestSyncStorage = () => {
syncSessionStorage.setItem(signInStateKeys.username, user1.username);
syncSessionStorage.setItem(
signInStateKeys.signInSession,
user1.signInSession,
);
syncSessionStorage.setItem(
signInStateKeys.challengeName,
user1.challengeName,
);
syncSessionStorage.setItem(
signInStateKeys.expiry,
(new Date().getTime() - 99999).toString(),
);

signInStore.dispatch({
type: 'SET_INITIAL_STATE',
});
};

describe('signInStore', () => {
const authConfig = {
Cognito: {
userPoolClientId: '123456-abcde-42d8-891d-666l858766g7',
userPoolId: 'us-west-7_ampjc',
},
};

const session = '1234234232';
const challengeName = 'SMS_MFA';
const { username } = authAPITestParams.user1;
const { password } = authAPITestParams.user1;

beforeEach(() => {
cognitoUserPoolsTokenProvider.setAuthConfig(authConfig);
});

beforeAll(() => {
setUpGetConfig(Amplify);
});

afterEach(() => {
jest.clearAllMocks();
});

afterAll(() => {
jest.restoreAllMocks();
});

test('LocalSignInState is empty after initialization', async () => {
const localSignInState = signInStore.getState();

expect(localSignInState).toEqual({
challengeName: undefined,
signInSession: undefined,
username: undefined,
});
signInStore.dispatch({ type: 'RESET_STATE' });
});

test('State is set after calling setActiveSignInState', async () => {
const persistSignInStateSpy = jest.spyOn(
signInStoreImplementation,
'persistSignInState',
);
setActiveSignInState(user1);
const localSignInState = signInStore.getState();

expect(localSignInState).toEqual(user1);
expect(persistSignInStateSpy).toHaveBeenCalledTimes(1);
expect(persistSignInStateSpy).toHaveBeenCalledWith(user1);
signInStore.dispatch({ type: 'RESET_STATE' });
});

test('State is updated after calling SignIn', async () => {
const handleUserSRPAuthflowSpy = jest
.spyOn(signInHelpers, 'handleUserSRPAuthFlow')
.mockImplementationOnce(
async (): Promise<RespondToAuthChallengeCommandOutput> => ({
ChallengeName: challengeName,
Session: session,
$metadata: {},
ChallengeParameters: {
CODE_DELIVERY_DELIVERY_MEDIUM: 'SMS',
CODE_DELIVERY_DESTINATION: '*******9878',
},
}),
);

await signIn({
username,
password,
});
const newLocalSignInState = signInStore.getState();

expect(handleUserSRPAuthflowSpy).toHaveBeenCalledTimes(1);
expect(newLocalSignInState).toEqual({
challengeName,
signInSession: session,
username,
signInDetails: {
loginId: username,
authFlowType: 'USER_SRP_AUTH',
},
});
handleUserSRPAuthflowSpy.mockClear();
});

test('The stored sign-in state should be rehydrated if the sign-in session is still valid.', () => {
populateValidTestSyncStorage();

const localSignInState = signInStore.getState();

expect(localSignInState).toEqual({
username: user1.username,
challengeName: user1.challengeName,
signInSession: user1.signInSession,
});
signInStore.dispatch({ type: 'RESET_STATE' });
});

test('sign-in store should return undefined state when the sign-in session is expired', async () => {
populateInvalidTestSyncStorage();

const localSignInState = signInStore.getState();

expect(localSignInState).toEqual({
username: undefined,
challengeName: undefined,
signInSession: undefined,
});
signInStore.dispatch({ type: 'RESET_STATE' });
});

test('State SignInSession is updated after dispatching custom session value', () => {
const persistSignInStateSpy = jest.spyOn(
signInStoreImplementation,
'persistSignInState',
);
const newSignInSessionID = '135790-dodge-2468-9aaa-kersh23lad00';

populateValidTestSyncStorage();

const localSignInState = signInStore.getState();
expect(localSignInState).toEqual({
username: user1.username,
challengeName: user1.challengeName,
signInSession: user1.signInSession,
});

signInStore.dispatch({
type: 'SET_SIGN_IN_SESSION',
value: newSignInSessionID,
});

expect(persistSignInStateSpy).toHaveBeenCalledTimes(1);
expect(persistSignInStateSpy).toHaveBeenCalledWith({
signInSession: newSignInSessionID,
});
const newLocalSignInState = signInStore.getState();
expect(newLocalSignInState).toEqual({
username: user1.username,
challengeName: user1.challengeName,
signInSession: newSignInSessionID,
});
});

test('State Challenge Name is updated after dispatching custom challenge name', () => {
const newChallengeName = 'RANDOM_CHALLENGE' as ChallengeName;

populateValidTestSyncStorage();

const localSignInState = signInStore.getState();
expect(localSignInState).toEqual({
username: user1.username,
challengeName: user1.challengeName,
signInSession: user1.signInSession,
});

signInStore.dispatch({
type: 'SET_CHALLENGE_NAME',
value: newChallengeName,
});

const newLocalSignInState = signInStore.getState();
expect(newLocalSignInState).toEqual({
username: user1.username,
challengeName: newChallengeName,
signInSession: user1.signInSession,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('local sign-in state management tests', () => {

beforeEach(() => {
cognitoUserPoolsTokenProvider.setAuthConfig(authConfig);
signInStore.dispatch({ type: 'RESET_STATE' });
});

test('local state management should return state after signIn returns a ChallengeName', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,20 @@ jest.mock('@aws-amplify/core', () => {
getConfig: jest.fn(() => mockAuthConfigWithOAuth),
[ACTUAL_ADD_OAUTH_LISTENER]: jest.fn(),
},
ConsoleLogger: jest.fn(),
ConsoleLogger: jest.fn().mockImplementation(() => {
return { warn: jest.fn() };
}),
syncSessionStorage: {
setItem: jest.fn((key, value) => {
window.sessionStorage.setItem(key, value);
}),
getItem: jest.fn((key: string) => {
return window.sessionStorage.getItem(key);
}),
removeItem: jest.fn((key: string) => {
window.sessionStorage.removeItem(key);
}),
},
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { InitiateAuthCommandOutput } from '../../../src/foundation/factories/ser

jest.mock('../../../src/providers/cognito/utils/signInHelpers', () => ({
...jest.requireActual('../../../src/providers/cognito/utils/signInHelpers'),
cleanActiveSignInState: jest.fn(),
setActiveSignInState: jest.fn(),
getNewDeviceMetadata: jest.fn(),
getActiveSignInUsername: jest.fn(username => username),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ import {
getNewDeviceMetadata,
getSignInResult,
} from '../../../providers/cognito/utils/signInHelpers';
import {
cleanActiveSignInState,
setActiveSignInState,
signInStore,
} from '../../../client/utils/store';
import { setActiveSignInState, signInStore } from '../../../client/utils/store';
import { AuthSignInOutput } from '../../../types';
import { getAuthUserAgentValue } from '../../../utils';
import { getPasskey } from '../../utils/passkey';
Expand Down Expand Up @@ -106,7 +102,7 @@ export async function handleWebAuthnSignInResult(
}),
signInDetails,
});
cleanActiveSignInState();
signInStore.dispatch({ type: 'RESET_STATE' });
await dispatchSignedInHubEvent();

return {
Expand Down
Loading
Loading