@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
JavaScript
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 };