diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 843e6873994aa..af580d9ad6394 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -86,6 +86,56 @@ All email subjects, bodies and SMS messages for both invitation and verification Learn more about [message templates here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html). +### Sign In + +Users registering or signing in into your application can do so with multiple identifiers. There are 4 options +available: + +* `username`: Allow signing in using the one time immutable user name that the user chose at the time of sign up. +* `email`: Allow signing in using the email address that is associated with the account. +* `phone`: Allow signing in using the phone number that is associated with the account. +* `preferredUsername`: Allow signing in with an alternate user name that the user can change at any time. However, this + is not available if the `username` option is not chosen. + +The following code sets up a user pool so that the user can sign in with either their username or their email address - + +```ts +new UserPool(this, 'myuserpool', { + // ... + // ... + signInAliases: { + username: true, + email: true + }, +}); +``` + +User pools can either be configured so that user name is primary sign in form, but also allows for the other three to be +used additionally; or it can be configured so that email and/or phone numbers are the only ways a user can register and +sign in. Read more about this +[here](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases-settings). + +To match with 'Option 1' in the above link, with a verified email, `signInAliases` should be set to +`{ username: true, email: true }`. To match with 'Option 2' in the above link with both a verified +email and phone number, this property should be set to `{ email: true, phone: true }`. + +Cognito recommends that email and phone number be automatically verified, if they are one of the sign in methods for +the user pool. Read more about that +[here](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases). +The CDK does this by default, when email and/or phone number are specified as part of `signInAliases`. This can be +overridden by specifying the `autoVerify` property. + +The following code snippet sets up only email as a sign in alias, but both email and phone number to be auto-verified. + +```ts +new UserPool(this, 'myuserpool', { + // ... + // ... + signInAliases: { username: true, email: true }, + autoVerify: { email: true, phone: true } +}); +``` + ### Security Cognito sends various messages to its users via SMS, for different actions, ranging from account verification to @@ -108,4 +158,24 @@ new UserPool(this, 'myuserpool', { When the `smsRole` property is specified, the `smsRoleExternalId` may also be specified. The value of `smsRoleExternalId` will be used as the `sts:ExternalId` when the Cognito service assumes the role. In turn, the role's assume role policy should be configured to accept this value as the ExternalId. Learn more about [ExternalId -here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). \ No newline at end of file +here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). + +### Importing User Pools + +Any user pool that has been created outside of this stack, can be imported into the CDK app. Importing a user pool +allows for it to be used in other parts of the CDK app that reference an `IUserPool`. However, imported user pools have +limited configurability. As a rule of thumb, none of the properties that is are part of the +[`AWS::Cognito::UserPool`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html) +CloudFormation resource can be configured. + +User pools can be imported either using their id via the `UserPool.fromUserPoolId()`, or by using their ARN, via the +`UserPool.fromUserPoolArn()` API. + +```ts +const stack = new Stack(app, 'my-stack'); + +const awesomePool = UserPool.fromUserPoolId(stack, 'awesome-user-pool', 'us-east-1_oiuR12Abd'); + +const otherAwesomePool = UserPool.fromUserPoolArn(stack, 'other-awesome-user-pool', + 'arn:aws:cognito-idp:eu-west-1:123456789012:userpool/us-east-1_mtRyYQ14D'); +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index c61e3da8e9152..43a41541ac136 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -1,6 +1,6 @@ import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core'; +import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { CfnUserPool } from './cognito.generated'; /** @@ -121,28 +121,50 @@ export enum UserPoolAttribute { } /** - * Methods of user sign-in + * The different ways in which users of this pool can sign up or sign in. */ -export enum SignInType { +export interface SignInAliases { /** - * End-user will sign in with a username, with optional aliases + * Whether user is allowed to sign up or sign in with a username + * @default true */ - USERNAME, + readonly username?: boolean; /** - * End-user will sign in using an email address + * Whether a user is allowed to sign up or sign in with an email address + * @default false */ - EMAIL, + readonly email?: boolean; /** - * End-user will sign in using a phone number + * Whether a user is allowed to sign up or sign in with a phone number + * @default false */ - PHONE, + readonly phone?: boolean; /** - * End-user will sign in using either an email address or phone number + * Whether a user is allowed to ign in with a secondary username, that can be set and modified after sign up. + * Can only be used in conjunction with `USERNAME`. + * @default false */ - EMAIL_OR_PHONE + readonly preferredUsername?: boolean; +} + +/** + * Attributes that can be automatically verified for users in a user pool. + */ +export interface AutoVerifiedAttrs { + /** + * Whether the email address of the user should be auto verified at sign up. + * @default - true, if email is turned on for `signIn`. false, otherwise. + */ + readonly email?: boolean; + + /** + * Whether the phone number of the user should be auto verified at sign up. + * @default - true, if phone is turned on for `signIn`. false, otherwise. + */ + readonly phone?: boolean; } export interface UserPoolTriggers { @@ -327,28 +349,28 @@ export interface UserPoolProps { readonly smsRoleExternalId?: string; /** - * Method used for user registration & sign in. + * Methods in which a user registers or signs in to a user pool. * Allows either username with aliases OR sign in with email, phone, or both. * - * @default SignInType.Username - */ - readonly signInType?: SignInType; - - /** - * Attributes to allow as username alias. - * Only valid if signInType is USERNAME + * Read the sections on usernames and aliases to learn more - + * https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html * - * @default - No alias. + * To match with 'Option 1' in the above link, with a verified email, this property should be set to + * `{ username: true, email: true }`. To match with 'Option 2' in the above link with both a verified email and phone + * number, this property should be set to `{ email: true, phone: true }`. + * + * @default { username: true } */ - readonly usernameAliasAttributes?: UserPoolAttribute[]; + readonly signInAliases?: SignInAliases; /** - * Attributes which Cognito will automatically send a verification message to. - * Must be either EMAIL, PHONE, or both. + * Attributes which Cognito will look to verify automatically upon user sign up. + * EMAIL and PHONE are the only available options. * - * @default - No auto verification. + * @default - If `signIn` include email and/or phone, they will be included in `autoVerifiedAttributes` by default. + * If absent, no attributes will be auto-verified. */ - readonly autoVerifiedAttributes?: UserPoolAttribute[]; + readonly autoVerify?: AutoVerifiedAttrs; /** * Lambda functions to use for supported Cognito triggers. @@ -358,28 +380,9 @@ export interface UserPoolProps { readonly lambdaTriggers?: UserPoolTriggers; } -export interface UserPoolAttributes { - /** - * The ID of an existing user pool - */ - readonly userPoolId: string; - - /** - * The ARN of the imported user pool - */ - readonly userPoolArn: string; - - /** - * The provider name of the imported user pool - */ - readonly userPoolProviderName: string; - - /** - * The URL of the imported user pool - */ - readonly userPoolProviderUrl: string; -} - +/** + * Represents a Cognito UserPool + */ export interface IUserPool extends IResource { /** * The physical ID of this user pool resource @@ -392,18 +395,6 @@ export interface IUserPool extends IResource { * @attribute */ readonly userPoolArn: string; - - /** - * The provider name of this user pool resource - * @attribute - */ - readonly userPoolProviderName: string; - - /** - * The provider URL of this user pool resource - * @attribute - */ - readonly userPoolProviderUrl: string; } /** @@ -411,22 +402,28 @@ export interface IUserPool extends IResource { */ export class UserPool extends Resource implements IUserPool { /** - * Import an existing user pool resource - * @param scope Parent construct - * @param id Construct ID - * @param attrs Imported user pool properties + * Import an existing user pool based on its id. */ - public static fromUserPoolAttributes(scope: Construct, id: string, attrs: UserPoolAttributes): IUserPool { - /** - * Define a user pool which has been declared in another stack - */ + public static fromUserPoolId(scope: Construct, id: string, userPoolId: string): IUserPool { class Import extends Resource implements IUserPool { - public readonly userPoolId = attrs.userPoolId; - public readonly userPoolArn = attrs.userPoolArn; - public readonly userPoolProviderName = attrs.userPoolProviderName; - public readonly userPoolProviderUrl = attrs.userPoolProviderUrl; + public readonly userPoolId = userPoolId; + public readonly userPoolArn = Stack.of(this).formatArn({ + service: 'cognito-idp', + resource: 'userpool', + resourceName: userPoolId, + }); } + return new Import(scope, id); + } + /** + * Import an existing user pool based on its ARN. + */ + public static fromUserPoolArn(scope: Construct, id: string, userPoolArn: string): IUserPool { + class Import extends Resource implements IUserPool { + public readonly userPoolArn = userPoolArn; + public readonly userPoolId = Stack.of(this).parseArn(userPoolArn).resourceName!; + } return new Import(scope, id); } @@ -442,11 +439,13 @@ export class UserPool extends Resource implements IUserPool { /** * User pool provider name + * @attribute */ public readonly userPoolProviderName: string; /** * User pool provider URL + * @attribute */ public readonly userPoolProviderUrl: string; @@ -455,46 +454,7 @@ export class UserPool extends Resource implements IUserPool { constructor(scope: Construct, id: string, props: UserPoolProps = {}) { super(scope, id); - let aliasAttributes: UserPoolAttribute[] | undefined; - let usernameAttributes: UserPoolAttribute[] | undefined; - - if (props.usernameAliasAttributes != null && props.signInType !== SignInType.USERNAME) { - throw new Error(`'usernameAliasAttributes' can only be set with a signInType of 'USERNAME'`); - } - - if (props.usernameAliasAttributes - && !props.usernameAliasAttributes.every(a => { - return a === UserPoolAttribute.EMAIL || a === UserPoolAttribute.PHONE_NUMBER || a === UserPoolAttribute.PREFERRED_USERNAME; - })) { - throw new Error(`'usernameAliasAttributes' can only include EMAIL, PHONE_NUMBER, or PREFERRED_USERNAME`); - } - - if (props.autoVerifiedAttributes - && !props.autoVerifiedAttributes.every(a => a === UserPoolAttribute.EMAIL || a === UserPoolAttribute.PHONE_NUMBER)) { - throw new Error(`'autoVerifiedAttributes' can only include EMAIL or PHONE_NUMBER`); - } - - switch (props.signInType) { - case SignInType.USERNAME: - aliasAttributes = props.usernameAliasAttributes; - break; - - case SignInType.EMAIL: - usernameAttributes = [UserPoolAttribute.EMAIL]; - break; - - case SignInType.PHONE: - usernameAttributes = [UserPoolAttribute.PHONE_NUMBER]; - break; - - case SignInType.EMAIL_OR_PHONE: - usernameAttributes = [UserPoolAttribute.EMAIL, UserPoolAttribute.PHONE_NUMBER]; - break; - - default: - aliasAttributes = props.usernameAliasAttributes; - break; - } + const signIn = this.signInConfiguration(props); if (props.lambdaTriggers) { for (const t of Object.keys(props.lambdaTriggers)) { @@ -537,9 +497,9 @@ export class UserPool extends Resource implements IUserPool { const userPool = new CfnUserPool(this, 'Resource', { userPoolName: props.userPoolName, - usernameAttributes, - aliasAttributes, - autoVerifiedAttributes: props.autoVerifiedAttributes, + usernameAttributes: signIn.usernameAttrs, + aliasAttributes: signIn.aliasAttrs, + autoVerifiedAttributes: signIn.autoVerifyAttrs, lambdaConfig: Lazy.anyValue({ produce: () => this.triggers }), smsConfiguration: this.smsConfiguration(props), adminCreateUserConfig, @@ -674,6 +634,42 @@ export class UserPool extends Resource implements IUserPool { }); } + private signInConfiguration(props: UserPoolProps) { + let aliasAttrs: string[] | undefined; + let usernameAttrs: string[] | undefined; + let autoVerifyAttrs: string[] | undefined; + + const signIn: SignInAliases = props.signInAliases ?? { username: true }; + + if (signIn.preferredUsername && !signIn.username) { + throw new Error('username signIn must be enabled if preferredUsername is enabled'); + } + + if (signIn.username) { + aliasAttrs = []; + if (signIn.email) { aliasAttrs.push(UserPoolAttribute.EMAIL); } + if (signIn.phone) { aliasAttrs.push(UserPoolAttribute.PHONE_NUMBER); } + if (signIn.preferredUsername) { aliasAttrs.push(UserPoolAttribute.PREFERRED_USERNAME); } + if (aliasAttrs.length === 0) { aliasAttrs = undefined; } + } else { + usernameAttrs = []; + if (signIn.email) { usernameAttrs.push(UserPoolAttribute.EMAIL); } + if (signIn.phone) { usernameAttrs.push(UserPoolAttribute.PHONE_NUMBER); } + } + + if (props.autoVerify) { + autoVerifyAttrs = []; + if (props.autoVerify.email) { autoVerifyAttrs.push(UserPoolAttribute.EMAIL); } + if (props.autoVerify.phone) { autoVerifyAttrs.push(UserPoolAttribute.PHONE_NUMBER); } + } else if (signIn.email || signIn.phone) { + autoVerifyAttrs = []; + if (signIn.email) { autoVerifyAttrs.push(UserPoolAttribute.EMAIL); } + if (signIn.phone) { autoVerifyAttrs.push(UserPoolAttribute.PHONE_NUMBER); } + } + + return { usernameAttrs, aliasAttrs, autoVerifyAttrs }; + } + private smsConfiguration(props: UserPoolProps): CfnUserPool.SmsConfigurationProperty { if (props.smsRole) { return { @@ -710,4 +706,4 @@ export class UserPool extends Resource implements IUserPool { }; } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 92950f5fc475e..94ed38f05530f 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -88,9 +88,7 @@ }, "awslint": { "exclude": [ - "from-method:@aws-cdk/aws-cognito.UserPool", - "from-arn:UserPool.fromUserPoolArn", - "docs-public-apis:@aws-cdk/aws-cognito.IUserPool", + "no-unused-type:@aws-cdk/aws-cognito.UserPoolAttribute", "props-default-doc:@aws-cdk/aws-cognito.UserPoolTriggers.verifyAuthChallengeResponse", "props-default-doc:@aws-cdk/aws-cognito.UserPoolTriggers.userMigration", "props-default-doc:@aws-cdk/aws-cognito.UserPoolTriggers.preTokenGeneration", @@ -105,7 +103,6 @@ "docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret", "docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientId", "docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientName", - "docs-public-apis:@aws-cdk/aws-cognito.UserPoolAttributes", "docs-public-apis:@aws-cdk/aws-cognito.UserPoolClientProps" ] }, diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json new file mode 100644 index 0000000000000..c5fefd1e26bec --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json @@ -0,0 +1,81 @@ +{ + "Resources": { + "myuserpoolsmsRole0E16FDD9": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "integuserpoolmyuserpoolDA38443C" + } + }, + "Effect": "Allow", + "Principal": { + "Service": "cognito-idp.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "sns-publish" + } + ] + } + }, + "myuserpool01998219": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": false, + "InviteMessageTemplate": { + "EmailMessage": "invitation email body from the integ test", + "EmailSubject": "invitation email subject from the integ test", + "SMSMessage": "invitation sms message from the integ test" + } + }, + "AliasAttributes": [ + "email" + ], + "AutoVerifiedAttributes": [ + "email", + "phone_number" + ], + "EmailVerificationMessage": "verification email body from the integ test", + "EmailVerificationSubject": "verification email subject from the integ test", + "LambdaConfig": {}, + "SmsConfiguration": { + "ExternalId": "integuserpoolmyuserpoolDA38443C", + "SnsCallerArn": { + "Fn::GetAtt": [ + "myuserpoolsmsRole0E16FDD9", + "Arn" + ] + } + }, + "SmsVerificationMessage": "verification sms message from the integ test", + "UserPoolName": "MyUserPool", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "verification email body from the integ test", + "EmailSubject": "verification email subject from the integ test", + "SmsMessage": "verification sms message from the integ test" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts new file mode 100644 index 0000000000000..5c7a9c23c80b4 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts @@ -0,0 +1,28 @@ +import { App, Stack } from '@aws-cdk/core'; +import { UserPool } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-user-pool'); + +new UserPool(stack, 'myuserpool', { + userPoolName: 'MyUserPool', + userInvitation: { + emailSubject: 'invitation email subject from the integ test', + emailBody: 'invitation email body from the integ test', + smsMessage: 'invitation sms message from the integ test' + }, + selfSignUpEnabled: true, + userVerification: { + emailBody: 'verification email body from the integ test', + emailSubject: 'verification email subject from the integ test', + smsMessage: 'verification sms message from the integ test' + }, + signInAliases: { + username: true, + email: true, + }, + autoVerify: { + email: true, + phone: true, + }, +}); \ No newline at end of file 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 32436ffe9ec8b..16cfc7b9b051b 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -3,7 +3,7 @@ import { ABSENT } from '@aws-cdk/assert/lib/assertions/have-resource'; import { Role } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Stack, Tag } from '@aws-cdk/core'; -import { SignInType, UserPool, UserPoolAttribute, VerificationEmailStyle } from '../lib'; +import { UserPool, VerificationEmailStyle } from '../lib'; describe('User Pool', () => { test('default setup', () => { @@ -36,7 +36,7 @@ describe('User Pool', () => { } }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -51,7 +51,8 @@ describe('User Pool', () => { Service: 'cognito-idp.amazonaws.com' } } - ] + ], + Version: '2012-10-17' }, Policies: [ { @@ -62,8 +63,10 @@ describe('User Pool', () => { Effect: 'Allow', Resource: '*' } - ] - } + ], + Version: '2012-10-17' + }, + PolicyName: 'sns-publish' } ] }); @@ -154,6 +157,30 @@ describe('User Pool', () => { }); }); + test('import using id', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { region: 'some-region-1', account: '0123456789012' } + }); + const userPoolId = 'test-user-pool'; + + // WHEN + const pool = UserPool.fromUserPoolId(stack, 'userpool', userPoolId); + expect(pool.userPoolId).toEqual(userPoolId); + expect(pool.userPoolArn).toMatch(/cognito-idp:some-region-1:0123456789012:userpool\/test-user-pool/); + }); + + test('import using arn', () => { + // GIVEN + const stack = new Stack(); + const userPoolArn = 'arn:aws:cognito-idp:us-east-1:0123456789012:userpool/test-user-pool'; + + // WHEN + const pool = UserPool.fromUserPoolArn(stack, 'userpool', userPoolArn); + expect(pool.userPoolId).toEqual('test-user-pool'); + expect(pool.userPoolArn).toEqual(userPoolArn); + }); + test('support tags', () => { // GIVEN const stack = new Stack(); @@ -316,53 +343,97 @@ describe('User Pool', () => { }); }); - test('set sign in type', () => { + test('no username aliases specified', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool'); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + UsernameAttributes: ABSENT, + AliasAttributes: ABSENT, + }); + }); + + test('fails when preferredUsername is used without username', () => { + const stack = new Stack(); + expect(() => new UserPool(stack, 'Pool', { + signInAliases: { preferredUsername: true } + })).toThrow(/username/); + }); + + test('username and email are specified as the username aliases', () => { // GIVEN const stack = new Stack(); // WHEN new UserPool(stack, 'Pool', { - signInType: SignInType.EMAIL, - autoVerifiedAttributes: [ UserPoolAttribute.EMAIL ] + signInAliases: { username: true, email: true } }); // THEN expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { - UsernameAttributes: [ 'email' ], - AutoVerifiedAttributes: [ 'email' ] + UsernameAttributes: ABSENT, + AliasAttributes: [ 'email' ], }); }); - test('usernameAliasAttributes require signInType of USERNAME', () => { + test('email and phone number are specified as the username aliases', () => { + // GIVEN const stack = new Stack(); - expect(() => { - new UserPool(stack, 'Pool', { - signInType: SignInType.EMAIL, - usernameAliasAttributes: [ UserPoolAttribute.PREFERRED_USERNAME ] - }); - }).toThrow(/'usernameAliasAttributes' can only be set with a signInType of 'USERNAME'/); + // WHEN + new UserPool(stack, 'Pool', { + signInAliases: { email: true, phone: true } + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + UsernameAttributes: [ 'email', 'phone_number' ], + AliasAttributes: ABSENT, + }); }); - test('usernameAliasAttributes must be one or more of EMAIL, PHONE_NUMBER, or PREFERRED_USERNAME', () => { + test('email and phone number are autoverified, by default, if they are specified as signIn', () => { + // GIVEN const stack = new Stack(); - expect(() => { - new UserPool(stack, 'Pool', { - signInType: SignInType.USERNAME, - usernameAliasAttributes: [ UserPoolAttribute.GIVEN_NAME ] - }); - }).toThrow(/'usernameAliasAttributes' can only include EMAIL, PHONE_NUMBER, or PREFERRED_USERNAME/); + // WHEN + new UserPool(stack, 'Pool1', { + userPoolName: 'Pool1', + signInAliases: { email: true } + }); + new UserPool(stack, 'Pool2', { + userPoolName: 'Pool2', + signInAliases: { email: true, phone: true } + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + UserPoolName: 'Pool1', + AutoVerifiedAttributes: [ 'email' ], + }); + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + UserPoolName: 'Pool2', + AutoVerifiedAttributes: [ 'email', 'phone_number' ], + }); }); - test('autoVerifiedAttributes must be one or more of EMAIL or PHONE_NUMBER', () => { + test('explicit autoverify are correctly picked up', () => { + // GIVEN const stack = new Stack(); - expect(() => { - new UserPool(stack, 'Pool', { - signInType: SignInType.EMAIL, - autoVerifiedAttributes: [ UserPoolAttribute.EMAIL, UserPoolAttribute.GENDER ] - }); - }).toThrow(/'autoVerifiedAttributes' can only include EMAIL or PHONE_NUMBER/); + // WHEN + new UserPool(stack, 'Pool', { + signInAliases: { username: true }, + autoVerify: { email: true, phone: true }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + AutoVerifiedAttributes: [ 'email', 'phone_number' ], + }); }); }); \ No newline at end of file