UNPKG

@aws-amplify/ui

Version:

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

398 lines (395 loc) • 17 kB
import { actions, createMachine, assign, spawn, forwardTo } from 'xstate'; import '@aws-amplify/core/internals/utils'; import '../../utils/setUserAgent/constants.mjs'; import { isEmptyObject } from '../../utils/utils.mjs'; import ACTIONS from './actions.mjs'; import GUARDS from './guards.mjs'; import { forgotPasswordActor } from './actors/forgotPassword.mjs'; import { signInActor } from './actors/signIn.mjs'; import { signUpActor } from './actors/signUp.mjs'; import { signOutActor } from './actors/signOut.mjs'; import { verifyUserAttributesActor } from './actors/verifyUserAttributes.mjs'; import { defaultServices } from './defaultServices.mjs'; import { getAvailableAuthMethods } from './utils.mjs'; const getActorContext = (context, defaultStep) => { const availableAuthMethods = getAvailableAuthMethods(context.passwordlessCapabilities, context.config?.passwordless?.hiddenAuthMethods); // Determine effective preferred challenge: component prop takes precedence over backend config const preferredChallenge = context.config?.passwordless?.preferredAuthMethod ?? context.passwordlessCapabilities?.preferredChallenge; return { ...context.actorDoneData, step: context?.actorDoneData?.step ?? defaultStep, // initialize empty objects on actor start formValues: {}, touched: {}, validationError: {}, // values included on `context.config` that should be available in actors formFields: context.config?.formFields, loginMechanisms: context.config?.loginMechanisms, passwordSettings: context.config?.passwordSettings, signUpAttributes: context.config?.signUpAttributes, socialProviders: context.config?.socialProviders, availableAuthMethods, preferredChallenge, passwordless: context.config?.passwordless, }; }; const { choose, stop } = actions; const stopActor = (machineId) => stop(machineId); // setup step waits for ui to emit INIT action to proceed to configure const LEGACY_WAIT_CONFIG = { on: { INIT: { actions: 'configure', target: 'getConfig', }, SIGN_OUT: '#authenticator.signOut', }, }; // setup step proceeds directly to configure const NEXT_WAIT_CONFIG = { always: { actions: 'configure', target: 'getConfig' }, }; function createAuthenticatorMachine(options) { const { useNextWaitConfig, ...overrideConfigServices } = options ?? {}; const initConfig = useNextWaitConfig ? NEXT_WAIT_CONFIG : LEGACY_WAIT_CONFIG; return createMachine({ id: 'authenticator', initial: 'idle', context: { user: undefined, config: {}, services: defaultServices, actorRef: undefined, hasSetup: false, }, predictableActionArguments: true, states: { // See: https://xstate.js.org/docs/guides/communication.html#invoking-promises idle: { invoke: { src: 'handleGetCurrentUser', onDone: { actions: 'setUser', target: 'setup' }, onError: { target: 'setup' }, }, }, setup: { initial: 'initConfig', states: { initConfig, getConfig: { invoke: { src: 'getAmplifyConfig', onDone: [ { actions: ['applyAmplifyConfig', 'setHasSetup'], cond: 'hasUser', target: '#authenticator.authenticated', }, { actions: ['applyAmplifyConfig', 'setHasSetup'], target: 'goToInitialState', }, ], }, }, goToInitialState: { always: [ { cond: 'isInitialStateSignUp', target: '#authenticator.signUpActor', }, { cond: 'isInitialStateResetPassword', target: '#authenticator.forgotPasswordActor', }, { target: '#authenticator.signInActor' }, ], }, }, }, getCurrentUser: { invoke: { src: 'handleGetCurrentUser', onDone: { actions: 'setUser', target: '#authenticator.authenticated', }, onError: { target: '#authenticator.setup' }, }, }, signInActor: { initial: 'spawnActor', states: { spawnActor: { always: { actions: 'spawnSignInActor', target: 'runActor' }, }, runActor: { entry: 'clearActorDoneData', exit: stopActor('signInActor'), }, }, on: { FORGOT_PASSWORD: 'forgotPasswordActor', SELECT_METHOD: { actions: 'forwardToActor' }, SHOW_AUTH_METHODS: { actions: 'forwardToActor' }, SIGN_IN: 'signInActor', SIGN_UP: 'signUpActor', 'done.invoke.signInActor': [ { cond: 'hasCompletedAttributeConfirmation', target: '#authenticator.getCurrentUser', }, { cond: 'isShouldConfirmUserAttributeStep', actions: 'setActorDoneData', target: '#authenticator.verifyUserAttributesActor', }, { cond: 'isResetPasswordStep', actions: 'setActorDoneData', target: '#authenticator.forgotPasswordActor', }, { cond: 'isConfirmSignUpStep', actions: 'setActorDoneData', target: '#authenticator.signUpActor', }, ], }, }, signUpActor: { initial: 'spawnActor', states: { spawnActor: { always: { actions: 'spawnSignUpActor', target: 'runActor' }, }, runActor: { entry: 'clearActorDoneData', exit: stopActor('signUpActor'), }, }, on: { SIGN_IN: 'signInActor', 'done.invoke.signUpActor': [ { cond: 'hasCompletedAttributeConfirmation', target: '#authenticator.getCurrentUser', }, { cond: 'isShouldConfirmUserAttributeStep', actions: 'setActorDoneData', target: '#authenticator.verifyUserAttributesActor', }, { cond: 'isConfirmUserAttributeStep', target: '#authenticator.verifyUserAttributesActor', }, { actions: 'setActorDoneData', target: '#authenticator.signInActor', }, ], }, }, forgotPasswordActor: { initial: 'spawnActor', states: { spawnActor: { always: { actions: 'spawnForgotPasswordActor', target: 'runActor', }, }, runActor: { entry: 'clearActorDoneData', exit: stopActor('forgotPasswordActor'), }, }, on: { SIGN_IN: 'signInActor', 'done.invoke.forgotPasswordActor': [ { target: '#authenticator.signInActor' }, ], }, }, verifyUserAttributesActor: { initial: 'spawnActor', states: { spawnActor: { always: { actions: 'spawnVerifyUserAttributesActor', target: 'runActor', }, }, runActor: { entry: 'clearActorDoneData', exit: stopActor('verifyUserAttributesActor'), }, }, on: { 'done.invoke.verifyUserAttributesActor': [ { actions: 'setActorDoneData', target: '#authenticator.getCurrentUser', }, ], }, }, authenticated: { initial: 'idle', states: { idle: { on: { TOKEN_REFRESH: 'refreshUser' } }, refreshUser: { invoke: { src: '#authenticator.getCurrentUser', onDone: { actions: 'setUser', target: 'idle' }, onError: { target: '#authenticator.signOut' }, }, }, }, on: { SIGN_OUT: 'signOut' }, }, signOut: { initial: 'spawnActor', states: { spawnActor: { always: { actions: 'spawnSignOutActor', target: 'runActor' }, }, runActor: { entry: 'clearActorDoneData', exit: stopActor('signOutActor'), }, }, on: { 'done.invoke.signOutActor': { actions: 'clearUser', target: 'setup.getConfig', }, }, }, }, on: { SIGN_IN_WITH_REDIRECT: { target: '#authenticator.getCurrentUser' }, CHANGE: { actions: 'forwardToActor' }, BLUR: { actions: 'forwardToActor' }, SUBMIT: { actions: 'forwardToActor' }, FEDERATED_SIGN_IN: { actions: 'forwardToActor' }, RESEND: { actions: 'forwardToActor' }, SIGN_IN: { actions: 'forwardToActor' }, SKIP: { actions: 'forwardToActor' }, }, }, { actions: { ...ACTIONS, forwardToActor: choose([ { cond: 'hasActor', actions: forwardTo(({ actorRef }) => actorRef) }, ]), setActorDoneData: assign({ actorDoneData: (_, event) => ({ challengeName: event.data.challengeName, codeDeliveryDetails: event.data.codeDeliveryDetails, missingAttributes: event.data.missingAttributes, remoteError: event.data.remoteError, username: event.data.username, step: event.data.step, totpSecretCode: event.data.totpSecretCode, unverifiedUserAttributes: event.data.unverifiedUserAttributes, allowedMfaTypes: event.data.allowedMfaTypes, }), }), applyAmplifyConfig: assign({ passwordlessCapabilities: (_, { data: cliConfig }) => cliConfig.passwordlessCapabilities, config(context, { data: cliConfig }) { // Prefer explicitly configured settings over default CLI values\ const { loginMechanisms = cliConfig.loginMechanisms ?? [], signUpAttributes = cliConfig.signUpAttributes ?? [], socialProviders = cliConfig.socialProviders ?? [], initialState, formFields: _formFields, passwordSettings = cliConfig.passwordFormat ?? {}, passwordless, } = context.config; // By default, Cognito assumes `username`, so there isn't a different username attribute like `email`. // We explicitly add it as a login mechanism to be consistent with Admin UI's language. if (loginMechanisms.length === 0) { loginMechanisms.push('username'); } const formFields = convertFormFields(_formFields) ?? {}; return { formFields, initialState, loginMechanisms, passwordSettings, passwordless, signUpAttributes, socialProviders, }; }, }), spawnSignInActor: assign({ actorRef: (context, _) => { const { services } = context; const actor = signInActor({ services }).withContext(getActorContext(context, 'SIGN_IN')); return spawn(actor, { name: 'signInActor' }); }, }), spawnSignUpActor: assign({ actorRef: (context, _) => { const { services } = context; const actor = signUpActor({ services }).withContext(getActorContext(context, 'SIGN_UP')); return spawn(actor, { name: 'signUpActor' }); }, }), spawnForgotPasswordActor: assign({ actorRef: (context, _) => { const { services } = context; const actor = forgotPasswordActor({ services }).withContext(getActorContext(context, 'FORGOT_PASSWORD')); return spawn(actor, { name: 'forgotPasswordActor' }); }, }), spawnVerifyUserAttributesActor: assign({ actorRef: (context) => { const actor = verifyUserAttributesActor().withContext(getActorContext(context)); return spawn(actor, { name: 'verifyUserAttributesActor' }); }, }), spawnSignOutActor: assign({ actorRef: (context) => { const actor = signOutActor().withContext({ user: context?.user }); return spawn(actor, { name: 'signOutActor' }); }, }), configure: assign((_, event) => { const { services: customServices, ...config } = !isEmptyObject(overrideConfigServices) ? overrideConfigServices : event.data ?? {}; return { services: { ...defaultServices, ...customServices }, config, }; }), setHasSetup: assign({ hasSetup: true }), }, guards: { ...GUARDS, hasActor: ({ actorRef }) => !!actorRef, isInitialStateSignUp: ({ config }) => config.initialState === 'signUp', isInitialStateResetPassword: ({ config }) => config.initialState === 'forgotPassword', shouldSetup: ({ hasSetup }) => !hasSetup, hasUser: ({ user }) => { return !!user; }, }, services: { getAmplifyConfig: ({ services }) => services.getAmplifyConfig(), handleGetCurrentUser: ({ services }) => services.getCurrentUser(), }, }); } function convertFormFields(formFields) { if (formFields) { Object.keys(formFields).forEach((component) => { Object.keys(formFields[component]).forEach((inputName) => { let ff = formFields[component][inputName]; ff.required = ff.isRequired; }); }); } return formFields; } export { createAuthenticatorMachine };