@aws-amplify/auth 6.5.0 → 6.5.1-email-mfa-test.4e655bc.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 (33) hide show
  1. package/dist/cjs/common/AuthErrorStrings.js +2 -2
  2. package/dist/cjs/common/AuthErrorStrings.js.map +1 -1
  3. package/dist/cjs/providers/cognito/apis/confirmSignIn.js +2 -2
  4. package/dist/cjs/providers/cognito/apis/confirmSignIn.js.map +1 -1
  5. package/dist/cjs/providers/cognito/apis/updateMFAPreference.js +2 -1
  6. package/dist/cjs/providers/cognito/apis/updateMFAPreference.js.map +1 -1
  7. package/dist/cjs/providers/cognito/utils/oauth/getRedirectUrl.js +3 -1
  8. package/dist/cjs/providers/cognito/utils/oauth/getRedirectUrl.js.map +1 -1
  9. package/dist/cjs/providers/cognito/utils/signInHelpers.js +183 -130
  10. package/dist/cjs/providers/cognito/utils/signInHelpers.js.map +1 -1
  11. package/dist/esm/common/AuthErrorStrings.mjs +2 -2
  12. package/dist/esm/common/AuthErrorStrings.mjs.map +1 -1
  13. package/dist/esm/foundation/factories/serviceClients/cognitoIdentityProvider/types/sdk.d.ts +24 -3
  14. package/dist/esm/providers/cognito/apis/confirmSignIn.mjs +2 -2
  15. package/dist/esm/providers/cognito/apis/confirmSignIn.mjs.map +1 -1
  16. package/dist/esm/providers/cognito/apis/updateMFAPreference.mjs +2 -1
  17. package/dist/esm/providers/cognito/apis/updateMFAPreference.mjs.map +1 -1
  18. package/dist/esm/providers/cognito/types/inputs.d.ts +1 -0
  19. package/dist/esm/providers/cognito/utils/oauth/getRedirectUrl.mjs +3 -1
  20. package/dist/esm/providers/cognito/utils/oauth/getRedirectUrl.mjs.map +1 -1
  21. package/dist/esm/providers/cognito/utils/signInHelpers.d.ts +4 -3
  22. package/dist/esm/providers/cognito/utils/signInHelpers.mjs +181 -127
  23. package/dist/esm/providers/cognito/utils/signInHelpers.mjs.map +1 -1
  24. package/dist/esm/types/models.d.ts +44 -2
  25. package/package.json +104 -104
  26. package/src/common/AuthErrorStrings.ts +4 -2
  27. package/src/foundation/factories/serviceClients/cognitoIdentityProvider/types/sdk.ts +23 -1
  28. package/src/providers/cognito/apis/confirmSignIn.ts +2 -2
  29. package/src/providers/cognito/apis/updateMFAPreference.ts +2 -1
  30. package/src/providers/cognito/types/inputs.ts +1 -0
  31. package/src/providers/cognito/utils/oauth/getRedirectUrl.ts +1 -0
  32. package/src/providers/cognito/utils/signInHelpers.ts +240 -166
  33. package/src/types/models.ts +48 -1
@@ -8,6 +8,7 @@ import { MetadataBearer as __MetadataBearer } from '@aws-sdk/types';
8
8
  export type ChallengeName =
9
9
  | 'SMS_MFA'
10
10
  | 'SOFTWARE_TOKEN_MFA'
11
+ | 'EMAIL_OTP'
11
12
  | 'SELECT_MFA_TYPE'
12
13
  | 'MFA_SETUP'
13
14
  | 'PASSWORD_VERIFIER'
@@ -28,7 +29,7 @@ export type ChallengeParameters = {
28
29
  MFAS_CAN_SETUP?: string;
29
30
  } & Record<string, unknown>;
30
31
 
31
- export type CognitoMFAType = 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA';
32
+ export type CognitoMFAType = 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' | 'EMAIL_OTP';
32
33
 
33
34
  export interface CognitoMFASettings {
34
35
  Enabled?: boolean;
@@ -55,6 +56,7 @@ declare enum ChallengeNameType {
55
56
  SELECT_MFA_TYPE = 'SELECT_MFA_TYPE',
56
57
  SMS_MFA = 'SMS_MFA',
57
58
  SOFTWARE_TOKEN_MFA = 'SOFTWARE_TOKEN_MFA',
59
+ EMAIL_OTP = 'EMAIL_OTP',
58
60
  }
59
61
  declare enum DeliveryMediumType {
60
62
  EMAIL = 'EMAIL',
@@ -1430,6 +1432,10 @@ export interface SetUserMFAPreferenceRequest {
1430
1432
  * <p>The time-based one-time password software token MFA settings.</p>
1431
1433
  */
1432
1434
  SoftwareTokenMfaSettings?: SoftwareTokenMfaSettingsType;
1435
+ /**
1436
+ * <p>The email message multi-factor authentication (MFA) settings.</p>
1437
+ */
1438
+ EmailMfaSettings?: EmailMfaSettingsType;
1433
1439
  /**
1434
1440
  * <p>The access token for the user.</p>
1435
1441
  */
@@ -1538,6 +1544,22 @@ export interface SoftwareTokenMfaSettingsType {
1538
1544
  */
1539
1545
  PreferredMfa?: boolean;
1540
1546
  }
1547
+ /**
1548
+ * <p>The type used for enabling email MFA at the user level. If an MFA type is activated for a user, the user will be prompted for MFA during all sign-in attempts, unless device tracking
1549
+ * is turned on and the device has been trusted. If you want MFA to be applied selectively based on the assessed risk level of sign-in attempts, deactivate MFA for users and turn on Adaptive
1550
+ * Authentication for the user pool.</p>
1551
+ */
1552
+ export interface EmailMfaSettingsType {
1553
+ /**
1554
+ * <p>Specifies whether email MFA is activated. If an MFA type is activated for a user, the user will be prompted for MFA during all sign-in attempts, unless device tracking is turned
1555
+ * on and the device has been trusted.</p>
1556
+ */
1557
+ Enabled?: boolean;
1558
+ /**
1559
+ * <p>Specifies whether email MFA is the preferred MFA method.</p>
1560
+ */
1561
+ PreferredMfa?: boolean;
1562
+ }
1541
1563
  export type UpdateDeviceStatusCommandInput = UpdateDeviceStatusRequest;
1542
1564
  export interface UpdateDeviceStatusCommandOutput
1543
1565
  extends UpdateDeviceStatusResponse,
@@ -71,8 +71,8 @@ export async function confirmSignIn(
71
71
  throw new AuthError({
72
72
  name: AuthErrorCodes.SignInException,
73
73
  message: `
74
- An error occurred during the sign in process.
75
-
74
+ An error occurred during the sign in process.
75
+
76
76
  This most likely occurred due to:
77
77
  1. signIn was not called before confirmSignIn.
78
78
  2. signIn threw an exception.
@@ -27,7 +27,7 @@ import { createCognitoUserPoolEndpointResolver } from '../factories';
27
27
  export async function updateMFAPreference(
28
28
  input: UpdateMFAPreferenceInput,
29
29
  ): Promise<void> {
30
- const { sms, totp } = input;
30
+ const { sms, totp, email } = input;
31
31
  const authConfig = Amplify.getConfig().Auth?.Cognito;
32
32
  assertTokenProviderConfig(authConfig);
33
33
  const { userPoolEndpoint, userPoolId } = authConfig;
@@ -47,6 +47,7 @@ export async function updateMFAPreference(
47
47
  AccessToken: tokens.accessToken.toString(),
48
48
  SMSMfaSettings: getMFASettings(sms),
49
49
  SoftwareTokenMfaSettings: getMFASettings(totp),
50
+ EmailMfaSettings: getMFASettings(email),
50
51
  },
51
52
  );
52
53
  }
@@ -118,6 +118,7 @@ export type SignUpInput = AuthSignUpInput<SignUpOptions<UserAttributeKey>>;
118
118
  export interface UpdateMFAPreferenceInput {
119
119
  sms?: MFAPreference;
120
120
  totp?: MFAPreference;
121
+ email?: MFAPreference;
121
122
  }
122
123
 
123
124
  /**
@@ -39,6 +39,7 @@ export function getRedirectUrl(
39
39
  // origin + pathname => https://example.com/app
40
40
  const isSameOriginAndPathName = (redirect: string) =>
41
41
  redirect.startsWith(
42
+ // eslint-disable-next-line no-constant-binary-expression
42
43
  String(window.location.origin + window.location.pathname ?? '/'),
43
44
  );
44
45
  // domain => outlook.live.com, github.com
@@ -154,49 +154,110 @@ export async function handleMFASetupChallenge({
154
154
  config,
155
155
  }: HandleAuthChallengeRequest): Promise<RespondToAuthChallengeCommandOutput> {
156
156
  const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
157
- const challengeResponses = {
157
+
158
+ if (challengeResponse === 'EMAIL') {
159
+ return {
160
+ ChallengeName: 'MFA_SETUP',
161
+ Session: session,
162
+ ChallengeParameters: {
163
+ MFAS_CAN_SETUP: '["EMAIL_OTP"]',
164
+ },
165
+ $metadata: {},
166
+ };
167
+ }
168
+
169
+ if (challengeResponse === 'TOTP') {
170
+ return {
171
+ ChallengeName: 'MFA_SETUP',
172
+ Session: session,
173
+ ChallengeParameters: {
174
+ MFAS_CAN_SETUP: '["SOFTWARE_TOKEN_MFA"]',
175
+ },
176
+ $metadata: {},
177
+ };
178
+ }
179
+
180
+ const challengeResponses: Record<string, string> = {
158
181
  USERNAME: username,
159
182
  };
160
- const verifySoftwareToken = createVerifySoftwareTokenClient({
161
- endpointResolver: createCognitoUserPoolEndpointResolver({
162
- endpointOverride: userPoolEndpoint,
163
- }),
164
- });
165
- const { Session } = await verifySoftwareToken(
166
- {
167
- region: getRegionFromUserPoolId(userPoolId),
168
- userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn),
169
- },
170
- {
171
- UserCode: challengeResponse,
183
+
184
+ const isTOTPCode = /^\d+$/.test(challengeResponse.trim());
185
+
186
+ if (isTOTPCode) {
187
+ const verifySoftwareToken = createVerifySoftwareTokenClient({
188
+ endpointResolver: createCognitoUserPoolEndpointResolver({
189
+ endpointOverride: userPoolEndpoint,
190
+ }),
191
+ });
192
+
193
+ const { Session } = await verifySoftwareToken(
194
+ {
195
+ region: getRegionFromUserPoolId(userPoolId),
196
+ userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn),
197
+ },
198
+ {
199
+ UserCode: challengeResponse,
200
+ Session: session,
201
+ FriendlyDeviceName: deviceName,
202
+ },
203
+ );
204
+
205
+ signInStore.dispatch({
206
+ type: 'SET_SIGN_IN_SESSION',
207
+ value: Session,
208
+ });
209
+
210
+ const jsonReq: RespondToAuthChallengeCommandInput = {
211
+ ChallengeName: 'MFA_SETUP',
212
+ ChallengeResponses: challengeResponses,
213
+ Session,
214
+ ClientMetadata: clientMetadata,
215
+ ClientId: userPoolClientId,
216
+ };
217
+
218
+ const respondToAuthChallenge = createRespondToAuthChallengeClient({
219
+ endpointResolver: createCognitoUserPoolEndpointResolver({
220
+ endpointOverride: userPoolEndpoint,
221
+ }),
222
+ });
223
+
224
+ return respondToAuthChallenge(
225
+ { region: getRegionFromUserPoolId(userPoolId) },
226
+ jsonReq,
227
+ );
228
+ }
229
+
230
+ const isEmail = /^\S+@\S+\.\S+$/.test(challengeResponse.trim());
231
+
232
+ if (isEmail) {
233
+ challengeResponses.EMAIL = challengeResponse;
234
+
235
+ const jsonReq: RespondToAuthChallengeCommandInput = {
236
+ ChallengeName: 'MFA_SETUP',
237
+ ChallengeResponses: challengeResponses,
172
238
  Session: session,
173
- FriendlyDeviceName: deviceName,
174
- },
175
- );
239
+ ClientMetadata: clientMetadata,
240
+ ClientId: userPoolClientId,
241
+ };
176
242
 
177
- signInStore.dispatch({
178
- type: 'SET_SIGN_IN_SESSION',
179
- value: Session,
180
- });
243
+ const respondToAuthChallenge = createRespondToAuthChallengeClient({
244
+ endpointResolver: createCognitoUserPoolEndpointResolver({
245
+ endpointOverride: userPoolEndpoint,
246
+ }),
247
+ });
181
248
 
182
- const jsonReq: RespondToAuthChallengeCommandInput = {
183
- ChallengeName: 'MFA_SETUP',
184
- ChallengeResponses: challengeResponses,
185
- Session,
186
- ClientMetadata: clientMetadata,
187
- ClientId: userPoolClientId,
188
- };
249
+ return respondToAuthChallenge(
250
+ { region: getRegionFromUserPoolId(userPoolId) },
251
+ jsonReq,
252
+ );
253
+ }
189
254
 
190
- const respondToAuthChallenge = createRespondToAuthChallengeClient({
191
- endpointResolver: createCognitoUserPoolEndpointResolver({
192
- endpointOverride: userPoolEndpoint,
193
- }),
255
+ throw new AuthError({
256
+ name: AuthErrorCodes.SignInException,
257
+ message: `Cannot proceed with MFA setup using challengeResponse: ${challengeResponse}`,
258
+ recoverySuggestion:
259
+ 'Try passing "EMAIL", "TOTP", a valid email, or OTP code as the challengeResponse.',
194
260
  });
195
-
196
- return respondToAuthChallenge(
197
- { region: getRegionFromUserPoolId(userPoolId) },
198
- jsonReq,
199
- );
200
261
  }
201
262
 
202
263
  export async function handleSelectMFATypeChallenge({
@@ -208,7 +269,9 @@ export async function handleSelectMFATypeChallenge({
208
269
  }: HandleAuthChallengeRequest): Promise<RespondToAuthChallengeCommandOutput> {
209
270
  const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
210
271
  assertValidationError(
211
- challengeResponse === 'TOTP' || challengeResponse === 'SMS',
272
+ challengeResponse === 'TOTP' ||
273
+ challengeResponse === 'SMS' ||
274
+ challengeResponse === 'EMAIL',
212
275
  AuthValidationErrorCode.IncorrectMFAMethod,
213
276
  );
214
277
 
@@ -247,88 +310,6 @@ export async function handleSelectMFATypeChallenge({
247
310
  );
248
311
  }
249
312
 
250
- export async function handleSMSMFAChallenge({
251
- challengeResponse,
252
- clientMetadata,
253
- session,
254
- username,
255
- config,
256
- }: HandleAuthChallengeRequest): Promise<RespondToAuthChallengeCommandOutput> {
257
- const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
258
- const challengeResponses = {
259
- USERNAME: username,
260
- SMS_MFA_CODE: challengeResponse,
261
- };
262
- const UserContextData = getUserContextData({
263
- username,
264
- userPoolId,
265
- userPoolClientId,
266
- });
267
- const jsonReq: RespondToAuthChallengeCommandInput = {
268
- ChallengeName: 'SMS_MFA',
269
- ChallengeResponses: challengeResponses,
270
- Session: session,
271
- ClientMetadata: clientMetadata,
272
- ClientId: userPoolClientId,
273
- UserContextData,
274
- };
275
-
276
- const respondToAuthChallenge = createRespondToAuthChallengeClient({
277
- endpointResolver: createCognitoUserPoolEndpointResolver({
278
- endpointOverride: userPoolEndpoint,
279
- }),
280
- });
281
-
282
- return respondToAuthChallenge(
283
- {
284
- region: getRegionFromUserPoolId(userPoolId),
285
- userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn),
286
- },
287
- jsonReq,
288
- );
289
- }
290
- export async function handleSoftwareTokenMFAChallenge({
291
- challengeResponse,
292
- clientMetadata,
293
- session,
294
- username,
295
- config,
296
- }: HandleAuthChallengeRequest): Promise<RespondToAuthChallengeCommandOutput> {
297
- const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
298
- const challengeResponses = {
299
- USERNAME: username,
300
- SOFTWARE_TOKEN_MFA_CODE: challengeResponse,
301
- };
302
-
303
- const UserContextData = getUserContextData({
304
- username,
305
- userPoolId,
306
- userPoolClientId,
307
- });
308
-
309
- const jsonReq: RespondToAuthChallengeCommandInput = {
310
- ChallengeName: 'SOFTWARE_TOKEN_MFA',
311
- ChallengeResponses: challengeResponses,
312
- Session: session,
313
- ClientMetadata: clientMetadata,
314
- ClientId: userPoolClientId,
315
- UserContextData,
316
- };
317
-
318
- const respondToAuthChallenge = createRespondToAuthChallengeClient({
319
- endpointResolver: createCognitoUserPoolEndpointResolver({
320
- endpointOverride: userPoolEndpoint,
321
- }),
322
- });
323
-
324
- return respondToAuthChallenge(
325
- {
326
- region: getRegionFromUserPoolId(userPoolId),
327
- userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn),
328
- },
329
- jsonReq,
330
- );
331
- }
332
313
  export async function handleCompleteNewPasswordChallenge({
333
314
  challengeResponse,
334
315
  clientMetadata,
@@ -842,37 +823,65 @@ export async function getSignInResult(params: {
842
823
  case 'MFA_SETUP': {
843
824
  const { signInSession, username } = signInStore.getState();
844
825
 
845
- if (!isMFATypeEnabled(challengeParameters, 'TOTP'))
846
- throw new AuthError({
847
- name: AuthErrorCodes.SignInException,
848
- message: `Cannot initiate MFA setup from available types: ${getMFATypes(
849
- parseMFATypes(challengeParameters.MFAS_CAN_SETUP),
850
- )}`,
826
+ const mfaSetupTypes =
827
+ getMFATypes(parseMFATypes(challengeParameters.MFAS_CAN_SETUP)) || [];
828
+
829
+ const allowedMfaSetupTypes = getAllowedMfaSetupTypes(mfaSetupTypes);
830
+
831
+ const isTotpMfaSetupAvailable = allowedMfaSetupTypes.includes('TOTP');
832
+ const isEmailMfaSetupAvailable = allowedMfaSetupTypes.includes('EMAIL');
833
+
834
+ if (isTotpMfaSetupAvailable && isEmailMfaSetupAvailable) {
835
+ return {
836
+ isSignedIn: false,
837
+ nextStep: {
838
+ signInStep: 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION',
839
+ allowedMFATypes: allowedMfaSetupTypes,
840
+ },
841
+ };
842
+ }
843
+
844
+ if (isEmailMfaSetupAvailable) {
845
+ return {
846
+ isSignedIn: false,
847
+ nextStep: {
848
+ signInStep: 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP',
849
+ },
850
+ };
851
+ }
852
+
853
+ if (isTotpMfaSetupAvailable) {
854
+ const associateSoftwareToken = createAssociateSoftwareTokenClient({
855
+ endpointResolver: createCognitoUserPoolEndpointResolver({
856
+ endpointOverride: authConfig.userPoolEndpoint,
857
+ }),
858
+ });
859
+ const { Session, SecretCode: secretCode } =
860
+ await associateSoftwareToken(
861
+ { region: getRegionFromUserPoolId(authConfig.userPoolId) },
862
+ {
863
+ Session: signInSession,
864
+ },
865
+ );
866
+
867
+ signInStore.dispatch({
868
+ type: 'SET_SIGN_IN_SESSION',
869
+ value: Session,
851
870
  });
852
871
 
853
- const associateSoftwareToken = createAssociateSoftwareTokenClient({
854
- endpointResolver: createCognitoUserPoolEndpointResolver({
855
- endpointOverride: authConfig.userPoolEndpoint,
856
- }),
857
- });
858
- const { Session, SecretCode: secretCode } = await associateSoftwareToken(
859
- { region: getRegionFromUserPoolId(authConfig.userPoolId) },
860
- {
861
- Session: signInSession,
862
- },
863
- );
864
- signInStore.dispatch({
865
- type: 'SET_SIGN_IN_SESSION',
866
- value: Session,
867
- });
872
+ return {
873
+ isSignedIn: false,
874
+ nextStep: {
875
+ signInStep: 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP',
876
+ totpSetupDetails: getTOTPSetupDetails(secretCode!, username),
877
+ },
878
+ };
879
+ }
868
880
 
869
- return {
870
- isSignedIn: false,
871
- nextStep: {
872
- signInStep: 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP',
873
- totpSetupDetails: getTOTPSetupDetails(secretCode!, username),
874
- },
875
- };
881
+ throw new AuthError({
882
+ name: AuthErrorCodes.SignInException,
883
+ message: `Cannot initiate MFA setup from available types: ${mfaSetupTypes}`,
884
+ });
876
885
  }
877
886
  case 'NEW_PASSWORD_REQUIRED':
878
887
  return {
@@ -913,6 +922,18 @@ export async function getSignInResult(params: {
913
922
  signInStep: 'CONFIRM_SIGN_IN_WITH_TOTP_CODE',
914
923
  },
915
924
  };
925
+ case 'EMAIL_OTP':
926
+ return {
927
+ isSignedIn: false,
928
+ nextStep: {
929
+ signInStep: 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE',
930
+ codeDeliveryDetails: {
931
+ deliveryMedium:
932
+ challengeParameters.CODE_DELIVERY_DELIVERY_MEDIUM as AuthDeliveryMedium,
933
+ destination: challengeParameters.CODE_DELIVERY_DESTINATION,
934
+ },
935
+ },
936
+ };
916
937
  case 'ADMIN_NO_SRP_AUTH':
917
938
  break;
918
939
  case 'DEVICE_PASSWORD_VERIFIER':
@@ -1000,14 +1021,6 @@ export async function handleChallengeName(
1000
1021
  const deviceName = options?.friendlyDeviceName;
1001
1022
 
1002
1023
  switch (challengeName) {
1003
- case 'SMS_MFA':
1004
- return handleSMSMFAChallenge({
1005
- challengeResponse,
1006
- clientMetadata,
1007
- session,
1008
- username,
1009
- config,
1010
- });
1011
1024
  case 'SELECT_MFA_TYPE':
1012
1025
  return handleSelectMFATypeChallenge({
1013
1026
  challengeResponse,
@@ -1050,8 +1063,11 @@ export async function handleChallengeName(
1050
1063
  username,
1051
1064
  tokenOrchestrator,
1052
1065
  );
1066
+ case 'SMS_MFA':
1053
1067
  case 'SOFTWARE_TOKEN_MFA':
1054
- return handleSoftwareTokenMFAChallenge({
1068
+ case 'EMAIL_OTP':
1069
+ return handleMFAChallenge({
1070
+ challengeName,
1055
1071
  challengeResponse,
1056
1072
  clientMetadata,
1057
1073
  session,
@@ -1062,7 +1078,7 @@ export async function handleChallengeName(
1062
1078
  // TODO: remove this error message for production apps
1063
1079
  throw new AuthError({
1064
1080
  name: AuthErrorCodes.SignInException,
1065
- message: `An error occurred during the sign in process.
1081
+ message: `An error occurred during the sign in process.
1066
1082
  ${challengeName} challengeName returned by the underlying service was not addressed.`,
1067
1083
  });
1068
1084
  }
@@ -1070,6 +1086,7 @@ export async function handleChallengeName(
1070
1086
  export function mapMfaType(mfa: string): CognitoMFAType {
1071
1087
  let mfaType: CognitoMFAType = 'SMS_MFA';
1072
1088
  if (mfa === 'TOTP') mfaType = 'SOFTWARE_TOKEN_MFA';
1089
+ if (mfa === 'EMAIL') mfaType = 'EMAIL_OTP';
1073
1090
 
1074
1091
  return mfaType;
1075
1092
  }
@@ -1077,6 +1094,7 @@ export function mapMfaType(mfa: string): CognitoMFAType {
1077
1094
  export function getMFAType(type?: string): AuthMFAType | undefined {
1078
1095
  if (type === 'SMS_MFA') return 'SMS';
1079
1096
  if (type === 'SOFTWARE_TOKEN_MFA') return 'TOTP';
1097
+ if (type === 'EMAIL_OTP') return 'EMAIL';
1080
1098
  // TODO: log warning for unknown MFA type
1081
1099
  }
1082
1100
 
@@ -1091,15 +1109,10 @@ export function parseMFATypes(mfa?: string): CognitoMFAType[] {
1091
1109
  return JSON.parse(mfa) as CognitoMFAType[];
1092
1110
  }
1093
1111
 
1094
- export function isMFATypeEnabled(
1095
- challengeParams: ChallengeParameters,
1096
- mfaType: AuthMFAType,
1097
- ): boolean {
1098
- const { MFAS_CAN_SETUP } = challengeParams;
1099
- const mfaTypes = getMFATypes(parseMFATypes(MFAS_CAN_SETUP));
1100
- if (!mfaTypes) return false;
1101
-
1102
- return mfaTypes.includes(mfaType);
1112
+ export function getAllowedMfaSetupTypes(availableMfaSetupTypes: AuthMFAType[]) {
1113
+ return availableMfaSetupTypes.filter(
1114
+ authMfaType => authMfaType === 'EMAIL' || authMfaType === 'TOTP',
1115
+ );
1103
1116
  }
1104
1117
 
1105
1118
  export async function assertUserNotAuthenticated() {
@@ -1230,3 +1243,64 @@ export function getActiveSignInUsername(username: string): string {
1230
1243
 
1231
1244
  return state.username ?? username;
1232
1245
  }
1246
+
1247
+ export async function handleMFAChallenge({
1248
+ challengeName,
1249
+ challengeResponse,
1250
+ clientMetadata,
1251
+ session,
1252
+ username,
1253
+ config,
1254
+ }: HandleAuthChallengeRequest & {
1255
+ challengeName: Extract<
1256
+ ChallengeName,
1257
+ 'EMAIL_OTP' | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA'
1258
+ >;
1259
+ }) {
1260
+ const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
1261
+
1262
+ const challengeResponses: Record<string, string> = {
1263
+ USERNAME: username,
1264
+ };
1265
+
1266
+ if (challengeName === 'EMAIL_OTP') {
1267
+ challengeResponses.EMAIL_OTP_CODE = challengeResponse;
1268
+ }
1269
+
1270
+ if (challengeName === 'SMS_MFA') {
1271
+ challengeResponses.SMS_MFA_CODE = challengeResponse;
1272
+ }
1273
+
1274
+ if (challengeName === 'SOFTWARE_TOKEN_MFA') {
1275
+ challengeResponses.SOFTWARE_TOKEN_MFA_CODE = challengeResponse;
1276
+ }
1277
+
1278
+ const userContextData = getUserContextData({
1279
+ username,
1280
+ userPoolId,
1281
+ userPoolClientId,
1282
+ });
1283
+
1284
+ const jsonReq: RespondToAuthChallengeCommandInput = {
1285
+ ChallengeName: challengeName,
1286
+ ChallengeResponses: challengeResponses,
1287
+ Session: session,
1288
+ ClientMetadata: clientMetadata,
1289
+ ClientId: userPoolClientId,
1290
+ UserContextData: userContextData,
1291
+ };
1292
+
1293
+ const respondToAuthChallenge = createRespondToAuthChallengeClient({
1294
+ endpointResolver: createCognitoUserPoolEndpointResolver({
1295
+ endpointOverride: userPoolEndpoint,
1296
+ }),
1297
+ });
1298
+
1299
+ return respondToAuthChallenge(
1300
+ {
1301
+ region: getRegionFromUserPoolId(userPoolId),
1302
+ userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn),
1303
+ },
1304
+ jsonReq,
1305
+ );
1306
+ }
@@ -44,7 +44,7 @@ export interface AuthTOTPSetupDetails {
44
44
  getSetupUri(appName: string, accountName?: string): URL;
45
45
  }
46
46
 
47
- export type AuthMFAType = 'SMS' | 'TOTP';
47
+ export type AuthMFAType = 'SMS' | 'TOTP' | 'EMAIL';
48
48
 
49
49
  export type AuthAllowedMFATypes = AuthMFAType[];
50
50
 
@@ -63,6 +63,20 @@ export interface ContinueSignInWithTOTPSetup {
63
63
  signInStep: 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP';
64
64
  totpSetupDetails: AuthTOTPSetupDetails;
65
65
  }
66
+ export interface ContinueSignInWithEmailSetup {
67
+ /**
68
+ * Auth step requires user to set up EMAIL as multifactor authentication by associating an email address
69
+ * and entering the OTP.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Code retrieved from email
74
+ * const emailAddress = 'example@example.com';
75
+ * await confirmSignIn({challengeResponse: emailAddress });
76
+ * ```
77
+ */
78
+ signInStep: 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP';
79
+ }
66
80
  export interface ConfirmSignInWithTOTPCode {
67
81
  /**
68
82
  * Auth step requires user to use TOTP as multifactor authentication by retriving an OTP code from authenticator app.
@@ -92,6 +106,21 @@ export interface ContinueSignInWithMFASelection {
92
106
  allowedMFATypes?: AuthAllowedMFATypes;
93
107
  }
94
108
 
109
+ export interface ContinueSignInWithMFASetupSelection {
110
+ /**
111
+ * Auth step requires user to select an mfa option (SMS | TOTP) to setup before continuing the sign-in flow.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * await confirmSignIn({challengeResponse:'TOTP'});
116
+ * // OR
117
+ * await confirmSignIn({challengeResponse:'EMAIL'});
118
+ * ```
119
+ */
120
+ signInStep: 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION';
121
+ allowedMFATypes?: AuthAllowedMFATypes;
122
+ }
123
+
95
124
  export interface ConfirmSignInWithCustomChallenge {
96
125
  /**
97
126
  * Auth step requires user to respond to a custom challenge.
@@ -146,6 +175,21 @@ export interface ConfirmSignInWithSMSCode {
146
175
  codeDeliveryDetails?: AuthCodeDeliveryDetails;
147
176
  }
148
177
 
178
+ export interface ConfirmSignInWithEmailCode {
179
+ /**
180
+ * Auth step requires user to use EMAIL as multifactor authentication by retrieving a code sent to inbox.
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * // Code retrieved from email
185
+ * const emailCode = '112233'
186
+ * await confirmSignIn({challengeResponse: emailCode})
187
+ * ```
188
+ */
189
+ signInStep: 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE';
190
+ codeDeliveryDetails?: AuthCodeDeliveryDetails;
191
+ }
192
+
149
193
  export interface ConfirmSignUpStep {
150
194
  /**
151
195
  * Auth step requires to confirm user's sign-up.
@@ -181,7 +225,10 @@ export type AuthNextSignInStep<
181
225
  | ConfirmSignInWithNewPasswordRequired<UserAttributeKey>
182
226
  | ConfirmSignInWithSMSCode
183
227
  | ConfirmSignInWithTOTPCode
228
+ | ConfirmSignInWithEmailCode
184
229
  | ContinueSignInWithTOTPSetup
230
+ | ContinueSignInWithEmailSetup
231
+ | ContinueSignInWithMFASetupSelection
185
232
  | ConfirmSignUpStep
186
233
  | ResetPasswordStep
187
234
  | DoneSignInStep;