Skip to content

Commit ecbe1bf

Browse files
authored
feat(cognito): throw ValidationError instead of untyped errors (#33170)
### Issue # (if applicable) `aws-cognito` for #32569 ### Description of changes ValidationErrors everywhere ### Describe any new or updated permissions being added n/a ### Description of how you validated changes Existing tests. Exemptions granted as this is basically a refactor of existing code. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 988043e commit ecbe1bf

File tree

11 files changed

+56
-48
lines changed

11 files changed

+56
-48
lines changed

packages/aws-cdk-lib/.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [
1717
// no-throw-default-error
1818
const enableNoThrowDefaultErrorIn = [
1919
'aws-amplify',
20-
'aws-amplifyuibuilder',
20+
'aws-amplifyuibuilder',
2121
'aws-apigatewayv2-authorizers',
2222
'aws-apigatewayv2-integrations',
23+
'aws-cognito',
2324
'aws-elasticloadbalancing',
2425
'aws-elasticloadbalancingv2',
2526
'aws-elasticloadbalancingv2-actions',

packages/aws-cdk-lib/aws-cognito/lib/user-pool-attr.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { StandardAttributeNames } from './private/attr-names';
22
import { Token } from '../../core';
3+
import { UnscopedValidationError } from '../../core/lib/errors';
34

45
/**
56
* The set of standard attributes that can be marked as required or mutable.
@@ -242,10 +243,10 @@ export class StringAttribute implements ICustomAttribute {
242243

243244
constructor(props: StringAttributeProps = {}) {
244245
if (props.minLen && !Token.isUnresolved(props.minLen) && props.minLen < 0) {
245-
throw new Error(`minLen cannot be less than 0 (value: ${props.minLen}).`);
246+
throw new UnscopedValidationError(`minLen cannot be less than 0 (value: ${props.minLen}).`);
246247
}
247248
if (props.maxLen && !Token.isUnresolved(props.maxLen) && props.maxLen > 2048) {
248-
throw new Error(`maxLen cannot be greater than 2048 (value: ${props.maxLen}).`);
249+
throw new UnscopedValidationError(`maxLen cannot be greater than 2048 (value: ${props.maxLen}).`);
249250
}
250251
this.minLen = props?.minLen;
251252
this.maxLen = props?.maxLen;

packages/aws-cdk-lib/aws-cognito/lib/user-pool-client.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IUserPool } from './user-pool';
44
import { ClientAttributes } from './user-pool-attr';
55
import { IUserPoolResourceServer, ResourceServerScope } from './user-pool-resource-server';
66
import { IResource, Resource, Duration, Stack, SecretValue, Token } from '../../core';
7+
import { ValidationError } from '../../core/lib/errors';
78
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '../../custom-resources';
89

910
/**
@@ -383,7 +384,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
383384
class Import extends Resource implements IUserPoolClient {
384385
public readonly userPoolClientId = userPoolClientId;
385386
get userPoolClientSecret(): SecretValue {
386-
throw new Error('UserPool Client Secret is not available for imported Clients');
387+
throw new ValidationError('UserPool Client Secret is not available for imported Clients', this);
387388
}
388389
}
389390

@@ -414,7 +415,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
414415
super(scope, id);
415416

416417
if (props.disableOAuth && props.oAuth) {
417-
throw new Error('OAuth settings cannot be specified when disableOAuth is set.');
418+
throw new ValidationError('OAuth settings cannot be specified when disableOAuth is set.', this);
418419
}
419420

420421
this.oAuthFlows = props.oAuth?.flows ?? {
@@ -427,23 +428,23 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
427428
if (callbackUrls === undefined) {
428429
callbackUrls = ['https://example.com'];
429430
} else if (callbackUrls.length === 0) {
430-
throw new Error('callbackUrl must not be empty when codeGrant or implicitGrant OAuth flows are enabled.');
431+
throw new ValidationError('callbackUrl must not be empty when codeGrant or implicitGrant OAuth flows are enabled.', this);
431432
}
432433
}
433434

434435
if (props.oAuth?.defaultRedirectUri && !Token.isUnresolved(props.oAuth.defaultRedirectUri)) {
435436
if (callbackUrls && !callbackUrls.includes(props.oAuth.defaultRedirectUri)) {
436-
throw new Error('defaultRedirectUri must be included in callbackUrls.');
437+
throw new ValidationError('defaultRedirectUri must be included in callbackUrls.', this);
437438
}
438439

439440
const defaultRedirectUriPattern = /^(?=.{1,1024}$)[\p{L}\p{M}\p{S}\p{N}\p{P}]+$/u;
440441
if (!defaultRedirectUriPattern.test(props.oAuth.defaultRedirectUri)) {
441-
throw new Error(`defaultRedirectUri must match the \`^(?=.{1,1024}$)[\p{L}\p{M}\p{S}\p{N}\p{P}]+$\` pattern, got ${props.oAuth.defaultRedirectUri}`);
442+
throw new ValidationError(`defaultRedirectUri must match the \`^(?=.{1,1024}$)[\p{L}\p{M}\p{S}\p{N}\p{P}]+$\` pattern, got ${props.oAuth.defaultRedirectUri}`, this);
442443
}
443444
}
444445

445446
if (!props.generateSecret && props.enablePropagateAdditionalUserContextData) {
446-
throw new Error('Cannot activate enablePropagateAdditionalUserContextData in an app client without a client secret.');
447+
throw new ValidationError('Cannot activate enablePropagateAdditionalUserContextData in an app client without a client secret.', this);
447448
}
448449

449450
this._generateSecret = props.generateSecret;
@@ -480,16 +481,14 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
480481
*/
481482
public get userPoolClientName(): string {
482483
if (this._userPoolClientName === undefined) {
483-
throw new Error('userPoolClientName is available only if specified on the UserPoolClient during initialization');
484+
throw new ValidationError('userPoolClientName is available only if specified on the UserPoolClient during initialization', this);
484485
}
485486
return this._userPoolClientName;
486487
}
487488

488489
public get userPoolClientSecret(): SecretValue {
489490
if (!this._generateSecret) {
490-
throw new Error(
491-
'userPoolClientSecret is available only if generateSecret is set to true.',
492-
);
491+
throw new ValidationError('userPoolClientSecret is available only if generateSecret is set to true.', this);
493492
}
494493

495494
// Create the Custom Resource that assists in resolving the User Pool Client secret
@@ -540,7 +539,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
540539

541540
private configureOAuthFlows(): string[] | undefined {
542541
if ((this.oAuthFlows.authorizationCodeGrant || this.oAuthFlows.implicitCodeGrant) && this.oAuthFlows.clientCredentials) {
543-
throw new Error('clientCredentials OAuth flow cannot be selected along with codeGrant or implicitGrant.');
542+
throw new ValidationError('clientCredentials OAuth flow cannot be selected along with codeGrant or implicitGrant.', this);
544543
}
545544
const oAuthFlows: string[] = [];
546545
if (this.oAuthFlows.clientCredentials) { oAuthFlows.push('client_credentials'); }
@@ -614,7 +613,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
614613
private validateDuration(name: string, min: Duration, max: Duration, value?: Duration) {
615614
if (value === undefined) { return; }
616615
if (value.toMilliseconds() < min.toMilliseconds() || value.toMilliseconds() > max.toMilliseconds()) {
617-
throw new Error(`${name}: Must be a duration between ${min.toHumanString()} and ${max.toHumanString()} (inclusive); received ${value.toHumanString()}.`);
616+
throw new ValidationError(`${name}: Must be a duration between ${min.toHumanString()} and ${max.toHumanString()} (inclusive); received ${value.toHumanString()}.`, this);
618617
}
619618
}
620619
}

packages/aws-cdk-lib/aws-cognito/lib/user-pool-domain.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IUserPool } from './user-pool';
44
import { UserPoolClient } from './user-pool-client';
55
import { ICertificate } from '../../aws-certificatemanager';
66
import { IResource, Resource, Stack, Token } from '../../core';
7+
import { ValidationError } from '../../core/lib/errors';
78
import { AwsCustomResource, AwsCustomResourcePolicy, AwsSdkCall, PhysicalResourceId } from '../../custom-resources';
89

910
/**
@@ -126,14 +127,14 @@ export class UserPoolDomain extends Resource implements IUserPoolDomain {
126127
super(scope, id);
127128

128129
if (!!props.customDomain === !!props.cognitoDomain) {
129-
throw new Error('One of, and only one of, cognitoDomain or customDomain must be specified');
130+
throw new ValidationError('One of, and only one of, cognitoDomain or customDomain must be specified', this);
130131
}
131132

132133
if (props.cognitoDomain?.domainPrefix &&
133134
!Token.isUnresolved(props.cognitoDomain?.domainPrefix) &&
134135
!/^[a-z0-9-]+$/.test(props.cognitoDomain.domainPrefix)) {
135136

136-
throw new Error('domainPrefix for cognitoDomain can contain only lowercase alphabets, numbers and hyphens');
137+
throw new ValidationError('domainPrefix for cognitoDomain can contain only lowercase alphabets, numbers and hyphens', this);
137138
}
138139

139140
this.isCognitoDomain = !!props.cognitoDomain;
@@ -214,7 +215,7 @@ export class UserPoolDomain extends Resource implements IUserPoolDomain {
214215
} else if (client.oAuthFlows.implicitCodeGrant) {
215216
responseType = 'token';
216217
} else {
217-
throw new Error('signInUrl is not supported for clients without authorizationCodeGrant or implicitCodeGrant flow enabled');
218+
throw new ValidationError('signInUrl is not supported for clients without authorizationCodeGrant or implicitCodeGrant flow enabled', this);
218219
}
219220
const path = options.signInPath ?? '/login';
220221
return `${this.baseUrl(options)}${path}?client_id=${client.userPoolClientId}&response_type=${responseType}&redirect_uri=${options.redirectUri}`;

packages/aws-cdk-lib/aws-cognito/lib/user-pool-email.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Construct } from 'constructs';
22
import { toASCII as punycodeEncode } from 'punycode/';
33
import { Stack, Token } from '../../core';
4+
import { UnscopedValidationError, ValidationError } from '../../core/lib/errors';
45

56
/**
67
* Configuration for Cognito sending emails via Amazon SES
@@ -159,7 +160,7 @@ class SESEmail extends UserPoolEmail {
159160
const region = Stack.of(scope).region;
160161

161162
if (Token.isUnresolved(region) && !this.options.sesRegion) {
162-
throw new Error('Your stack region cannot be determined so "sesRegion" is required in SESOptions');
163+
throw new ValidationError('Your stack region cannot be determined so "sesRegion" is required in SESOptions', scope);
163164
}
164165

165166
let from = encodeAndTest(this.options.fromEmail);
@@ -171,7 +172,7 @@ class SESEmail extends UserPoolEmail {
171172
if (this.options.sesVerifiedDomain) {
172173
const domainFromEmail = this.options.fromEmail.split('@').pop();
173174
if (domainFromEmail !== this.options.sesVerifiedDomain) {
174-
throw new Error('"fromEmail" contains a different domain than the "sesVerifiedDomain"');
175+
throw new ValidationError('"fromEmail" contains a different domain than the "sesVerifiedDomain"', scope);
175176
}
176177
}
177178

@@ -194,7 +195,7 @@ function encodeAndTest(input: string | undefined): string | undefined {
194195
if (input) {
195196
const local = input.split('@')[0];
196197
if (!/[\p{ASCII}]+/u.test(local)) {
197-
throw new Error('the local part of the email address must use ASCII characters only');
198+
throw new UnscopedValidationError('the local part of the email address must use ASCII characters only');
198199
}
199200
return punycodeEncode(input);
200201
} else {

packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CfnUserPoolGroup } from './cognito.generated';
33
import { IUserPool } from './user-pool';
44
import { IRole } from '../../aws-iam';
55
import { IResource, Resource, Token } from '../../core';
6+
import { ValidationError } from '../../core/lib/errors';
67

78
/**
89
* Represents a user pool group.
@@ -90,21 +91,21 @@ export class UserPoolGroup extends Resource implements IUserPoolGroup {
9091
if (props.description !== undefined &&
9192
!Token.isUnresolved(props.description) &&
9293
(props.description.length > 2048)) {
93-
throw new Error(`\`description\` must be between 0 and 2048 characters. Received: ${props.description.length} characters`);
94+
throw new ValidationError(`\`description\` must be between 0 and 2048 characters. Received: ${props.description.length} characters`, this);
9495
}
9596

9697
if (props.precedence !== undefined &&
9798
!Token.isUnresolved(props.precedence) &&
9899
(props.precedence < 0 || props.precedence > 2 ** 31 - 1)) {
99-
throw new Error(`\`precedence\` must be between 0 and 2^31-1. Received: ${props.precedence}`);
100+
throw new ValidationError(`\`precedence\` must be between 0 and 2^31-1. Received: ${props.precedence}`, this);
100101
}
101102

102103
if (
103104
props.groupName !== undefined &&
104105
!Token.isUnresolved(props.groupName) &&
105106
!/^[\p{L}\p{M}\p{S}\p{N}\p{P}]{1,128}$/u.test(props.groupName)
106107
) {
107-
throw new Error('\`groupName\` must be between 1 and 128 characters and can include letters, numbers, and symbols.');
108+
throw new ValidationError('\`groupName\` must be between 1 and 128 characters and can include letters, numbers, and symbols.', this);
108109
}
109110

110111
const resource = new CfnUserPoolGroup(this, 'Resource', {

packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/apple.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { UserPoolIdentityProviderProps } from './base';
33
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
44
import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base';
55
import { SecretValue } from '../../../core';
6+
import { ValidationError } from '../../../core/lib/errors';
67

78
/**
89
* Properties to initialize UserPoolAppleIdentityProvider
@@ -56,7 +57,7 @@ export class UserPoolIdentityProviderApple extends UserPoolIdentityProviderBase
5657
// Exactly one of the properties must be configured
5758
if ((!props.privateKey && !props.privateKeyValue) ||
5859
(props.privateKey && props.privateKeyValue)) {
59-
throw new Error('Exactly one of "privateKey" or "privateKeyValue" must be configured.');
60+
throw new ValidationError('Exactly one of "privateKey" or "privateKeyValue" must be configured.', this);
6061
}
6162

6263
const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {

packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/google.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Construct } from 'constructs';
22
import { UserPoolIdentityProviderProps } from './base';
33
import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base';
44
import { SecretValue } from '../../../core';
5+
import { ValidationError } from '../../../core/lib/errors';
56
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
67

78
/**
@@ -49,7 +50,7 @@ export class UserPoolIdentityProviderGoogle extends UserPoolIdentityProviderBase
4950
// at least one of the properties must be configured
5051
if ((!props.clientSecret && !props.clientSecretValue) ||
5152
(props.clientSecret && props.clientSecretValue)) {
52-
throw new Error('Exactly one of "clientSecret" or "clientSecretValue" must be configured.');
53+
throw new ValidationError('Exactly one of "clientSecret" or "clientSecretValue" must be configured.', this);
5354
}
5455

5556
const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {

packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/oidc.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Construct } from 'constructs';
22
import { UserPoolIdentityProviderProps } from './base';
33
import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base';
44
import { Names, Token } from '../../../core';
5+
import { ValidationError } from '../../../core/lib/errors';
56
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
67

78
/**
@@ -134,12 +135,12 @@ export class UserPoolIdentityProviderOidc extends UserPoolIdentityProviderBase {
134135
private getProviderName(name?: string): string {
135136
if (name) {
136137
if (!Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) {
137-
throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`);
138+
throw new ValidationError(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`, this);
138139
}
139140
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html#cfn-cognito-userpoolidentityprovider-providername
140141
// u is for unicode
141142
if (!name.match(/^[^_\p{Z}][\p{L}\p{M}\p{S}\p{N}\p{P}][^_\p{Z}]+$/u)) {
142-
throw new Error(`Expected provider name must match [^_\p{Z}][\p{L}\p{M}\p{S}\p{N}\p{P}][^_\p{Z}]+, received ${name}`);
143+
throw new ValidationError(`Expected provider name must match [^_\p{Z}][\p{L}\p{M}\p{S}\p{N}\p{P}][^_\p{Z}]+, received ${name}`, this);
143144
}
144145
return name;
145146
}

packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/saml.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Construct } from 'constructs';
22
import { UserPoolIdentityProviderProps } from './base';
33
import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base';
44
import { Names, Token } from '../../../core';
5+
import { ValidationError } from '../../../core/lib/errors';
56
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
67

78
/**
@@ -163,7 +164,7 @@ export class UserPoolIdentityProviderSaml extends UserPoolIdentityProviderBase {
163164

164165
private validateName(name?: string) {
165166
if (name && !Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) {
166-
throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`);
167+
throw new ValidationError(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`, this);
167168
}
168169
}
169170
}

0 commit comments

Comments
 (0)