UNPKG

@aws-amplify/ui

Version:

`@aws-amplify/ui` contains low-level logic & styles for stand-alone usage or re-use in framework-specific implementations.

356 lines (353 loc) • 14.7 kB
import { createMachine, sendUpdate } from 'xstate'; import { listWebAuthnCredentials, signInWithRedirect, fetchUserAttributes, autoSignIn } from 'aws-amplify/auth'; import { getSignUpInput } from '../utils.mjs'; import { runValidators } from '../../../validators/index.mjs'; import ACTIONS from '../actions.mjs'; import GUARDS from '../guards.mjs'; import { getFederatedSignInState } from './utils.mjs'; const handleResetPasswordResponse = { onDone: [ { actions: 'setCodeDeliveryDetails', target: '#signUpActor.resolved' }, ], onError: { actions: ['setRemoteError', 'sendUpdate'] }, }; const handleAutoSignInResponse = { onDone: [ { cond: 'hasCompletedSignIn', actions: 'setNextSignInStep', target: '#signUpActor.fetchUserAttributes', }, { cond: 'shouldConfirmSignInWithNewPassword', actions: 'setNextSignInStep', target: '#signUpActor.resolved', }, { cond: 'shouldResetPasswordFromSignIn', actions: 'setNextSignInStep', target: '#signUpActor.resetPassword', }, { cond: 'shouldConfirmSignUpFromSignIn', actions: 'setNextSignInStep', target: '#signUpActor.resendSignUpCode', }, { actions: [ 'setNextSignInStep', 'setChallengeName', 'setMissingAttributes', 'setTotpSecretCode', 'setAllowedMfaTypes', ], target: '#signUpActor.resolved', }, ], onError: { actions: 'setRemoteError', target: '#signUpActor.resolved', }, }; function signUpActor({ services }) { return createMachine({ id: 'signUpActor', initial: 'init', predictableActionArguments: true, states: { init: { always: [ { cond: 'shouldConfirmSignUp', target: 'confirmSignUp' }, { target: 'signUp' }, ], }, autoSignIn: { tags: 'pending', invoke: { src: 'autoSignIn', ...handleAutoSignInResponse }, }, fetchUserAttributes: { invoke: { src: 'fetchUserAttributes', onDone: [ { cond: 'hasPasskeyRegistrationPrompts', actions: 'setFetchedUserAttributes', target: 'checkPasskeys', }, { actions: 'setFetchedUserAttributes', target: 'evaluatePasskeyPrompt', }, ], onError: { actions: 'setConfirmAttributeCompleteStep', target: '#signUpActor.resolved', }, }, }, checkPasskeys: { invoke: { src: async () => { try { const result = await listWebAuthnCredentials(); return result.credentials && result.credentials.length > 0; } catch { return false; } }, onDone: { actions: 'setHasExistingPasskeys', target: 'evaluatePasskeyPrompt', }, onError: { actions: 'clearHasExistingPasskeys', target: 'evaluatePasskeyPrompt', }, }, }, evaluatePasskeyPrompt: { always: [ { cond: 'shouldPromptPasskeyRegistrationAfterSignup', target: '#signUpActor.passkeyPrompt', }, { cond: 'shouldVerifyAttribute', actions: [ 'setShouldVerifyUserAttributeStep', 'setUnverifiedUserAttributes', ], target: '#signUpActor.resolved', }, { actions: 'setConfirmAttributeCompleteStep', target: '#signUpActor.resolved', }, ], }, federatedSignIn: { ...getFederatedSignInState('signUp') }, resetPassword: { invoke: { src: 'resetPassword', ...handleResetPasswordResponse }, }, resendSignUpCode: { tags: 'pending', entry: 'sendUpdate', exit: 'sendUpdate', invoke: { src: 'resendSignUpCode', onDone: { actions: ['setCodeDeliveryDetails', 'sendUpdate'], target: '#signUpActor.confirmSignUp', }, onError: [ { cond: 'isUserAlreadyConfirmed', target: '#signUpActor.resolved', }, { actions: ['setRemoteError', 'sendUpdate'], target: '#signUpActor.confirmSignUp', }, ], }, }, signUp: { type: 'parallel', exit: 'clearTouched', on: { FEDERATED_SIGN_IN: { target: 'federatedSignIn' }, }, states: { validation: { initial: 'pending', states: { pending: { invoke: { src: 'validateSignUp', onDone: { actions: 'clearValidationError', target: 'valid', }, onError: { actions: 'setFieldErrors', target: 'invalid' }, }, }, valid: { entry: 'sendUpdate' }, invalid: { entry: 'sendUpdate' }, }, on: { BLUR: { actions: 'handleBlur', target: '.pending' }, CHANGE: { actions: 'handleInput', target: '.pending' }, }, }, submission: { initial: 'idle', states: { idle: { entry: ['sendUpdate'], on: { SUBMIT: { actions: [ 'setSelectedAuthMethodFromForm', 'handleSubmit', ], target: 'validate', }, }, }, validate: { entry: 'sendUpdate', invoke: { src: 'validateSignUp', onDone: { target: 'handleSignUp', actions: 'clearValidationError', }, onError: { actions: 'setFieldErrors', target: 'idle' }, }, }, handleSignUp: { tags: 'pending', entry: ['setUsernameSignUp', 'clearError'], exit: 'sendUpdate', invoke: { src: 'handleSignUp', onDone: [ { cond: 'hasCompletedSignUp', actions: 'setNextSignUpStep', target: '#signUpActor.resolved', }, { cond: 'shouldAutoSignIn', actions: 'setNextSignUpStep', target: '#signUpActor.autoSignIn', }, { actions: [ 'setCodeDeliveryDetails', 'setNextSignUpStep', ], target: '#signUpActor.init', }, ], onError: { actions: ['sendUpdate', 'setRemoteError'], target: 'idle', }, }, }, }, }, }, }, confirmSignUp: { initial: 'edit', entry: 'sendUpdate', states: { edit: { on: { SUBMIT: { actions: 'handleSubmit', target: 'submit' }, CHANGE: { actions: 'handleInput' }, BLUR: { actions: 'handleBlur' }, RESEND: '#signUpActor.resendSignUpCode', }, }, submit: { tags: 'pending', entry: ['clearError', 'sendUpdate'], invoke: { src: 'confirmSignUp', onDone: [ { cond: 'shouldAutoSignIn', actions: ['setNextSignUpStep', 'clearFormValues'], target: '#signUpActor.autoSignIn', }, { actions: 'setNextSignUpStep', target: '#signUpActor.init', }, ], onError: { actions: ['setRemoteError', 'sendUpdate'], target: 'edit', }, }, }, }, }, passkeyPrompt: { entry: 'sendUpdate', on: { SKIP: { actions: 'setConfirmAttributeCompleteStep', target: 'resolved', }, SUBMIT: { actions: 'setConfirmAttributeCompleteStep', target: 'resolved', }, }, }, resolved: { type: 'final', data: (context) => ({ challengeName: context.challengeName, missingAttributes: context.missingAttributes, remoteError: context.remoteError, step: context.step, totpSecretCode: context.totpSecretCode, username: context.username, unverifiedUserAttributes: context.unverifiedUserAttributes, allowedMfaTypes: context.allowedMfaTypes, }), }, }, }, { // sendUpdate is a HOC actions: { ...ACTIONS, sendUpdate: sendUpdate() }, guards: GUARDS, services: { autoSignIn() { return autoSignIn(); }, async fetchUserAttributes() { return fetchUserAttributes(); }, confirmSignUp({ formValues, username }) { const { confirmation_code: confirmationCode } = formValues; const input = { username, confirmationCode }; return services.handleConfirmSignUp(input); }, resendSignUpCode({ username }) { return services.handleResendSignUpCode({ username }); }, signInWithRedirect(_, { data }) { return signInWithRedirect(data); }, handleSignUp(context) { const { formValues, loginMechanisms, username, selectedAuthMethod, preferredChallenge, } = context; const loginMechanism = loginMechanisms[0]; const authMethod = selectedAuthMethod ?? preferredChallenge; const input = getSignUpInput(username, formValues, loginMechanism, authMethod); return services.handleSignUp(input); }, async validateSignUp(context) { // This needs to exist in the machine to reference new `services` return runValidators(context.formValues, context.touched, context.passwordSettings, [ // Validation of password services.validateFormPassword, // Validation for default form fields services.validateConfirmPassword, services.validatePreferredUsername, // Validation for required fields based on auth method services.validateRequiredFieldsForAuthMethod, // Validation for any custom Sign Up fields services.validateCustomSignUp, ]); }, }, }); } export { signUpActor };