Skip to content

Commit

Permalink
feat(auth): add auto sign-in support (#12229)
Browse files Browse the repository at this point in the history
* feat: add autoSignIn support

* chore: add  default verification signUp method

* feat(auth): add auto sign-in (#12153)

* feat: add autoSignIn support

* chore: add  default verification signUp method

* autoSignIn refactor

* chore: remove unused code

* feat: add sign-up helpers for auto-sign-in

* feat: add auto sign-in helpers

* fix build

* chore: fix error during auto-sign-in with code

* remove unused types

* chore: address feedback

* fix autoSignIn enable

* chore: add unit tests

* fix unit tests

* chore: remove unsued type

* chore: address feedback

* chore: address feedback

* chore: import hub-internal from core

* fix: bundle size

* fix bundle size
  • Loading branch information
israx authored Oct 10, 2023
1 parent 6c1de3b commit 22c5e12
Show file tree
Hide file tree
Showing 19 changed files with 604 additions and 83 deletions.
87 changes: 87 additions & 0 deletions packages/auth/__tests__/providers/cognito/autoSignIn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import {
CognitoUserPoolsTokenProvider,
signUp,
} from '../../../src/providers/cognito';
import { autoSignIn } from '../../../src/providers/cognito/apis/autoSignIn';
import * as signUpClient from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider';
import { authAPITestParams } from './testUtils/authApiTestParams';
import { RespondToAuthChallengeCommandOutput } from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider/types';
import { Amplify } from 'aws-amplify';
import * as initiateAuthHelpers from '../../../src/providers/cognito/utils/signInHelpers';
import { AuthError } from '../../../src/errors/AuthError';
jest.mock('@aws-amplify/core/lib/clients/handlers/fetch');

const authConfig = {
Cognito: {
userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398',
userPoolId: 'us-west-2_zzzzz',
},
};
CognitoUserPoolsTokenProvider.setAuthConfig(authConfig);
Amplify.configure({
Auth: authConfig,
});
describe('Auto sign-in API Happy Path Cases:', () => {
let signUpSpy;
let handleUserSRPAuthflowSpy;
const { user1 } = authAPITestParams;
beforeEach(async () => {
signUpSpy = jest
.spyOn(signUpClient, 'signUp')
.mockImplementationOnce(async () => {
return {
UserConfirmed: true,
};
});

handleUserSRPAuthflowSpy = jest
.spyOn(initiateAuthHelpers, 'handleUserSRPAuthFlow')
.mockImplementationOnce(
async (): Promise<RespondToAuthChallengeCommandOutput> =>
authAPITestParams.RespondToAuthChallengeCommandOutput
);
});
afterEach(() => {
signUpSpy.mockClear();
handleUserSRPAuthflowSpy.mockClear();
});
test('signUp should enable autoSignIn and return COMPLETE_AUTO_SIGN_IN step', async () => {
const resp = await signUp({
username: user1.username,
password: user1.password,
options: {
userAttributes: { email: user1.email },
serviceOptions: {
autoSignIn: true,
},
},
});
expect(resp).toEqual({
isSignUpComplete: true,
nextStep: {
signUpStep: 'COMPLETE_AUTO_SIGN_IN',
},
});
expect(signUpSpy).toBeCalledTimes(1);
});

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

describe('Auto sign-in API Error Path Cases:', () => {
test('autoSignIn should throw an error when autoSignIn is not enabled', async () => {
try {
await autoSignIn();
} catch (error) {
expect(error).toBeInstanceOf(AuthError);
expect(error.name).toBe('AutoSignInException');
}
});
});
1 change: 1 addition & 0 deletions packages/auth/src/errors/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const USER_ALREADY_AUTHENTICATED_EXCEPTION =
'UserAlreadyAuthenticatedException';
export const DEVICE_METADATA_NOT_FOUND_EXCEPTION =
'DeviceMetadataNotFoundException';
export const AUTO_SIGN_IN_EXCEPTION = 'AutoSignInException';
1 change: 1 addition & 0 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {
rememberDevice,
forgetDevice,
fetchDevices,
autoSignIn,
} from './providers/cognito';

export {
Expand Down
119 changes: 119 additions & 0 deletions packages/auth/src/providers/cognito/apis/autoSignIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { AuthError } from '../../../errors/AuthError';
import { AUTO_SIGN_IN_EXCEPTION } from '../../../errors/constants';
import { AutoSignInCallback } from '../../../types/models';
import { SignInOutput } from '../types';

const initialAutoSignIn: AutoSignInCallback =
async (): Promise<SignInOutput> => {
throw new AuthError({
name: AUTO_SIGN_IN_EXCEPTION,
message:
'The autoSignIn flow has not started, or has been cancelled/completed.',
recoverySuggestion:
'Please try to use the signIn API or log out before starting a new autoSignIn flow.',
});
};

/**
* Signs a user in automatically after finishing the sign-up process.
*
* This API will automatically sign a user in if the autoSignIn flow has been completed in the following cases:
* - User confirmed their account with a verification code sent to their phone or email (default option).
* - User confirmed their account with a verification link sent to their phone or email. In order to
* enable this option you need to go to the Amazon Cognito [console](https://aws.amazon.com/pm/cognito),
* look for your userpool, then go to the `Messaging` tab and enable `link` mode inside the `Verification message` option.
* Finally you need to define the `signUpVerificationMethod` in your `Auth` config.
*
* @example
* ```typescript
* Amplify.configure({
* Auth: {
* Cognito: {
* ...cognitoConfig,
* signUpVerificationMethod: "link" // the default value is "code"
* }
* }});
* ```
*
* @throws AutoSignInException - Thrown when the autoSignIn flow has not started, or has been cancelled/completed.
* @returns The signInOutput.
*
* @example
* ```typescript
* // handleSignUp.ts
* async function handleSignUp(
* username:string,
* password:string
* ){
* try {
* const { nextStep } = await signUp({
* username,
* password,
* options: {
* userAttributes:{ email:'email@email.com'},
* serviceOptions: {
* autoSignIn: true // This enables the auto sign-in flow.
* },
* },
* });
*
* handleSignUpStep(nextStep);
*
* } catch (error) {
* console.log(error);
* }
* }
*
* // handleConfirmSignUp.ts
* async function handleConfirmSignUp(username:string, confirmationCode:string) {
* try {
* const { nextStep } = await confirmSignUp({
* username,
* confirmationCode,
* });
*
* handleSignUpStep(nextStep);
* } catch (error) {
* console.log(error);
* }
* }
*
* // signUpUtils.ts
* async function handleSignUpStep( step: SignUpOutput["nextStep"]) {
* switch (step.signUpStep) {
* case "CONFIRM_SIGN_UP":
*
* // Redirect end-user to confirm-sign up screen.
*
* case "COMPLETE_AUTO_SIGN_IN":
* const codeDeliveryDetails = step.codeDeliveryDetails;
* if (codeDeliveryDetails) {
* // Redirect user to confirm-sign-up with link screen.
* }
* const signInOutput = await autoSignIn();
* // handle sign-in steps
* }
*
* ```
*/
export let autoSignIn: AutoSignInCallback = initialAutoSignIn;

/**
* Sets the context of autoSignIn at run time.
* @internal
*/
export function setAutoSignIn(callback: AutoSignInCallback) {
autoSignIn = callback;
}

/**
* Resets the context
*
* @internal
*/
export function resetAutoSignIn() {
autoSignIn = initialAutoSignIn;
}
63 changes: 54 additions & 9 deletions packages/auth/src/providers/cognito/apis/confirmSignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@
// SPDX-License-Identifier: Apache-2.0

import { Amplify } from '@aws-amplify/core';
import { assertTokenProviderConfig, AuthAction } from '@aws-amplify/core/internals/utils';
import {
assertTokenProviderConfig,
AuthAction,
HubInternal,
} from '@aws-amplify/core/internals/utils';
import { ConfirmSignUpInput, ConfirmSignUpOutput } from '../types';
import { assertValidationError } from '../../../errors/utils/assertValidationError';
import { AuthValidationErrorCode } from '../../../errors/types/validation';
import { ConfirmSignUpException } from '../types/errors';
import { confirmSignUp as confirmSignUpClient } from '../utils/clients/CognitoIdentityProvider';
import { getRegion } from '../utils/clients/CognitoIdentityProvider/utils';
import { AutoSignInEventData } from '../types/models';
import {
isAutoSignInStarted,
isAutoSignInUserUsingConfirmSignUp,
setAutoSignInStarted,
} from '../utils/signUpHelpers';
import { getAuthUserAgentValue } from '../../../utils';

/**
Expand Down Expand Up @@ -40,9 +50,9 @@ export async function confirmSignUp(
);

await confirmSignUpClient(
{
{
region: getRegion(authConfig.userPoolId),
userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignUp)
userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignUp),
},
{
Username: username,
Expand All @@ -54,10 +64,45 @@ export async function confirmSignUp(
}
);

return {
isSignUpComplete: true,
nextStep: {
signUpStep: 'DONE',
},
};
return new Promise((resolve, reject) => {
try {
const signUpOut: ConfirmSignUpOutput = {
isSignUpComplete: true,
nextStep: {
signUpStep: 'DONE',
},
};

if (
!isAutoSignInStarted() ||
!isAutoSignInUserUsingConfirmSignUp(username)
) {
return resolve(signUpOut);
}

const stopListener = HubInternal.listen<AutoSignInEventData>(
'auth-internal',
({ payload }) => {
switch (payload.event) {
case 'autoSignIn':
resolve({
isSignUpComplete: true,
nextStep: {
signUpStep: 'COMPLETE_AUTO_SIGN_IN',
},
});
setAutoSignInStarted(false);
stopListener();
}
}
);

HubInternal.dispatch('auth-internal', {
event: 'confirmSignUp',
data: signUpOut,
});
} catch (error) {
reject(error);
}
});
}
Loading

0 comments on commit 22c5e12

Please sign in to comment.