in packages/ui/src/machines/authenticator/signUp.ts [28:336]
export function createSignUpMachine({ services }: SignUpMachineOptions) {
return createMachine<SignUpContext, AuthEvent>(
{
id: 'signUpActor',
initial: 'init',
states: {
init: {
always: [
{ target: 'confirmSignUp', cond: 'shouldInitConfirmSignUp' },
{ target: 'signUp' },
],
},
signUp: {
type: 'parallel',
exit: ['clearError', 'clearFormValues', 'clearTouched'],
states: {
validation: {
initial: 'pending',
states: {
pending: {
invoke: {
src: 'validateSignUp',
onDone: {
target: 'valid',
actions: 'clearValidationError',
},
onError: {
target: 'invalid',
actions: 'setFieldErrors',
},
},
},
valid: { entry: sendUpdate() },
invalid: { entry: sendUpdate() },
},
on: {
CHANGE: {
actions: 'handleInput',
target: '.pending',
},
BLUR: {
actions: 'handleBlur',
target: '.pending',
},
},
},
submission: {
initial: 'idle',
states: {
idle: {
entry: sendUpdate(),
on: {
SUBMIT: 'validate',
FEDERATED_SIGN_IN: 'federatedSignIn',
},
},
federatedSignIn: {
tags: ['pending'],
entry: [sendUpdate(), 'clearError'],
invoke: {
src: 'federatedSignIn',
onDone: '#signUpActor.resolved',
onError: { actions: 'setRemoteError' },
},
},
validate: {
entry: sendUpdate(),
invoke: {
src: 'validateSignUp',
onDone: {
target: 'pending',
actions: 'clearValidationError',
},
onError: {
target: 'idle',
actions: 'setFieldErrors',
},
},
},
pending: {
tags: ['pending'],
entry: ['parsePhoneNumber', sendUpdate(), 'clearError'],
invoke: {
src: 'signUp',
onDone: [
{
cond: 'shouldSkipConfirm',
target: 'skipConfirm',
actions: ['setUser'],
},
{
target: 'resolved',
actions: [
'setUser',
'setCredentials',
'setCodeDeliveryDetails',
],
},
],
onError: {
target: 'idle',
actions: 'setRemoteError',
},
},
},
skipConfirm: {
invoke: {
src: 'signIn',
onDone: {
target: '#signUpActor.resolved',
actions: 'setUser',
},
onError: {
target: 'idle',
actions: 'setRemoteError',
},
},
},
resolved: {
type: 'final',
always: '#signUpActor.confirmSignUp',
},
},
},
},
},
confirmSignUp: {
initial: 'edit',
states: {
edit: {
entry: sendUpdate(),
on: {
SUBMIT: 'submit',
CHANGE: { actions: 'handleInput' },
BLUR: { actions: 'handleBlur' },
RESEND: 'resend',
},
},
resend: {
tags: ['pending'],
entry: sendUpdate(),
invoke: {
src: 'resendConfirmationCode',
onDone: { target: 'edit' },
onError: [
{
target: '#signUpActor.resolved',
actions: 'setUser',
cond: 'isUserAlreadyConfirmed',
},
{ target: 'edit', actions: 'setRemoteError' },
],
},
},
submit: {
tags: ['pending'],
entry: [sendUpdate(), 'clearError'],
invoke: {
src: 'confirmSignUp',
onDone: {
target: '#signUpActor.resolved',
actions: ['setUser'],
},
onError: { target: 'edit', actions: 'setRemoteError' },
},
},
},
},
resolved: {
type: 'final',
data: (context, event) => {
const { username, password } = context.authAttributes;
return {
user: get(event, 'data.user') || context.user,
authAttributes: { username, password },
};
},
},
},
},
{
guards: {
/**
* This guard covers an edge case that exists in the current state of the UI.
* As of now, our ConfirmSignUp screen only supports showing an input for a
* confirmation code. However, a Cognito UserPool can instead verify users
* through a link that gets emailed to them. If a user verifies through the
* link and then they click on the "Resend Code" button, they will get an error
* saying that the user has already been confirmed. If we encounter that error,
* we want to just funnel them through the rest of the flow. In the future, we will
* want to update our UI to support both confirmation codes and links.
*
* https://github.com/aws-amplify/amplify-ui/issues/219
*/
isUserAlreadyConfirmed: (context, event) => {
return event.data.message === 'User is already confirmed.';
},
shouldInitConfirmSignUp: (context) => {
return context.intent && context.intent === 'confirmSignUp';
},
shouldSkipConfirm: (context, event) => {
return event.data.userConfirmed;
},
},
actions: {
clearError,
clearFormValues,
clearTouched,
clearValidationError,
handleInput,
handleBlur,
parsePhoneNumber,
setCredentials,
setFieldErrors,
setRemoteError,
setCodeDeliveryDetails,
setUser,
},
services: {
async signIn(context, event) {
const { user, authAttributes, formValues } = context;
const username =
get(user, 'username') || get(authAttributes, 'username');
const password = get(formValues, 'password');
return await Auth.signIn(username, password);
},
async confirmSignUp(context, event) {
const { user, authAttributes, formValues } = context;
const { confirmation_code: code } = formValues;
const username =
get(user, 'username') || get(authAttributes, 'username');
const { password } = authAttributes;
await services.handleConfirmSignUp({ username, code });
return await Auth.signIn(username, password);
},
async resendConfirmationCode(context, event) {
const { user, authAttributes } = context;
const username =
get(user, 'username') || get(authAttributes, 'username');
return Auth.resendSignUp(username);
},
async federatedSignIn(_, event) {
const { provider } = event.data;
const result = await Auth.federatedSignIn({ provider });
return result;
},
async signUp(context, _event) {
const { formValues, loginMechanisms } = context;
const [primaryAlias = 'username'] = loginMechanisms;
const { [primaryAlias]: username, password } = formValues;
const attributes = pickBy(formValues, (_, key) => {
// Allowlist of Cognito User Pool Attributes (from OpenID Connect specification)
// See: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
switch (key) {
case 'address':
case 'birthdate':
case 'email':
case 'family_name':
case 'gender':
case 'given_name':
case 'locale':
case 'middle_name':
case 'name':
case 'nickname':
case 'phone_number':
case 'picture':
case 'preferred_username':
case 'profile':
case 'updated_at':
case 'website':
case 'zoneinfo':
return true;
// Otherwise, it's a custom attribute
default:
return key.startsWith('custom:');
}
});
return await services.handleSignUp({
username,
password,
attributes,
});
},
async validateSignUp(context, event) {
// This needs to exist in the machine to reference new `services`
return runValidators(context.formValues, context.touched, [
// Validation for default form fields
services.validateConfirmPassword,
services.validatePreferredUsername,
// Validation for any custom Sign Up fields
services.validateCustomSignUp,
]);
},
},
}
);
}