diff --git a/.changeset/new-houses-share.md b/.changeset/new-houses-share.md new file mode 100644 index 00000000000..9a801f229bf --- /dev/null +++ b/.changeset/new-houses-share.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/auth-construct': minor +--- + +support email mfa diff --git a/.changeset/smart-parks-guess.md b/.changeset/smart-parks-guess.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/smart-parks-guess.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/amplify_outputs.json b/amplify_outputs.json new file mode 100644 index 00000000000..09335998983 --- /dev/null +++ b/amplify_outputs.json @@ -0,0 +1,3 @@ +{ + "version": "1.4" +} diff --git a/packages/auth-construct/API.md b/packages/auth-construct/API.md index 6c2a137ce37..cfce6840c45 100644 --- a/packages/auth-construct/API.md +++ b/packages/auth-construct/API.md @@ -144,13 +144,22 @@ export type MFA = { mode: 'OPTIONAL' | 'REQUIRED'; } & MFASettings); +// @public +export type MFAEmailSettings = boolean; + // @public export type MFASettings = { + totp?: MFATotpSettings; + sms?: MFASmsSettings; + email: MFAEmailSettings; +} | { totp?: MFATotpSettings; sms: MFASmsSettings; + email?: MFAEmailSettings; } | { totp: MFATotpSettings; sms?: MFASmsSettings; + email?: MFAEmailSettings; }; // @public diff --git a/packages/auth-construct/src/construct.test.ts b/packages/auth-construct/src/construct.test.ts index 35a65167e06..605bba292a5 100644 --- a/packages/auth-construct/src/construct.test.ts +++ b/packages/auth-construct/src/construct.test.ts @@ -300,7 +300,7 @@ void describe('Auth construct', () => { }); }); - void it('creates email login mechanism with MFA', () => { + void it('creates email login mechanism with SMS MFA', () => { const app = new App(); const stack = new Stack(app); const emailBodyFunction = (createCode: () => string) => @@ -348,6 +348,33 @@ void describe('Auth construct', () => { }); }); + void it('creates email login mechanism with email MFA', () => { + const app = new App(); + const stack = new Stack(app); + new AmplifyAuth(stack, 'test', { + loginWith: { + email: true, + }, + multifactor: { + mode: 'OPTIONAL', + email: true, + }, + senders: { + email: { + fromEmail: 'noreply@yourdomain.com', + fromName: 'testAdmin', + replyTo: 'noreply@yourdomain.com', + }, + }, + accountRecovery: 'EMAIL_AND_PHONE_WITHOUT_MFA', + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Cognito::UserPool', { + MfaConfiguration: 'OPTIONAL', + EnabledMfas: ['EMAIL_OTP'], + }); + }); + void it('throws error if invalid email verification message for CODE', () => { const app = new App(); const stack = new Stack(app); @@ -1169,6 +1196,28 @@ void describe('Auth construct', () => { assert.equal(outputs['mfaConfiguration']['Value'], 'ON'); }); + void it('updates mfaConfiguration & mfaTypes when MFA is set to REQUIRED with only EMAIL enabled', () => { + new AmplifyAuth(stack, 'test', { + loginWith: { + email: true, + }, + multifactor: { mode: 'REQUIRED', email: true }, + senders: { + email: { + fromEmail: 'noreply@yourdomain.com', + fromName: 'testAdmin', + replyTo: 'noreply@yourdomain.com', + }, + }, + accountRecovery: 'EMAIL_AND_PHONE_WITHOUT_MFA', + }); + + const template = Template.fromStack(stack); + const outputs = template.findOutputs('*'); + assert.equal(outputs['mfaTypes']['Value'], '["EMAIL"]'); + assert.equal(outputs['mfaConfiguration']['Value'], 'ON'); + }); + void it('updates socialProviders and oauth outputs when external providers are present', () => { new AmplifyAuth(stack, 'test', { loginWith: { diff --git a/packages/auth-construct/src/construct.ts b/packages/auth-construct/src/construct.ts index 417475262f9..2616eb5da4a 100644 --- a/packages/auth-construct/src/construct.ts +++ b/packages/auth-construct/src/construct.ts @@ -819,6 +819,7 @@ export class AmplifyAuth ? { sms: mfa.sms ? true : false, otp: mfa.totp ? true : false, + email: mfa.email ? true : false, } : undefined; }; @@ -1230,6 +1231,9 @@ export class AmplifyAuth if (type === 'SOFTWARE_TOKEN_MFA') { mfaTypes.push('TOTP'); } + if (type === 'EMAIL_OTP') { + mfaTypes.push('EMAIL'); + } }); return JSON.stringify(mfaTypes); }, diff --git a/packages/auth-construct/src/index.ts b/packages/auth-construct/src/index.ts index 86f3dd3a4a0..272065a5a3b 100644 --- a/packages/auth-construct/src/index.ts +++ b/packages/auth-construct/src/index.ts @@ -14,6 +14,7 @@ export { MFA, MFASmsSettings, MFATotpSettings, + MFAEmailSettings, MFASettings, PhoneNumberLogin, TriggerEvent, diff --git a/packages/auth-construct/src/types.ts b/packages/auth-construct/src/types.ts index 3823690c098..6cce15bda29 100644 --- a/packages/auth-construct/src/types.ts +++ b/packages/auth-construct/src/types.ts @@ -125,15 +125,30 @@ export type MFASmsSettings = * @see - https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-mfa-totp.html */ export type MFATotpSettings = boolean; +/** + * If true, the MFA token is sent to the user via email. + * @see - https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-mfa-sms-email-message.html + */ +export type MFAEmailSettings = boolean; /** * Configure the MFA types that users can use. At least one of totp or sms is required. */ export type MFASettings = + | { + totp?: MFATotpSettings; + sms?: MFASmsSettings; + email: MFAEmailSettings; + } | { totp?: MFATotpSettings; sms: MFASmsSettings; + email?: MFAEmailSettings; } - | { totp: MFATotpSettings; sms?: MFASmsSettings }; + | { + totp: MFATotpSettings; + sms?: MFASmsSettings; + email?: MFAEmailSettings; + }; /** * MFA configuration. MFA settings are required if the mode is either "OPTIONAL" or "REQUIRED"