@aws-amplify/ui 6.12.1 → 6.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/esm/helpers/authenticator/facade.mjs +9 -2
  2. package/dist/esm/helpers/authenticator/formFields/defaults.mjs +34 -8
  3. package/dist/esm/helpers/authenticator/getRoute.mjs +8 -0
  4. package/dist/esm/helpers/authenticator/textUtil.mjs +34 -0
  5. package/dist/esm/i18n/dictionaries/authenticator/defaultTexts.mjs +27 -0
  6. package/dist/esm/i18n/dictionaries/authenticator/es.mjs +1 -1
  7. package/dist/esm/i18n/dictionaries/authenticator/fr.mjs +1 -1
  8. package/dist/esm/machines/authenticator/actions.mjs +63 -4
  9. package/dist/esm/machines/authenticator/actors/signIn.mjs +199 -49
  10. package/dist/esm/machines/authenticator/actors/signUp.mjs +81 -27
  11. package/dist/esm/machines/authenticator/defaultServices.mjs +37 -0
  12. package/dist/esm/machines/authenticator/guards.mjs +49 -1
  13. package/dist/esm/machines/authenticator/index.mjs +29 -15
  14. package/dist/esm/machines/authenticator/utils.mjs +65 -6
  15. package/dist/index.js +632 -111
  16. package/dist/styles/authenticator.css +17 -0
  17. package/dist/styles/authenticator.layer.css +17 -0
  18. package/dist/styles.css +17 -0
  19. package/dist/styles.layer.css +17 -0
  20. package/dist/types/helpers/authenticator/facade.d.ts +6 -2
  21. package/dist/types/helpers/authenticator/textUtil.d.ts +24 -1
  22. package/dist/types/i18n/dictionaries/authenticator/defaultTexts.d.ts +27 -0
  23. package/dist/types/i18n/dictionaries/index.d.ts +27 -0
  24. package/dist/types/i18n/translations.d.ts +27 -0
  25. package/dist/types/machines/authenticator/defaultServices.d.ts +105 -0
  26. package/dist/types/machines/authenticator/types.d.ts +26 -2
  27. package/dist/types/machines/authenticator/utils.d.ts +14 -2
  28. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -1117,7 +1117,7 @@ const esDict$1 = {
1117
1117
  'Sign Up with Google': 'Crear cuenta con Google',
1118
1118
  'Sign Up': 'Crear cuenta',
1119
1119
  'User already exists': 'El usuario ya existe',
1120
- 'User does not exist': 'El usuario no existe',
1120
+ 'User does not exist.': 'El usuario no existe',
1121
1121
  'Username/client id combination not found.': 'El usuario no existe',
1122
1122
  'Username cannot be empty': 'El nombre de usuario no puede estar vacío',
1123
1123
  'Your passwords must match': 'Las contraseñas deben coincidir',
@@ -1244,7 +1244,7 @@ const frDict$1 = {
1244
1244
  'Sign Up': "S'inscrire",
1245
1245
  SMS: 'SMS',
1246
1246
  'User already exists': "L'utilisateur existe déjà",
1247
- 'User does not exist': "L'utilisateur n'existe pas",
1247
+ 'User does not exist.': "L'utilisateur n'existe pas",
1248
1248
  'Username cannot be empty': "Le nom d'utilisateur doit être renseigné",
1249
1249
  'Username/client id combination not found.': "L'utilisateur n'existe pas",
1250
1250
  'We Emailed You': 'Nous vous avons envoyé un code',
@@ -2770,7 +2770,13 @@ const defaultTexts$1 = {
2770
2770
  CONFIRM: 'Confirm',
2771
2771
  CONFIRMATION_CODE: 'Confirmation Code',
2772
2772
  CONFIRMING: 'Confirming',
2773
+ CONTINUE: 'Continue',
2774
+ CONTINUE_WITHOUT_PASSKEY: 'Continue without a Passkey',
2773
2775
  CREATE_ACCOUNT: 'Create Account',
2776
+ CREATE_ACCOUNT_WITH_EMAIL_OTP: 'Create account with Email OTP',
2777
+ CREATE_ACCOUNT_WITH_PASSWORD: 'Create account with Password',
2778
+ CREATE_ACCOUNT_WITH_SMS_OTP: 'Create account with SMS OTP',
2779
+ CREATE_PASSKEY: 'Create a Passkey',
2774
2780
  CREATING_ACCOUNT: 'Creating Account',
2775
2781
  EMAIL_ADDRESS: 'Email',
2776
2782
  EMAIL_OTP: 'Email Message',
@@ -2787,6 +2793,8 @@ const defaultTexts$1 = {
2787
2793
  ENTER_PHONE_NUMBER: 'Enter your Phone Number',
2788
2794
  ENTER_PREFERRED_USERNAME: 'Enter your Preferred Username',
2789
2795
  ENTER_USERNAME: 'Enter your username',
2796
+ ENTER_USERNAME_FIRST: 'Please enter your username first',
2797
+ EXISTING_PASSKEYS: 'Existing Passkeys',
2790
2798
  FAMILY_NAME: 'Family Name',
2791
2799
  GIVEN_NAME: 'Given Name',
2792
2800
  FORGOT_PASSWORD: 'Forgot Password?',
@@ -2801,16 +2809,30 @@ const defaultTexts$1 = {
2801
2809
  NICKNAME: 'Nickname',
2802
2810
  NEW_PASSWORD: 'New password',
2803
2811
  OR: 'or',
2812
+ OTHER_SIGN_IN_OPTIONS: 'Other sign-in options',
2813
+ PASSKEY_AUTHENTICATION_CANCELED: 'Passkey authentication was canceled. Please try again or use a different sign-in method.',
2814
+ PASSKEY_CREATED_SUCCESS: 'Passkey created successfully!',
2815
+ PASSKEY_LABEL: 'Passkey',
2816
+ PASSKEY_PROMPT_DESCRIPTION: 'Passkeys are WebAuthn credentials that validate your identity using biometric data like touch or facial recognition or device authentication like passwords or PINs, serving as a secure password replacement.',
2817
+ PASSKEY_PROMPT_HEADING: 'Sign in faster with Passkey',
2818
+ PASSKEY_REGISTERED: 'Your passkey has been successfully registered.',
2819
+ PASSKEY_REGISTRATION_FAILED: 'Failed to create passkey. Please try again.',
2804
2820
  PASSWORD: 'Password',
2821
+ PASSWORDLESS_NOT_ENABLED: 'Passwordless authentication is not enabled for this account. Please contact your administrator or use password sign-in.',
2805
2822
  PHONE_NUMBER: 'Phone Number',
2806
2823
  PREFERRED_USERNAME: 'Preferred Username',
2807
2824
  PROFILE: 'Profile',
2825
+ REGISTERING: 'Registering',
2808
2826
  RESEND_CODE: 'Resend Code',
2809
2827
  RESET_PASSWORD_HEADING: 'Reset your password',
2810
2828
  RESET_PASSWORD: 'Reset Password',
2811
2829
  SEND_CODE: 'Send code',
2830
+ CODE_DELIVERY_FAILED: 'Unable to send verification code. Please try again.',
2831
+ VERIFICATION_CODE_EXPIRED: 'Your verification code has expired. Please request a new code.',
2832
+ VERIFICATION_CODE_INVALID: 'The verification code you entered is incorrect. Please try again.',
2812
2833
  SENDING: 'Sending',
2813
2834
  SELECT_MFA_TYPE: 'Select MFA Type',
2835
+ SETUP_ANOTHER_PASSKEY: 'Setup another Passkey',
2814
2836
  SETUP_EMAIL: 'Setup Email',
2815
2837
  SETUP_TOTP: 'Setup TOTP',
2816
2838
  SHOW_PASSWORD: 'Show password',
@@ -2818,8 +2840,12 @@ const defaultTexts$1 = {
2818
2840
  SIGN_IN_TAB: 'Sign In',
2819
2841
  SIGN_IN_WITH_AMAZON: 'Sign In with Amazon',
2820
2842
  SIGN_IN_WITH_APPLE: 'Sign In with Apple',
2843
+ SIGN_IN_WITH_EMAIL: 'Sign In with Email',
2821
2844
  SIGN_IN_WITH_FACEBOOK: 'Sign In with Facebook',
2822
2845
  SIGN_IN_WITH_GOOGLE: 'Sign In with Google',
2846
+ SIGN_IN_WITH_PASSKEY: 'Sign In with Passkey',
2847
+ SIGN_IN_WITH_PASSWORD: 'Sign In with Password',
2848
+ SIGN_IN_WITH_SMS: 'Sign In with SMS',
2823
2849
  SIGN_IN: 'Sign in to your account',
2824
2850
  SIGN_UP_BUTTON: 'Create a new account',
2825
2851
  SIGNING_IN_BUTTON: 'Signing in',
@@ -2829,6 +2855,7 @@ const defaultTexts$1 = {
2829
2855
  SUBMITTING: 'Submitting',
2830
2856
  SOFTWARE_TOKEN_MFA: 'Authenticator App (TOTP)',
2831
2857
  UPPERCASE_COPY: 'COPY',
2858
+ USERNAME: 'Username',
2832
2859
  VERIFY_CONTACT: 'Verify Contact',
2833
2860
  VERIFY_HEADING: 'Account recovery requires verified contact information',
2834
2861
  VERIFY: 'Verify',
@@ -3100,6 +3127,14 @@ const getRoute = (state, actorState) => {
3100
3127
  case actorState?.matches('setupTotp.edit'):
3101
3128
  case actorState?.matches('setupTotp.submit'):
3102
3129
  return 'setupTotp';
3130
+ case actorState?.matches('signIn.submit'):
3131
+ return actorState?.context.selectedAuthMethod != null
3132
+ ? 'signInSelectAuthFactor'
3133
+ : 'signIn';
3134
+ case actorState?.matches('signIn.selectMethod'):
3135
+ return 'signInSelectAuthFactor';
3136
+ case actorState?.matches('passkeyPrompt'):
3137
+ return 'passkeyPrompt';
3103
3138
  case actorState?.matches('signIn'):
3104
3139
  return 'signIn';
3105
3140
  case actorState?.matches('signUp'):
@@ -3157,8 +3192,10 @@ const getSendEventAliases = (send) => {
3157
3192
  return {
3158
3193
  initializeMachine: sendToMachine('INIT'),
3159
3194
  resendCode: sendToMachine('RESEND'),
3195
+ selectAuthMethod: sendToMachine('SELECT_METHOD'),
3160
3196
  signOut: sendToMachine('SIGN_OUT'),
3161
3197
  submitForm: sendToMachine('SUBMIT'),
3198
+ toShowAuthMethods: sendToMachine('SHOW_AUTH_METHODS'),
3162
3199
  updateForm: sendToMachine('CHANGE'),
3163
3200
  updateBlur: sendToMachine('BLUR'),
3164
3201
  // Actions that don't immediately invoke a service but instead transition to a screen
@@ -3183,8 +3220,9 @@ const getNextSendEventAliases = (send) => {
3183
3220
  };
3184
3221
  const getServiceContextFacade = (state) => {
3185
3222
  const actorContext = (getActorContext$1(state) ?? {});
3186
- const { allowedMfaTypes, challengeName, codeDeliveryDetails, remoteError: error, validationError: validationErrors, totpSecretCode = null, unverifiedUserAttributes, username, } = actorContext;
3187
- const { socialProviders = [] } = state.context?.config ?? {};
3223
+ const { allowedMfaTypes, availableAuthMethods, challengeName, codeDeliveryDetails, preferredChallenge, remoteError: error, selectedAuthMethod, validationError: validationErrors, totpSecretCode = null, unverifiedUserAttributes, username, } = actorContext;
3224
+ const { socialProviders = [], loginMechanisms } = state.context?.config ?? {};
3225
+ const loginMechanism = loginMechanisms?.[0];
3188
3226
  // check for user in actorContext prior to state context. actorContext is more "up to date",
3189
3227
  // but is not available on all states
3190
3228
  const user = actorContext?.user ?? state.context?.user;
@@ -3208,12 +3246,16 @@ const getServiceContextFacade = (state) => {
3208
3246
  const facade = {
3209
3247
  allowedMfaTypes,
3210
3248
  authStatus,
3249
+ availableAuthMethods,
3211
3250
  challengeName,
3212
3251
  codeDeliveryDetails,
3213
3252
  error,
3214
3253
  hasValidationErrors,
3215
3254
  isPending,
3255
+ loginMechanism,
3256
+ preferredChallenge,
3216
3257
  route,
3258
+ selectedAuthMethod,
3217
3259
  socialProviders,
3218
3260
  totpSecretCode,
3219
3261
  unverifiedUserAttributes,
@@ -3682,16 +3724,32 @@ const getConfirmationCodeFormFields = (_) => ({
3682
3724
  placeholder: 'Code',
3683
3725
  },
3684
3726
  });
3685
- const getSignInFormFields = (state) => ({
3686
- username: { ...getAliasDefaultFormField(state) },
3687
- password: {
3688
- ...getDefaultFormField('password'),
3689
- autocomplete: 'current-password',
3690
- },
3691
- });
3727
+ const getSignInFormFields = (state) => {
3728
+ const actorContext = state.context.actorRef?.getSnapshot()?.context;
3729
+ const availableAuthMethods = actorContext?.availableAuthMethods;
3730
+ const preferredChallenge = actorContext?.preferredChallenge;
3731
+ const shouldShowPassword = !availableAuthMethods?.length ||
3732
+ (availableAuthMethods.length === 1 &&
3733
+ availableAuthMethods[0] === 'PASSWORD') ||
3734
+ (availableAuthMethods.length > 1 &&
3735
+ (!preferredChallenge || preferredChallenge === 'PASSWORD'));
3736
+ const fields = {
3737
+ username: { ...getAliasDefaultFormField(state) },
3738
+ };
3739
+ if (shouldShowPassword) {
3740
+ fields.password = {
3741
+ ...getDefaultFormField('password'),
3742
+ autocomplete: 'current-password',
3743
+ };
3744
+ }
3745
+ return fields;
3746
+ };
3692
3747
  const getSignUpFormFields = (state) => {
3693
3748
  const { loginMechanisms, signUpAttributes } = state.context.config;
3694
3749
  const primaryAlias = getPrimaryAlias(state);
3750
+ const actorContext = state.context.actorRef?.getSnapshot()?.context;
3751
+ const availableAuthMethods = actorContext?.availableAuthMethods;
3752
+ const hasMultipleMethods = availableAuthMethods && availableAuthMethods.length > 1;
3695
3753
  /**
3696
3754
  * @migration signUp Fields created here
3697
3755
  */
@@ -3707,7 +3765,17 @@ const getSignUpFormFields = (state) => {
3707
3765
  const fieldAttrs = fieldName === primaryAlias
3708
3766
  ? getAliasDefaultFormField(state)
3709
3767
  : getDefaultFormField(fieldName);
3710
- formField[fieldName] = { ...fieldAttrs };
3768
+ // Make email, phone_number, password, and confirm_password optional when multiple auth methods available
3769
+ // Validation will check based on selected method
3770
+ const isOptional = hasMultipleMethods &&
3771
+ (fieldName === 'email' ||
3772
+ fieldName === 'phone_number' ||
3773
+ fieldName === 'password' ||
3774
+ fieldName === 'confirm_password');
3775
+ formField[fieldName] = {
3776
+ ...fieldAttrs,
3777
+ ...(isOptional && { isRequired: false }),
3778
+ };
3711
3779
  }
3712
3780
  else {
3713
3781
  // There's a `custom:*` attribute or one we don't already have an implementation for
@@ -3903,6 +3971,17 @@ const getSelectMfaTypeByChallengeName = (challengeName) => {
3903
3971
  }
3904
3972
  return translate(DefaultTexts.MFA_SELECTION);
3905
3973
  };
3974
+ const getUsernameLabelByLoginMechanism = (loginMechanism) => {
3975
+ switch (loginMechanism) {
3976
+ case 'email':
3977
+ return translate(DefaultTexts.EMAIL_ADDRESS);
3978
+ case 'phone_number':
3979
+ return translate(DefaultTexts.PHONE_NUMBER);
3980
+ case 'username':
3981
+ default:
3982
+ return translate(DefaultTexts.USERNAME);
3983
+ }
3984
+ };
3906
3985
  const getMfaTypeLabelByValue = (mfaType) => {
3907
3986
  switch (mfaType) {
3908
3987
  case 'EMAIL':
@@ -3922,6 +4001,7 @@ const authenticatorTextUtil = {
3922
4001
  getChangingText: () => translate(DefaultTexts.CHANGING_PASSWORD),
3923
4002
  getConfirmText: () => translate(DefaultTexts.CONFIRM),
3924
4003
  getConfirmingText: () => translate(DefaultTexts.CONFIRMING),
4004
+ getContinueText: () => translate(DefaultTexts.CONTINUE),
3925
4005
  getCopyText: () => translate(DefaultTexts.UPPERCASE_COPY),
3926
4006
  getHidePasswordText: () => translate(DefaultTexts.HIDE_PASSWORD),
3927
4007
  getLoadingText: () => translate(DefaultTexts.LOADING),
@@ -3944,6 +4024,9 @@ const authenticatorTextUtil = {
3944
4024
  /** SignUp */
3945
4025
  getCreatingAccountText: () => translate(DefaultTexts.CREATING_ACCOUNT),
3946
4026
  getCreateAccountText: () => translate(DefaultTexts.CREATE_ACCOUNT),
4027
+ getCreateAccountWithEmailText: () => translate(DefaultTexts.CREATE_ACCOUNT_WITH_EMAIL_OTP),
4028
+ getCreateAccountWithPasswordText: () => translate(DefaultTexts.CREATE_ACCOUNT_WITH_PASSWORD),
4029
+ getCreateAccountWithSmsText: () => translate(DefaultTexts.CREATE_ACCOUNT_WITH_SMS_OTP),
3947
4030
  /** ConfirmSignUp */
3948
4031
  getDeliveryMessageText,
3949
4032
  getDeliveryMethodText,
@@ -3970,6 +4053,25 @@ const authenticatorTextUtil = {
3970
4053
  getVerifyText: () => translate(DefaultTexts.VERIFY),
3971
4054
  getVerifyContactText: () => translate(DefaultTexts.VERIFY_CONTACT),
3972
4055
  getAccountRecoveryInfoText: () => translate(DefaultTexts.VERIFY_HEADING),
4056
+ /** Passwordless */
4057
+ getPasskeyPromptHeadingText: () => translate(DefaultTexts.PASSKEY_PROMPT_HEADING),
4058
+ getPasskeyPromptDescriptionText: () => translate(DefaultTexts.PASSKEY_PROMPT_DESCRIPTION),
4059
+ getCreatePasskeyText: () => translate(DefaultTexts.CREATE_PASSKEY),
4060
+ getRegisteringText: () => translate(DefaultTexts.REGISTERING),
4061
+ getContinueWithoutPasskeyText: () => translate(DefaultTexts.CONTINUE_WITHOUT_PASSKEY),
4062
+ getPasskeyCreatedSuccessText: () => translate(DefaultTexts.PASSKEY_CREATED_SUCCESS),
4063
+ getPasskeyRegisteredText: () => translate(DefaultTexts.PASSKEY_REGISTERED),
4064
+ getPasskeyRegistrationFailedText: () => translate(DefaultTexts.PASSKEY_REGISTRATION_FAILED),
4065
+ getPasskeyLabelText: () => translate(DefaultTexts.PASSKEY_LABEL),
4066
+ getExistingPasskeysText: () => translate(DefaultTexts.EXISTING_PASSKEYS),
4067
+ getSetupAnotherPasskeyText: () => translate(DefaultTexts.SETUP_ANOTHER_PASSKEY),
4068
+ getSignInWithPasswordText: () => translate(DefaultTexts.SIGN_IN_WITH_PASSWORD),
4069
+ getSignInWithEmailText: () => translate(DefaultTexts.SIGN_IN_WITH_EMAIL),
4070
+ getSignInWithSmsText: () => translate(DefaultTexts.SIGN_IN_WITH_SMS),
4071
+ getSignInWithPasskeyText: () => translate(DefaultTexts.SIGN_IN_WITH_PASSKEY),
4072
+ getOtherSignInOptionsText: () => translate(DefaultTexts.OTHER_SIGN_IN_OPTIONS),
4073
+ getEnterUsernameFirstText: () => translate(DefaultTexts.ENTER_USERNAME_FIRST),
4074
+ getUsernameLabelByLoginMechanism,
3973
4075
  /** Validations */
3974
4076
  // TODO: add defaultText
3975
4077
  getInvalidEmailText: () => translate('Please enter a valid email'),
@@ -4162,35 +4264,99 @@ const getUserAttributes = (formValues) => {
4162
4264
  }
4163
4265
  return userAttributes;
4164
4266
  };
4165
- const getSignUpInput = (username, formValues, loginMechanism) => {
4267
+ const getSignUpInput = (username, formValues, loginMechanism, authMethod) => {
4166
4268
  const { password, ...values } = formValues;
4167
4269
  const attributes = getUserAttributes(values);
4270
+ const isPasswordless = authMethod && authMethod !== 'PASSWORD';
4271
+ // For SMS OTP, set the email channel to empty string if present to force account creation with phone number
4272
+ let userAttributes = attributes;
4273
+ if (authMethod === 'SMS_OTP' && attributes.email) {
4274
+ userAttributes = { ...attributes, email: '' };
4275
+ }
4168
4276
  const options = {
4169
- autoSignIn: DEFAULT_AUTO_SIGN_IN,
4277
+ autoSignIn: isPasswordless
4278
+ ? {
4279
+ enabled: true,
4280
+ authFlowType: 'USER_AUTH',
4281
+ preferredChallenge: authMethod,
4282
+ }
4283
+ : DEFAULT_AUTO_SIGN_IN,
4170
4284
  userAttributes: {
4171
4285
  // use `username` value for `phone_number`
4172
4286
  ...(loginMechanism === 'phone_number'
4173
- ? { ...attributes, phone_number: username }
4174
- : attributes),
4287
+ ? { ...userAttributes, phone_number: username }
4288
+ : userAttributes),
4175
4289
  },
4176
4290
  };
4177
- return { username, password, options };
4291
+ return { username, password: isPasswordless ? undefined : password, options };
4178
4292
  };
4179
4293
  const getUsernameSignUp = ({ formValues, loginMechanisms, }) => {
4294
+ // Check if a specific auth method was selected via form data
4295
+ const authMethod = formValues.__authMethod;
4296
+ // For SMS_OTP, always use phone_number as username
4297
+ if (authMethod === 'SMS_OTP') {
4298
+ const { country_code, phone_number } = formValues;
4299
+ return sanitizePhoneNumber(country_code, phone_number);
4300
+ }
4301
+ // For EMAIL_OTP, always use email as username
4302
+ if (authMethod === 'EMAIL_OTP') {
4303
+ return formValues.email;
4304
+ }
4305
+ // When 'username' is in loginMechanisms, always use the username field for the Username parameter.
4306
+ // This handles both username-only mode and alias mode (username + email/phone as sign-in options).
4307
+ // See: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases
4308
+ if (loginMechanisms.includes('username')) {
4309
+ return formValues.username;
4310
+ }
4180
4311
  const loginMechanism = loginMechanisms[0];
4181
4312
  if (loginMechanism === 'phone_number') {
4182
4313
  const { country_code, phone_number } = formValues;
4183
4314
  return sanitizePhoneNumber(country_code, phone_number);
4184
4315
  }
4316
+ // Otherwise, use the primary login mechanism (email as username)
4185
4317
  return formValues[loginMechanism];
4186
4318
  };
4319
+ /**
4320
+ * Get available authentication methods based on backend capabilities and hidden methods
4321
+ */
4322
+ const getAvailableAuthMethods = (passwordlessCapabilities, hiddenAuthMethods) => {
4323
+ const allMethods = [];
4324
+ // If hiddenAuthMethods is explicitly provided (even as empty array),
4325
+ // assume all methods are available and let hiddenAuthMethods filter them
4326
+ const assumeAllAvailable = hiddenAuthMethods !== undefined;
4327
+ // PASSWORD is always available by default
4328
+ allMethods.push('PASSWORD');
4329
+ // Add passwordless methods if backend supports them OR if hiddenAuthMethods is explicitly set
4330
+ if (assumeAllAvailable || passwordlessCapabilities?.emailOtpEnabled) {
4331
+ allMethods.push('EMAIL_OTP');
4332
+ }
4333
+ if (assumeAllAvailable || passwordlessCapabilities?.smsOtpEnabled) {
4334
+ allMethods.push('SMS_OTP');
4335
+ }
4336
+ if (assumeAllAvailable || passwordlessCapabilities?.webAuthnEnabled) {
4337
+ allMethods.push('WEB_AUTHN');
4338
+ }
4339
+ // Filter out hidden methods
4340
+ const hidden = hiddenAuthMethods ?? [];
4341
+ const availableMethods = allMethods.filter((method) => !hidden.includes(method));
4342
+ // Validate that at least one method remains
4343
+ if (availableMethods.length === 0) {
4344
+ throw new Error('InvalidPasswordlessSettings: All authentication methods are hidden');
4345
+ }
4346
+ return availableMethods;
4347
+ };
4187
4348
 
4188
4349
  const { assign } = xstate.actions;
4189
4350
  const clearActorDoneData = assign({ actorDoneData: undefined });
4190
4351
  const clearChallengeName = assign({ challengeName: undefined });
4191
4352
  const clearMissingAttributes = assign({ missingAttributes: undefined });
4192
4353
  const clearError = assign({ remoteError: undefined });
4193
- const clearFormValues = assign({ formValues: {} });
4354
+ const clearFormValues = assign({
4355
+ formValues: (context) => ({
4356
+ // Preserve username for passwordless flows to avoid "username is required" errors
4357
+ username: context.formValues?.username,
4358
+ }),
4359
+ });
4194
4360
  const clearTouched = assign({ touched: {} });
4195
4361
  const clearUser = assign({ user: undefined });
4196
4362
  const clearValidationError = assign({ validationError: {} });
@@ -4291,7 +4457,28 @@ const setRemoteError = assign({
4291
4457
  if (data.name === 'NoUserPoolError') {
4292
4458
  return `Configuration error (see console) – please contact the administrator`;
4293
4459
  }
4294
- return data?.message || data;
4460
+ const message = data?.message || '';
4461
+ // Handle USER_AUTH flow not enabled error
4462
+ if (message.includes('USER_AUTH flow not enabled')) {
4463
+ return translate(DefaultTexts.PASSWORDLESS_NOT_ENABLED);
4464
+ }
4465
+ // Handle cannot send code error
4466
+ if (message.includes('Cannot send code to either')) {
4467
+ return translate(DefaultTexts.CODE_DELIVERY_FAILED);
4468
+ }
4469
+ // Handle invalid/wrong verification code
4470
+ if (message.includes('Invalid code or auth state')) {
4471
+ return translate(DefaultTexts.VERIFICATION_CODE_INVALID);
4472
+ }
4473
+ // Handle expired verification code
4474
+ if (message.includes('session is expired')) {
4475
+ return translate(DefaultTexts.VERIFICATION_CODE_EXPIRED);
4476
+ }
4477
+ // Handle passkey authentication canceled
4478
+ if (message.includes('ceremony has been canceled')) {
4479
+ return translate(DefaultTexts.PASSKEY_AUTHENTICATION_CANCELED);
4480
+ }
4481
+ return message || data;
4295
4482
  },
4296
4483
  });
4297
4484
  const setUser = assign({ user: (_, { data }) => data });
@@ -4328,8 +4515,12 @@ const handleBlur = assign({
4328
4515
  }),
4329
4516
  });
4330
4517
  const setUnverifiedUserAttributes = assign({
4331
- unverifiedUserAttributes: (_, { data }) => {
4332
- const { email, phone_number } = data;
4518
+ unverifiedUserAttributes: (context, { data }) => {
4519
+ // Use fetchedUserAttributes from context if data is not provided
4520
+ const attributes = data || context.fetchedUserAttributes;
4521
+ if (!attributes)
4522
+ return {};
4523
+ const { email, phone_number } = attributes;
4333
4524
  const unverifiedUserAttributes = {
4334
4525
  ...(email && { email }),
4335
4526
  ...(phone_number && { phone_number }),
@@ -4343,11 +4534,34 @@ const setSelectedUserAttribute = assign({
4343
4534
  });
4344
4535
  // Maps to unexposed `ConfirmSignUpSignUpStep`
4345
4536
  const setConfirmSignUpSignUpStep = assign({ step: 'CONFIRM_SIGN_UP' });
4537
+ // Passwordless actions
4538
+ const setSelectedAuthMethod = assign({
4539
+ selectedAuthMethod: (_, { data }) => data.method,
4540
+ });
4541
+ const setSelectedAuthMethodFromForm = assign({
4542
+ selectedAuthMethod: (_, { data }) => {
4543
+ // Extract method from form data if present, default to PASSWORD for form submissions
4544
+ return data?.__authMethod || 'PASSWORD';
4545
+ },
4546
+ });
4547
+ const setSelectAuthMethodStep = assign({
4548
+ step: 'SELECT_AUTH_METHOD',
4549
+ });
4550
+ const setFetchedUserAttributes = assign({
4551
+ fetchedUserAttributes: (_, event) => event.data,
4552
+ });
4553
+ const setHasExistingPasskeys = assign({
4554
+ hasExistingPasskeys: (_, event) => event.data,
4555
+ });
4556
+ const clearHasExistingPasskeys = assign({
4557
+ hasExistingPasskeys: false,
4558
+ });
4346
4559
  const ACTIONS = {
4347
4560
  clearActorDoneData,
4348
4561
  clearChallengeName,
4349
4562
  clearError,
4350
4563
  clearFormValues,
4564
+ clearHasExistingPasskeys,
4351
4565
  clearMissingAttributes,
4352
4566
  clearSelectedUserAttribute,
4353
4567
  clearTouched,
@@ -4359,7 +4573,9 @@ const ACTIONS = {
4359
4573
  setAllowedMfaTypes,
4360
4574
  setChallengeName,
4361
4575
  setCodeDeliveryDetails,
4576
+ setFetchedUserAttributes,
4362
4577
  setFieldErrors,
4578
+ setHasExistingPasskeys,
4363
4579
  setMissingAttributes,
4364
4580
  setNextResetPasswordStep,
4365
4581
  setNextSignInStep,
@@ -4367,6 +4583,9 @@ const ACTIONS = {
4367
4583
  setRemoteError,
4368
4584
  setConfirmAttributeCompleteStep,
4369
4585
  setConfirmSignUpSignUpStep,
4586
+ setSelectAuthMethodStep,
4587
+ setSelectedAuthMethod,
4588
+ setSelectedAuthMethodFromForm,
4370
4589
  setShouldVerifyUserAttributeStep,
4371
4590
  setSelectedUserAttribute,
4372
4591
  setSignInStep,
@@ -4410,7 +4629,11 @@ const shouldResetPassword = ({ step }) => step === 'RESET_PASSWORD';
4410
4629
  const shouldConfirmResetPassword = ({ step }) => step === 'CONFIRM_RESET_PASSWORD_WITH_CODE';
4411
4630
  const shouldConfirmSignUp = ({ step }) => step === 'CONFIRM_SIGN_UP';
4412
4631
  // miscellaneous guards
4413
- const shouldVerifyAttribute = (_, { data }) => {
4632
+ const shouldVerifyAttribute = (context, event) => {
4633
+ // Try to get data from event first (for backward compatibility), then from context
4634
+ const data = (event.data || context.fetchedUserAttributes);
4635
+ if (!data)
4636
+ return false;
4414
4637
  const { email, phone_number, phone_number_verified, email_verified } = data;
4415
4638
  // if neither email nor phone_number exist
4416
4639
  // there is nothing to verify
@@ -4435,11 +4658,51 @@ const shouldVerifyAttribute = (_, { data }) => {
4435
4658
  * https://github.com/aws-amplify/amplify-ui/issues/219
4436
4659
  */
4437
4660
  const isUserAlreadyConfirmed = (_, { data }) => data.message === 'User is already confirmed.';
4661
+ // passwordless guards
4662
+ const shouldSelectAuthMethod = ({ availableAuthMethods, preferredChallenge, selectedAuthMethod, }) => {
4663
+ // Show selection if:
4664
+ // 1. Multiple methods available
4665
+ // 2. AND either no preferredChallenge OR selectedAuthMethod is explicitly cleared (null)
4666
+ const hasMultipleMethods = availableAuthMethods && availableAuthMethods.length > 1;
4667
+ const shouldShowSelection = !preferredChallenge || selectedAuthMethod === null;
4668
+ return hasMultipleMethods && shouldShowSelection;
4669
+ };
4670
+ const shouldPromptPasskeyRegistration = ({ passwordless, hasExistingPasskeys, }) => {
4671
+ const { passkeyRegistrationPrompts } = passwordless || {};
4672
+ if (!passkeyRegistrationPrompts) {
4673
+ return false;
4674
+ }
4675
+ // Don't prompt if user already has passkeys
4676
+ if (hasExistingPasskeys) {
4677
+ return false;
4678
+ }
4679
+ if (typeof passkeyRegistrationPrompts === 'boolean') {
4680
+ return passkeyRegistrationPrompts;
4681
+ }
4682
+ return passkeyRegistrationPrompts.afterSignin === 'ALWAYS';
4683
+ };
4684
+ const shouldPromptPasskeyRegistrationAfterSignup = ({ passwordless, hasExistingPasskeys, }) => {
4685
+ const { passkeyRegistrationPrompts } = passwordless || {};
4686
+ if (!passkeyRegistrationPrompts) {
4687
+ return false;
4688
+ }
4689
+ // Don't prompt if user already has passkeys
4690
+ if (hasExistingPasskeys) {
4691
+ return false;
4692
+ }
4693
+ if (typeof passkeyRegistrationPrompts === 'boolean') {
4694
+ return passkeyRegistrationPrompts;
4695
+ }
4696
+ return passkeyRegistrationPrompts.afterSignup === 'ALWAYS';
4697
+ };
4698
+ const hasPasskeyRegistrationPrompts = ({ passwordless }) => passwordless?.passkeyRegistrationPrompts != null;
4699
+ const shouldReturnToSelectMethod = ({ selectedAuthMethod, step, }) => selectedAuthMethod != null && step === 'SELECT_AUTH_METHOD';
4438
4700
  const GUARDS = {
4439
4701
  hasCompletedAttributeConfirmation,
4440
4702
  hasCompletedResetPassword,
4441
4703
  hasCompletedSignIn,
4442
4704
  hasCompletedSignUp,
4705
+ hasPasskeyRegistrationPrompts,
4443
4706
  isConfirmSignUpStep,
4444
4707
  isConfirmUserAttributeStep,
4445
4708
  isResetPasswordStep,
@@ -4453,10 +4716,14 @@ const GUARDS = {
4453
4716
  shouldConfirmSignUpFromSignIn,
4454
4717
  shouldResetPassword,
4455
4718
  shouldResetPasswordFromSignIn,
4719
+ shouldReturnToSelectMethod,
4720
+ shouldSelectAuthMethod,
4456
4721
  shouldSetupTotp,
4457
4722
  shouldSetupEmail,
4458
4723
  shouldSelectMfaType,
4459
4724
  shouldVerifyAttribute,
4725
+ shouldPromptPasskeyRegistration,
4726
+ shouldPromptPasskeyRegistrationAfterSignup,
4460
4727
  };
4461
4728
 
4462
4729
  // Runs all validators given. Resolves if there are no error. Rejects otherwise.
@@ -4504,11 +4771,24 @@ const defaultServices = {
4504
4771
  const parsedSocialProviders = loginWith?.oauth?.providers
4505
4772
  ? loginWith.oauth.providers?.map((provider) => provider.toString().toLowerCase())
4506
4773
  : undefined;
4774
+ // Detect passwordless capabilities from amplify_outputs.json
4775
+ // Support both snake_case (legacy) and camelCase (current) formats
4776
+ const passwordlessConfig = result.Auth?.Cognito?.passwordless;
4777
+ const passwordlessCapabilities = {
4778
+ emailOtpEnabled: passwordlessConfig?.emailOtpEnabled ??
4779
+ passwordlessConfig?.email_otp_enabled === true,
4780
+ smsOtpEnabled: passwordlessConfig?.smsOtpEnabled ??
4781
+ passwordlessConfig?.sms_otp_enabled === true,
4782
+ webAuthnEnabled: !!(passwordlessConfig?.webAuthn ?? passwordlessConfig?.web_authn),
4783
+ preferredChallenge: passwordlessConfig?.preferredChallenge ??
4784
+ passwordlessConfig?.preferred_challenge,
4785
+ };
4507
4786
  return {
4508
4787
  ...cliConfig,
4509
4788
  loginMechanisms: parsedLoginMechanisms,
4510
4789
  signUpAttributes: parsedSignupAttributes,
4511
4790
  socialProviders: parsedSocialProviders,
4791
+ passwordlessCapabilities,
4512
4792
  };
4513
4793
  },
4514
4794
  getCurrentUser: auth.getCurrentUser,
@@ -4571,6 +4851,30 @@ const defaultServices = {
4571
4851
  }
4572
4852
  },
4573
4853
  async validatePreferredUsername(_, __) { },
4854
+ async validateRequiredFieldsForAuthMethod(formData) {
4855
+ const authMethod = formData.__authMethod;
4856
+ // If no auth method specified, skip validation (will use default required fields)
4857
+ if (!authMethod)
4858
+ return null;
4859
+ // Check required fields based on auth method
4860
+ if (authMethod === 'EMAIL_OTP' && !formData.email) {
4861
+ return { email: 'Email is required for Email OTP sign up' };
4862
+ }
4863
+ if (authMethod === 'SMS_OTP' && !formData.phone_number) {
4864
+ return { phone_number: 'Phone number is required for SMS OTP sign up' };
4865
+ }
4866
+ if (authMethod === 'PASSWORD') {
4867
+ const errors = {};
4868
+ if (!formData.password) {
4869
+ errors.password = 'Password is required';
4870
+ }
4871
+ if (!formData.confirm_password) {
4872
+ errors.confirm_password = 'Confirm Password is required';
4873
+ }
4874
+ return Object.keys(errors).length > 0 ? errors : null;
4875
+ }
4876
+ return null;
4877
+ },
4574
4878
  };
4575
4879
 
4576
4880
  function forgotPasswordActor({ services, }) {
@@ -4800,32 +5104,13 @@ const handleSignInResponse = {
4800
5104
  'setNextSignInStep',
4801
5105
  'setTotpSecretCode',
4802
5106
  'setAllowedMfaTypes',
5107
+ 'setCodeDeliveryDetails',
4803
5108
  ],
4804
5109
  target: '#signInActor.init',
4805
5110
  },
4806
5111
  ],
4807
5112
  onError: { actions: 'setRemoteError', target: 'edit' },
4808
5113
  };
4809
- const handleFetchUserAttributesResponse$1 = {
4810
- onDone: [
4811
- {
4812
- cond: 'shouldVerifyAttribute',
4813
- actions: [
4814
- 'setShouldVerifyUserAttributeStep',
4815
- 'setUnverifiedUserAttributes',
4816
- ],
4817
- target: '#signInActor.resolved',
4818
- },
4819
- {
4820
- actions: 'setConfirmAttributeCompleteStep',
4821
- target: '#signInActor.resolved',
4822
- },
4823
- ],
4824
- onError: {
4825
- actions: 'setConfirmAttributeCompleteStep',
4826
- target: '#signInActor.resolved',
4827
- },
4828
- };
4829
5114
  const getDefaultConfirmSignInState = (exit) => ({
4830
5115
  initial: 'edit',
4831
5116
  exit,
@@ -4836,6 +5121,7 @@ const getDefaultConfirmSignInState = (exit) => ({
4836
5121
  SUBMIT: { actions: 'handleSubmit', target: 'submit' },
4837
5122
  SIGN_IN: '#signInActor.signIn',
4838
5123
  CHANGE: { actions: 'handleInput' },
5124
+ RESEND: { target: 'resend' },
4839
5125
  },
4840
5126
  },
4841
5127
  submit: {
@@ -4843,6 +5129,21 @@ const getDefaultConfirmSignInState = (exit) => ({
4843
5129
  entry: ['sendUpdate', 'clearError'],
4844
5130
  invoke: { src: 'confirmSignIn', ...handleSignInResponse },
4845
5131
  },
5132
+ resend: {
5133
+ tags: 'pending',
5134
+ entry: ['sendUpdate', 'clearError'],
5135
+ invoke: {
5136
+ src: 'resendSignInCode',
5137
+ onDone: {
5138
+ actions: ['setCodeDeliveryDetails', 'sendUpdate'],
5139
+ target: 'edit',
5140
+ },
5141
+ onError: {
5142
+ actions: 'setRemoteError',
5143
+ target: 'edit',
5144
+ },
5145
+ },
5146
+ },
4846
5147
  },
4847
5148
  });
4848
5149
  function signInActor({ services }) {
@@ -4877,13 +5178,68 @@ function signInActor({ services }) {
4877
5178
  { target: 'signIn' },
4878
5179
  ],
4879
5180
  },
4880
- federatedSignIn: getFederatedSignInState('signIn'),
5181
+ federatedSignIn: { ...getFederatedSignInState('signIn') },
4881
5182
  fetchUserAttributes: {
4882
5183
  invoke: {
4883
5184
  src: 'fetchUserAttributes',
4884
- ...handleFetchUserAttributesResponse$1,
5185
+ onDone: [
5186
+ {
5187
+ cond: 'hasPasskeyRegistrationPrompts',
5188
+ actions: 'setFetchedUserAttributes',
5189
+ target: 'checkPasskeys',
5190
+ },
5191
+ {
5192
+ actions: 'setFetchedUserAttributes',
5193
+ target: 'evaluatePasskeyPrompt',
5194
+ },
5195
+ ],
5196
+ onError: {
5197
+ actions: 'setConfirmAttributeCompleteStep',
5198
+ target: '#signInActor.resolved',
5199
+ },
5200
+ },
5201
+ },
5202
+ checkPasskeys: {
5203
+ invoke: {
5204
+ src: async () => {
5205
+ try {
5206
+ const result = await auth.listWebAuthnCredentials();
5207
+ return result.credentials && result.credentials.length > 0;
5208
+ }
5209
+ catch {
5210
+ return false;
5211
+ }
5212
+ },
5213
+ onDone: {
5214
+ actions: 'setHasExistingPasskeys',
5215
+ target: 'evaluatePasskeyPrompt',
5216
+ },
5217
+ onError: {
5218
+ actions: 'clearHasExistingPasskeys',
5219
+ target: 'evaluatePasskeyPrompt',
5220
+ },
4885
5221
  },
4886
5222
  },
5223
+ evaluatePasskeyPrompt: {
5224
+ always: [
5225
+ {
5226
+ cond: 'shouldPromptPasskeyRegistration',
5227
+ target: '#signInActor.passkeyPrompt',
5228
+ },
5229
+ {
5230
+ cond: 'shouldVerifyAttribute',
5231
+ actions: [
5232
+ 'setShouldVerifyUserAttributeStep',
5233
+ 'setUnverifiedUserAttributes',
5234
+ ],
5235
+ target: '#signInActor.resolved',
5236
+ },
5237
+ {
5238
+ actions: 'setConfirmAttributeCompleteStep',
5239
+ target: '#signInActor.resolved',
5240
+ },
5241
+ ],
5242
+ },
4887
5243
  resendSignUpCode: {
4888
5244
  invoke: {
4889
5245
  src: 'handleResendSignUpCode',
@@ -4918,23 +5274,70 @@ function signInActor({ services }) {
4918
5274
  on: {
4919
5275
  CHANGE: { actions: 'handleInput' },
4920
5276
  FEDERATED_SIGN_IN: { target: '#signInActor.federatedSignIn' },
4921
- SUBMIT: { actions: 'handleSubmit', target: 'submit' },
5277
+ SHOW_AUTH_METHODS: {
5278
+ actions: 'setUsernameSignIn',
5279
+ target: 'selectMethod',
5280
+ },
5281
+ SUBMIT: [
5282
+ {
5283
+ cond: 'shouldSelectAuthMethod',
5284
+ actions: 'handleSubmit',
5285
+ target: 'selectMethod',
5286
+ },
5287
+ {
5288
+ actions: 'handleSubmit',
5289
+ target: 'submit',
5290
+ },
5291
+ ],
5292
+ },
5293
+ },
5294
+ selectMethod: {
5295
+ entry: [
5296
+ 'sendUpdate',
5297
+ 'setSelectAuthMethodStep',
5298
+ 'setUsernameSignIn',
5299
+ ],
5300
+ on: {
5301
+ SELECT_METHOD: {
5302
+ actions: 'setSelectedAuthMethod',
5303
+ target: 'submit',
5304
+ },
5305
+ SUBMIT: {
5306
+ actions: ['handleSubmit', 'setSelectedAuthMethodFromForm'],
5307
+ target: 'submit',
5308
+ },
5309
+ SIGN_IN: {
5310
+ target: 'edit',
5311
+ },
4922
5312
  },
4923
5313
  },
4924
5314
  submit: {
4925
5315
  tags: 'pending',
4926
5316
  entry: ['clearError', 'sendUpdate', 'setUsernameSignIn'],
4927
5317
  exit: 'clearFormValues',
4928
- invoke: { src: 'handleSignIn', ...handleSignInResponse },
5318
+ invoke: {
5319
+ src: 'handleSignIn',
5320
+ onDone: handleSignInResponse.onDone,
5321
+ onError: [
5322
+ {
5323
+ cond: 'shouldReturnToSelectMethod',
5324
+ actions: 'setRemoteError',
5325
+ target: 'selectMethod',
5326
+ },
5327
+ handleSignInResponse.onError,
5328
+ ],
5329
+ },
4929
5330
  },
4930
5331
  },
4931
5332
  },
4932
- confirmSignIn: getDefaultConfirmSignInState([
4933
- 'clearChallengeName',
4934
- 'clearFormValues',
4935
- 'clearError',
4936
- 'clearTouched',
4937
- ]),
5333
+ confirmSignIn: {
5334
+ ...getDefaultConfirmSignInState([
5335
+ 'clearChallengeName',
5336
+ 'clearFormValues',
5337
+ 'clearError',
5338
+ 'clearTouched',
5339
+ ]),
5340
+ },
4938
5341
  forceChangePassword: {
4939
5342
  entry: 'sendUpdate',
4940
5343
  type: 'parallel',
@@ -5007,21 +5410,40 @@ function signInActor({ services }) {
5007
5410
  },
5008
5411
  },
5009
5412
  },
5010
- setupTotp: getDefaultConfirmSignInState([
5011
- 'clearFormValues',
5012
- 'clearError',
5013
- 'clearTouched',
5014
- ]),
5015
- setupEmail: getDefaultConfirmSignInState([
5016
- 'clearFormValues',
5017
- 'clearError',
5018
- 'clearTouched',
5019
- ]),
5020
- selectMfaType: getDefaultConfirmSignInState([
5021
- 'clearFormValues',
5022
- 'clearError',
5023
- 'clearTouched',
5024
- ]),
5413
+ setupTotp: {
5414
+ ...getDefaultConfirmSignInState([
5415
+ 'clearFormValues',
5416
+ 'clearError',
5417
+ 'clearTouched',
5418
+ ]),
5419
+ },
5420
+ setupEmail: {
5421
+ ...getDefaultConfirmSignInState([
5422
+ 'clearFormValues',
5423
+ 'clearError',
5424
+ 'clearTouched',
5425
+ ]),
5426
+ },
5427
+ selectMfaType: {
5428
+ ...getDefaultConfirmSignInState([
5429
+ 'clearFormValues',
5430
+ 'clearError',
5431
+ 'clearTouched',
5432
+ ]),
5433
+ },
5434
+ passkeyPrompt: {
5435
+ entry: 'sendUpdate',
5436
+ on: {
5437
+ SUBMIT: {
5438
+ actions: 'setConfirmAttributeCompleteStep',
5439
+ target: 'resolved',
5440
+ },
5441
+ SKIP: {
5442
+ actions: 'setConfirmAttributeCompleteStep',
5443
+ target: 'resolved',
5444
+ },
5445
+ },
5446
+ },
5025
5447
  resolved: {
5026
5448
  type: 'final',
5027
5449
  data: (context) => ({
@@ -5047,9 +5469,41 @@ function signInActor({ services }) {
5047
5469
  handleResendSignUpCode({ username }) {
5048
5470
  return services.handleResendSignUpCode({ username });
5049
5471
  },
5050
- handleSignIn({ formValues, username }) {
5051
- const { password } = formValues;
5052
- return services.handleSignIn({ username, password });
5472
+ resendSignInCode({ username, selectedAuthMethod, availableAuthMethods, preferredChallenge, }) {
5473
+ // Resend code by calling signIn again with the same parameters
5474
+ const method = selectedAuthMethod ??
5475
+ preferredChallenge ??
5476
+ availableAuthMethods?.[0] ??
5477
+ 'PASSWORD';
5478
+ return services.handleSignIn({
5479
+ username,
5480
+ options: {
5481
+ authFlowType: 'USER_AUTH',
5482
+ preferredChallenge: method,
5483
+ },
5484
+ });
5485
+ },
5486
+ handleSignIn({ formValues, username, selectedAuthMethod, availableAuthMethods, preferredChallenge, }) {
5487
+ // Determine which method to use
5488
+ const method = selectedAuthMethod ??
5489
+ preferredChallenge ??
5490
+ availableAuthMethods?.[0] ??
5491
+ 'PASSWORD';
5492
+ if (method === 'PASSWORD') {
5493
+ // Traditional password flow
5494
+ const { password } = formValues;
5495
+ return services.handleSignIn({ username, password });
5496
+ }
5497
+ else {
5498
+ // Passwordless flow using USER_AUTH
5499
+ return services.handleSignIn({
5500
+ username,
5501
+ options: {
5502
+ authFlowType: 'USER_AUTH',
5503
+ preferredChallenge: method,
5504
+ },
5505
+ });
5506
+ }
5053
5507
  },
5054
5508
  confirmSignIn({ formValues, step }) {
5055
5509
  const formValuesKey = getConfirmSignInFormValuesKey(step);
@@ -5133,26 +5587,6 @@ const handleAutoSignInResponse = {
5133
5587
  target: '#signUpActor.resolved',
5134
5588
  },
5135
5589
  };
5136
- const handleFetchUserAttributesResponse = {
5137
- onDone: [
5138
- {
5139
- cond: 'shouldVerifyAttribute',
5140
- actions: [
5141
- 'setShouldVerifyUserAttributeStep',
5142
- 'setUnverifiedUserAttributes',
5143
- ],
5144
- target: '#signUpActor.resolved',
5145
- },
5146
- {
5147
- actions: 'setConfirmAttributeCompleteStep',
5148
- target: '#signUpActor.resolved',
5149
- },
5150
- ],
5151
- onError: {
5152
- actions: 'setConfirmAttributeCompleteStep',
5153
- target: '#signUpActor.resolved',
5154
- },
5155
- };
5156
5590
  function signUpActor({ services }) {
5157
5591
  return xstate.createMachine({
5158
5592
  id: 'signUpActor',
@@ -5172,10 +5606,65 @@ function signUpActor({ services }) {
5172
5606
  fetchUserAttributes: {
5173
5607
  invoke: {
5174
5608
  src: 'fetchUserAttributes',
5175
- ...handleFetchUserAttributesResponse,
5609
+ onDone: [
5610
+ {
5611
+ cond: 'hasPasskeyRegistrationPrompts',
5612
+ actions: 'setFetchedUserAttributes',
5613
+ target: 'checkPasskeys',
5614
+ },
5615
+ {
5616
+ actions: 'setFetchedUserAttributes',
5617
+ target: 'evaluatePasskeyPrompt',
5618
+ },
5619
+ ],
5620
+ onError: {
5621
+ actions: 'setConfirmAttributeCompleteStep',
5622
+ target: '#signUpActor.resolved',
5623
+ },
5624
+ },
5625
+ },
5626
+ checkPasskeys: {
5627
+ invoke: {
5628
+ src: async () => {
5629
+ try {
5630
+ const result = await auth.listWebAuthnCredentials();
5631
+ return result.credentials && result.credentials.length > 0;
5632
+ }
5633
+ catch {
5634
+ return false;
5635
+ }
5636
+ },
5637
+ onDone: {
5638
+ actions: 'setHasExistingPasskeys',
5639
+ target: 'evaluatePasskeyPrompt',
5640
+ },
5641
+ onError: {
5642
+ actions: 'clearHasExistingPasskeys',
5643
+ target: 'evaluatePasskeyPrompt',
5644
+ },
5176
5645
  },
5177
5646
  },
5178
- federatedSignIn: getFederatedSignInState('signUp'),
5647
+ evaluatePasskeyPrompt: {
5648
+ always: [
5649
+ {
5650
+ cond: 'shouldPromptPasskeyRegistrationAfterSignup',
5651
+ target: '#signUpActor.passkeyPrompt',
5652
+ },
5653
+ {
5654
+ cond: 'shouldVerifyAttribute',
5655
+ actions: [
5656
+ 'setShouldVerifyUserAttributeStep',
5657
+ 'setUnverifiedUserAttributes',
5658
+ ],
5659
+ target: '#signUpActor.resolved',
5660
+ },
5661
+ {
5662
+ actions: 'setConfirmAttributeCompleteStep',
5663
+ target: '#signUpActor.resolved',
5664
+ },
5665
+ ],
5666
+ },
5667
+ federatedSignIn: { ...getFederatedSignInState('signUp') },
5179
5668
  resetPassword: {
5180
5669
  invoke: { src: 'resetPassword', ...handleResetPasswordResponse },
5181
5670
  },
@@ -5194,7 +5683,10 @@ function signUpActor({ services }) {
5194
5683
  cond: 'isUserAlreadyConfirmed',
5195
5684
  target: '#signUpActor.resolved',
5196
5685
  },
5197
- { actions: ['setRemoteError', 'sendUpdate'] },
5686
+ {
5687
+ actions: ['setRemoteError', 'sendUpdate'],
5688
+ target: '#signUpActor.confirmSignUp',
5689
+ },
5198
5690
  ],
5199
5691
  },
5200
5692
  },
@@ -5232,7 +5724,13 @@ function signUpActor({ services }) {
5232
5724
  idle: {
5233
5725
  entry: ['sendUpdate'],
5234
5726
  on: {
5235
- SUBMIT: { actions: 'handleSubmit', target: 'validate' },
5727
+ SUBMIT: {
5728
+ actions: [
5729
+ 'setSelectedAuthMethodFromForm',
5730
+ 'handleSubmit',
5731
+ ],
5732
+ target: 'validate',
5733
+ },
5236
5734
  },
5237
5735
  },
5238
5736
  validate: {
@@ -5317,6 +5815,13 @@ function signUpActor({ services }) {
5317
5815
  },
5318
5816
  },
5319
5817
  },
5818
+ passkeyPrompt: {
5819
+ entry: 'sendUpdate',
5820
+ on: {
5821
+ SKIP: { target: 'resolved' },
5822
+ SUBMIT: { target: 'resolved' },
5823
+ },
5824
+ },
5320
5825
  resolved: {
5321
5826
  type: 'final',
5322
5827
  data: (context) => ({
@@ -5354,9 +5859,10 @@ function signUpActor({ services }) {
5354
5859
  return auth.signInWithRedirect(data);
5355
5860
  },
5356
5861
  handleSignUp(context) {
5357
- const { formValues, loginMechanisms, username } = context;
5862
+ const { formValues, loginMechanisms, username, selectedAuthMethod, preferredChallenge, } = context;
5358
5863
  const loginMechanism = loginMechanisms[0];
5359
- const input = getSignUpInput(username, formValues, loginMechanism);
5864
+ const authMethod = selectedAuthMethod ?? preferredChallenge;
5865
+ const input = getSignUpInput(username, formValues, loginMechanism, authMethod);
5360
5866
  return services.handleSignUp(input);
5361
5867
  },
5362
5868
  async validateSignUp(context) {
@@ -5367,6 +5873,8 @@ function signUpActor({ services }) {
5367
5873
  // Validation for default form fields
5368
5874
  services.validateConfirmPassword,
5369
5875
  services.validatePreferredUsername,
5876
+ // Validation for required fields based on auth method
5877
+ services.validateRequiredFieldsForAuthMethod,
5370
5878
  // Validation for any custom Sign Up fields
5371
5879
  services.validateCustomSignUp,
5372
5880
  ]);
@@ -5498,20 +6006,29 @@ function verifyUserAttributesActor() {
5498
6006
  });
5499
6007
  }
5500
6008
 
5501
- const getActorContext = (context, defaultStep) => ({
5502
- ...context.actorDoneData,
5503
- step: context?.actorDoneData?.step ?? defaultStep,
5504
- // initialize empty objects on actor start
5505
- formValues: {},
5506
- touched: {},
5507
- validationError: {},
5508
- // values included on `context.config` that should be available in actors
5509
- formFields: context.config?.formFields,
5510
- loginMechanisms: context.config?.loginMechanisms,
5511
- passwordSettings: context.config?.passwordSettings,
5512
- signUpAttributes: context.config?.signUpAttributes,
5513
- socialProviders: context.config?.socialProviders,
5514
- });
6009
+ const getActorContext = (context, defaultStep) => {
6010
+ const availableAuthMethods = getAvailableAuthMethods(context.passwordlessCapabilities, context.config?.passwordless?.hiddenAuthMethods);
6011
+ // Determine effective preferred challenge: component prop takes precedence over backend config
6012
+ const preferredChallenge = context.config?.passwordless?.preferredAuthMethod ??
6013
+ context.passwordlessCapabilities?.preferredChallenge;
6014
+ return {
6015
+ ...context.actorDoneData,
6016
+ step: context?.actorDoneData?.step ?? defaultStep,
6017
+ // initialize empty objects on actor start
6018
+ formValues: {},
6019
+ touched: {},
6020
+ validationError: {},
6021
+ // values included on `context.config` that should be available in actors
6022
+ formFields: context.config?.formFields,
6023
+ loginMechanisms: context.config?.loginMechanisms,
6024
+ passwordSettings: context.config?.passwordSettings,
6025
+ signUpAttributes: context.config?.signUpAttributes,
6026
+ socialProviders: context.config?.socialProviders,
6027
+ availableAuthMethods,
6028
+ preferredChallenge,
6029
+ passwordless: context.config?.passwordless,
6030
+ };
6031
+ };
5515
6032
  const { choose, stop } = xstate.actions;
5516
6033
  const stopActor = (machineId) => stop(machineId);
5517
6034
  // setup step waits for ui to emit INIT action to proceed to configure
@@ -5609,6 +6126,8 @@ function createAuthenticatorMachine(options) {
5609
6126
  },
5610
6127
  on: {
5611
6128
  FORGOT_PASSWORD: 'forgotPasswordActor',
6129
+ SELECT_METHOD: { actions: 'forwardToActor' },
6130
+ SHOW_AUTH_METHODS: { actions: 'forwardToActor' },
5612
6131
  SIGN_IN: 'signInActor',
5613
6132
  SIGN_UP: 'signUpActor',
5614
6133
  'done.invoke.signInActor': [
@@ -5775,10 +6294,11 @@ function createAuthenticatorMachine(options) {
5775
6294
  }),
5776
6295
  }),
5777
6296
  applyAmplifyConfig: xstate.assign({
6297
+ passwordlessCapabilities: (_, { data: cliConfig }) => cliConfig.passwordlessCapabilities,
5778
6298
  config(context, { data: cliConfig }) {
5779
6299
  // Prefer explicitly configured settings over default CLI values\
5780
6300
  const { loginMechanisms = cliConfig.loginMechanisms ?? [], signUpAttributes = cliConfig.signUpAttributes ?? [], socialProviders = cliConfig.socialProviders ?? [], initialState, formFields: _formFields, passwordSettings = cliConfig.passwordFormat ??
5781
- {}, } = context.config;
6301
+ {}, passwordless, } = context.config;
5782
6302
  // By default, Cognito assumes `username`, so there isn't a different username attribute like `email`.
5783
6303
  // We explicitly add it as a login mechanism to be consistent with Admin UI's language.
5784
6304
  if (loginMechanisms.length === 0) {
@@ -5790,6 +6310,7 @@ function createAuthenticatorMachine(options) {
5790
6310
  initialState,
5791
6311
  loginMechanisms,
5792
6312
  passwordSettings,
6313
+ passwordless,
5793
6314
  signUpAttributes,
5794
6315
  socialProviders,
5795
6316
  };