diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 28e37670b05a2..9f598761ee843 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -527,6 +527,13 @@ export interface UserPoolProps { */ readonly mfa?: Mfa; + /** + * The SMS message template sent during MFA verification. + * Use '{####}' in the template where Cognito should insert the verification code. + * @default 'Your authentication code is {####}.' + */ + readonly mfaMessage?: string; + /** * Configure the MFA types that users can use in this user pool. Ignored if `mfa` is set to `OFF`. * @@ -761,6 +768,7 @@ export class UserPool extends UserPoolBase { aliasAttributes: signIn.aliasAttrs, autoVerifiedAttributes: signIn.autoVerifyAttrs, lambdaConfig: Lazy.any({ produce: () => undefinedIfNoKeys(this.triggers) }), + smsAuthenticationMessage: this.mfaMessage(props), smsConfiguration: this.smsConfiguration(props), adminCreateUserConfig, emailVerificationMessage, @@ -810,6 +818,24 @@ export class UserPool extends UserPoolBase { }); } + private mfaMessage(props: UserPoolProps): string | undefined { + const CODE_TEMPLATE = '{####}'; + const MAX_LENGTH = 140; + const message = props.mfaMessage; + + if (message && !Token.isUnresolved(message)) { + if (!message.includes(CODE_TEMPLATE)) { + throw new Error(`MFA message must contain the template string '${CODE_TEMPLATE}'`); + } + + if (message.length > MAX_LENGTH) { + throw new Error(`MFA message must be between ${CODE_TEMPLATE.length} and ${MAX_LENGTH} characters`); + } + } + + return message; + } + private verificationMessageConfiguration(props: UserPoolProps): CfnUserPool.VerificationMessageTemplateProperty { const CODE_TEMPLATE = '{####}'; const VERIFY_EMAIL_TEMPLATE = '{##Verify Email##}'; 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 7f1c02c786a4e..ccd5959750a58 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -29,6 +29,7 @@ describe('User Pool', () => { EmailSubject: 'Verify your new account', SmsMessage: 'The verification code to your new account is {####}', }, + SmsAuthenticationMessage: ABSENT, SmsConfiguration: ABSENT, lambdaTriggers: ABSENT, }); @@ -80,6 +81,49 @@ describe('User Pool', () => { }); }), + test('mfa authentication message is configured correctly', () => { + // GIVEN + const stack = new Stack(); + const message = 'The authentication code to your account is {####}'; + + // WHEN + new UserPool(stack, 'Pool', { + mfaMessage: message, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + SmsAuthenticationMessage: message, + }); + }), + + test('mfa authentication message is validated', () => { + const stack = new Stack(); + + expect(() => new UserPool(stack, 'Pool1', { + mfaMessage: '{####', + })).toThrow(/MFA message must contain the template string/); + + expect(() => new UserPool(stack, 'Pool2', { + mfaMessage: '{####}', + })).not.toThrow(); + + expect(() => new UserPool(stack, 'Pool3', { + mfaMessage: `{####}${'x'.repeat(135)}`, + })).toThrow(/MFA message must be between 6 and 140 characters/); + + expect(() => new UserPool(stack, 'Pool4', { + mfaMessage: `{####}${'x'.repeat(134)}`, + })).not.toThrow(); + + // Validation is skipped for tokens. + const parameter = new CfnParameter(stack, 'Parameter'); + + expect(() => new UserPool(stack, 'Pool5', { + mfaMessage: parameter.valueAsString, + })).not.toThrow(); + }); + test('email and sms verification messages are validated', () => { const stack = new Stack(); @@ -1355,4 +1399,4 @@ function fooFunction(scope: Construct, name: string): lambda.IFunction { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', }); -} \ No newline at end of file +}