From c7f15f255ede6411f4afb68f5b9f1d54abe47df3 Mon Sep 17 00:00:00 2001 From: Duarte Nunes Date: Tue, 12 May 2020 14:56:18 -0300 Subject: [PATCH] feat(cognito): user pool client - prevent user existence errors Adds a toggle to the Cognito L2 constructs to suppress user existence errors. It is enabled by default as per [the documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-managing-errors.html). Fixes #7406 Signed-off-by: Duarte Nunes --- packages/@aws-cdk/aws-cognito/README.md | 16 +++++- .../aws-cognito/lib/user-pool-client.ts | 17 ++++++ ...r-pool-client-explicit-props.expected.json | 5 +- .../integ.user-pool-client-explicit-props.ts | 3 +- .../aws-cognito/test/user-pool-client.test.ts | 53 +++++++++++++++++++ 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 4c9a5c60ea77f..9886fdbbd2442 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -400,6 +400,20 @@ pool.addClient('app-client', { }); ``` +An app client can be configured to prevent user existence errors. This +instructs the Cognito authentication API to return generic authentication +failure responses instead of an UserNotFoundException. By default, the flag +is not set, which means different things for existing and new stacks. See the +[documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-managing-errors.html) +for the full details on the behavior of this flag. + +```ts +const pool = new UserPool(this, 'Pool'); +pool.addClient('app-client', { + preventUserExistenceErrors: true, +}); +``` + ### Domains After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be @@ -429,4 +443,4 @@ pool.addDomain('CustomDomain', { Read more about [Using the Amazon Cognito Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain-prefix.html) and [Using Your Own -Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html). \ No newline at end of file +Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html). diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index cde6bf72191ca..039c17376b8fe 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -173,6 +173,15 @@ export interface UserPoolClientOptions { * @default - see defaults in `OAuthSettings` */ readonly oAuth?: OAuthSettings; + + /** + * Whether Cognito returns a UserNotFoundException exception when the + * user does not exist in the user pool (false), or whether it returns + * another type of error that doesn't reveal the user's absence. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-managing-errors.html + * @default true for new stacks + */ + readonly preventUserExistenceErrors?: boolean; } /** @@ -234,6 +243,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { allowedOAuthScopes: this.configureOAuthScopes(props.oAuth), callbackUrLs: (props.oAuth?.callbackUrls && props.oAuth?.callbackUrls.length > 0) ? props.oAuth?.callbackUrls : undefined, allowedOAuthFlowsUserPoolClient: props.oAuth ? true : undefined, + preventUserExistenceErrors: this.configurePreventUserExistenceErrors(props.preventUserExistenceErrors), }); this.userPoolClientId = resource.ref; @@ -297,4 +307,11 @@ export class UserPoolClient extends Resource implements IUserPoolClient { } return undefined; } + + private configurePreventUserExistenceErrors(prevent?: boolean): string | undefined { + if (prevent === undefined) { + return undefined; + } + return prevent ? 'ENABLED' : 'LEGACY'; + } } diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json index 28a710bd40bba..63556451e98ff 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json @@ -93,8 +93,9 @@ "ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH" ], - "GenerateSecret": true + "GenerateSecret": true, + "PreventUserExistenceErrors": "ENABLED" } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts index 4870ab2276738..92a8bd8f19321 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts @@ -31,4 +31,5 @@ userpool.addClient('myuserpoolclient', { ], callbackUrls: [ 'https://redirect-here.myapp.com' ], }, -}); \ No newline at end of file + preventUserExistenceErrors: true, +}); diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index d309f379611a2..d1e0862df0a50 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -283,4 +283,57 @@ describe('User Pool Client', () => { AllowedOAuthScopes: [ 'aws.cognito.signin.user.admin' ], }); }); + + test('enable user existence errors prevention', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolClient(stack, 'Client', { + userPool: pool, + preventUserExistenceErrors: true, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + UserPoolId: stack.resolve(pool.userPoolId), + PreventUserExistenceErrors: 'ENABLED', + }); + }); + + test('disable user existence errors prevention', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolClient(stack, 'Client', { + userPool: pool, + preventUserExistenceErrors: false, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + UserPoolId: stack.resolve(pool.userPoolId), + PreventUserExistenceErrors: 'LEGACY', + }); + }); + + test('user existence errors prevention is absent by default', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolClient(stack, 'Client', { + userPool: pool, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + UserPoolId: stack.resolve(pool.userPoolId), + PreventUserExistenceErrors: ABSENT, + }); + }); }); \ No newline at end of file