From b5c60d50a6c4fb7e93185c5874a2651ba40d0247 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 24 Mar 2020 12:33:37 +0000 Subject: [PATCH] fix(cognito): user pool - link style email verification fails to deploy (#6938) A couple of changes - - `emailVerificationMessage` and `emailVerificationSubject` properties are not set when link-style verification is used. This is root cause for the error message reported by the user. - The defaults `emailBody` and `emailSubject` are modified when link-style verification is used. They will now correctly contain the template placeholder `{##Verify Email##}`. - Validations are now added that email and SMS verification messages have the mandatory placeholders. fixes #6811 --- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 76 ++++++++++++++----- .../aws-cognito/test/user-pool.test.ts | 56 +++++++++++++- 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 3bc593733c634..64c8e9259122f 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -40,6 +40,10 @@ export interface SignInAliases { export interface AutoVerifiedAttrs { /** * Whether the email address of the user should be auto verified at sign up. + * + * Note: If both `email` and `phone` is set, Cognito only verifies the phone number. To also verify email, see here - + * https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-email-phone-verification.html + * * @default - true, if email is turned on for `signIn`. false, otherwise. */ readonly email?: boolean; @@ -144,7 +148,9 @@ export interface UserVerificationConfig { * The email body template for the verification email sent to the user upon sign up. * See https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html to * learn more about message templates. - * @default 'Hello {username}, Your verification code is {####}' + * + * @default - 'Hello {username}, Your verification code is {####}' if VerificationEmailStyle.CODE is chosen, + * 'Hello {username}, Verify your account by clicking on {##Verify Email##}' if VerificationEmailStyle.LINK is chosen. */ readonly emailBody?: string; @@ -159,7 +165,9 @@ export interface UserVerificationConfig { * The message template for the verification SMS sent to the user upon sign up. * See https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html to * learn more about message templates. - * @default 'The verification code to your new account is {####}' + * + * @default - 'The verification code to your new account is {####}' if VerificationEmailStyle.CODE is chosen, + * not configured if VerificationEmailStyle.LINK is chosen */ readonly smsMessage?: string; } @@ -486,24 +494,14 @@ export class UserPool extends Resource implements IUserPool { } } - const emailVerificationSubject = props.userVerification?.emailSubject ?? 'Verify your new account'; - const emailVerificationMessage = props.userVerification?.emailBody ?? 'Hello {username}, Your verification code is {####}'; - const smsVerificationMessage = props.userVerification?.smsMessage ?? 'The verification code to your new account is {####}'; - - const defaultEmailOption = props.userVerification?.emailStyle ?? VerificationEmailStyle.CODE; - const verificationMessageTemplate: CfnUserPool.VerificationMessageTemplateProperty = - (defaultEmailOption === VerificationEmailStyle.CODE) ? { - defaultEmailOption, - emailMessage: emailVerificationMessage, - emailSubject: emailVerificationSubject, - smsMessage: smsVerificationMessage, - } : { - defaultEmailOption, - emailMessageByLink: emailVerificationMessage, - emailSubjectByLink: emailVerificationSubject, - smsMessage: smsVerificationMessage - }; - + const verificationMessageTemplate = this.verificationMessageConfiguration(props); + let emailVerificationMessage; + let emailVerificationSubject; + if (verificationMessageTemplate.defaultEmailOption === VerificationEmailStyle.CODE) { + emailVerificationMessage = verificationMessageTemplate.emailMessage; + emailVerificationSubject = verificationMessageTemplate.emailSubject; + } + const smsVerificationMessage = verificationMessageTemplate.smsMessage; const inviteMessageTemplate: CfnUserPool.InviteMessageTemplateProperty = { emailMessage: props.userInvitation?.emailBody, emailSubject: props.userInvitation?.emailSubject, @@ -664,6 +662,44 @@ export class UserPool extends Resource implements IUserPool { }); } + private verificationMessageConfiguration(props: UserPoolProps): CfnUserPool.VerificationMessageTemplateProperty { + const USERNAME_TEMPLATE = '{username}'; + const CODE_TEMPLATE = '{####}'; + const VERIFY_EMAIL_TEMPLATE = '{##Verify Email##}'; + + const emailStyle = props.userVerification?.emailStyle ?? VerificationEmailStyle.CODE; + const emailSubject = props.userVerification?.emailSubject ?? 'Verify your new account'; + const smsMessage = props.userVerification?.smsMessage ?? `The verification code to your new account is ${CODE_TEMPLATE}`; + + if (emailStyle === VerificationEmailStyle.CODE) { + const emailMessage = props.userVerification?.emailBody ?? `Hello ${USERNAME_TEMPLATE}, Your verification code is ${CODE_TEMPLATE}`; + if (emailMessage.indexOf(CODE_TEMPLATE) < 0) { + throw new Error(`Verification email body must contain the template string '${CODE_TEMPLATE}'`); + } + if (smsMessage.indexOf(CODE_TEMPLATE) < 0) { + throw new Error(`SMS message must contain the template string '${CODE_TEMPLATE}'`); + } + return { + defaultEmailOption: VerificationEmailStyle.CODE, + emailMessage, + emailSubject, + smsMessage, + }; + } else { + const emailMessage = props.userVerification?.emailBody ?? + `Hello ${USERNAME_TEMPLATE}, Verify your account by clicking on ${VERIFY_EMAIL_TEMPLATE}`; + if (emailMessage.indexOf(VERIFY_EMAIL_TEMPLATE) < 0) { + throw new Error(`Verification email body must contain the template string '${VERIFY_EMAIL_TEMPLATE}'`); + } + return { + defaultEmailOption: VerificationEmailStyle.LINK, + emailMessageByLink: emailMessage, + emailSubjectByLink: emailSubject, + smsMessage, + }; + } + } + private signInConfiguration(props: UserPoolProps) { let aliasAttrs: string[] | undefined; let usernameAttrs: string[] | undefined; diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index 9576295290a72..4267d13c37619 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -96,22 +96,70 @@ describe('User Pool', () => { // WHEN new UserPool(stack, 'Pool', { userVerification: { - emailStyle: VerificationEmailStyle.LINK + emailStyle: VerificationEmailStyle.LINK, } }); // THEN expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { - EmailVerificationMessage: 'Hello {username}, Your verification code is {####}', - EmailVerificationSubject: 'Verify your new account', + EmailVerificationMessage: ABSENT, + EmailVerificationSubject: ABSENT, + SmsVerificationMessage: 'The verification code to your new account is {####}', VerificationMessageTemplate: { DefaultEmailOption: 'CONFIRM_WITH_LINK', - EmailMessageByLink: 'Hello {username}, Your verification code is {####}', + EmailMessageByLink: 'Hello {username}, Verify your account by clicking on {##Verify Email##}', EmailSubjectByLink: 'Verify your new account', + SmsMessage: 'The verification code to your new account is {####}', } }); }), + test('email and sms verification messages are validated', () => { + const stack = new Stack(); + + expect(() => new UserPool(stack, 'Pool1', { + userVerification: { + emailStyle: VerificationEmailStyle.CODE, + emailBody: 'invalid email body', + } + })).toThrow(/Verification email body/); + + expect(() => new UserPool(stack, 'Pool2', { + userVerification: { + emailStyle: VerificationEmailStyle.CODE, + emailBody: 'valid email body {####}', + } + })).not.toThrow(); + + expect(() => new UserPool(stack, 'Pool3', { + userVerification: { + emailStyle: VerificationEmailStyle.CODE, + smsMessage: 'invalid sms message', + } + })).toThrow(/SMS message/); + + expect(() => new UserPool(stack, 'Pool4', { + userVerification: { + emailStyle: VerificationEmailStyle.CODE, + smsMessage: 'invalid sms message {####}', + } + })).not.toThrow(); + + expect(() => new UserPool(stack, 'Pool5', { + userVerification: { + emailStyle: VerificationEmailStyle.LINK, + emailBody: 'invalid email body {####}', + } + })).toThrow(/Verification email body/); + + expect(() => new UserPool(stack, 'Pool6', { + userVerification: { + emailStyle: VerificationEmailStyle.LINK, + emailBody: 'invalid email body {##Verify Email##}', + } + })).not.toThrow(); + }); + test('user invitation messages are configured correctly', () => { // GIVEN const stack = new Stack();