From ecbe1bf8e7e43d7423abfe93aa1c09189bf418c0 Mon Sep 17 00:00:00 2001 From: Matsuda Date: Mon, 27 Jan 2025 06:34:47 +0900 Subject: [PATCH] feat(cognito): throw `ValidationError` instead of untyped errors (#33170) ### Issue # (if applicable) `aws-cognito` for https://github.com/aws/aws-cdk/issues/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* --- packages/aws-cdk-lib/.eslintrc.js | 3 +- .../aws-cognito/lib/user-pool-attr.ts | 5 ++- .../aws-cognito/lib/user-pool-client.ts | 23 ++++++----- .../aws-cognito/lib/user-pool-domain.ts | 7 ++-- .../aws-cognito/lib/user-pool-email.ts | 7 ++-- .../aws-cognito/lib/user-pool-group.ts | 7 ++-- .../aws-cognito/lib/user-pool-idps/apple.ts | 3 +- .../aws-cognito/lib/user-pool-idps/google.ts | 3 +- .../aws-cognito/lib/user-pool-idps/oidc.ts | 5 ++- .../aws-cognito/lib/user-pool-idps/saml.ts | 3 +- .../aws-cdk-lib/aws-cognito/lib/user-pool.ts | 38 +++++++++---------- 11 files changed, 56 insertions(+), 48 deletions(-) diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index c66f81d863292..4e7b37a28ecfc 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -17,9 +17,10 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [ // no-throw-default-error const enableNoThrowDefaultErrorIn = [ 'aws-amplify', - 'aws-amplifyuibuilder', + 'aws-amplifyuibuilder', 'aws-apigatewayv2-authorizers', 'aws-apigatewayv2-integrations', + 'aws-cognito', 'aws-elasticloadbalancing', 'aws-elasticloadbalancingv2', 'aws-elasticloadbalancingv2-actions', diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-attr.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-attr.ts index 5eb97f6f9045a..432abefe507c5 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-attr.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-attr.ts @@ -1,5 +1,6 @@ import { StandardAttributeNames } from './private/attr-names'; import { Token } from '../../core'; +import { UnscopedValidationError } from '../../core/lib/errors'; /** * The set of standard attributes that can be marked as required or mutable. @@ -242,10 +243,10 @@ export class StringAttribute implements ICustomAttribute { constructor(props: StringAttributeProps = {}) { if (props.minLen && !Token.isUnresolved(props.minLen) && props.minLen < 0) { - throw new Error(`minLen cannot be less than 0 (value: ${props.minLen}).`); + throw new UnscopedValidationError(`minLen cannot be less than 0 (value: ${props.minLen}).`); } if (props.maxLen && !Token.isUnresolved(props.maxLen) && props.maxLen > 2048) { - throw new Error(`maxLen cannot be greater than 2048 (value: ${props.maxLen}).`); + throw new UnscopedValidationError(`maxLen cannot be greater than 2048 (value: ${props.maxLen}).`); } this.minLen = props?.minLen; this.maxLen = props?.maxLen; diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-client.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-client.ts index a423962c6f304..2716d231ffc50 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-client.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-client.ts @@ -4,6 +4,7 @@ import { IUserPool } from './user-pool'; import { ClientAttributes } from './user-pool-attr'; import { IUserPoolResourceServer, ResourceServerScope } from './user-pool-resource-server'; import { IResource, Resource, Duration, Stack, SecretValue, Token } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '../../custom-resources'; /** @@ -383,7 +384,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { class Import extends Resource implements IUserPoolClient { public readonly userPoolClientId = userPoolClientId; get userPoolClientSecret(): SecretValue { - throw new Error('UserPool Client Secret is not available for imported Clients'); + throw new ValidationError('UserPool Client Secret is not available for imported Clients', this); } } @@ -414,7 +415,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { super(scope, id); if (props.disableOAuth && props.oAuth) { - throw new Error('OAuth settings cannot be specified when disableOAuth is set.'); + throw new ValidationError('OAuth settings cannot be specified when disableOAuth is set.', this); } this.oAuthFlows = props.oAuth?.flows ?? { @@ -427,23 +428,23 @@ export class UserPoolClient extends Resource implements IUserPoolClient { if (callbackUrls === undefined) { callbackUrls = ['https://example.com']; } else if (callbackUrls.length === 0) { - throw new Error('callbackUrl must not be empty when codeGrant or implicitGrant OAuth flows are enabled.'); + throw new ValidationError('callbackUrl must not be empty when codeGrant or implicitGrant OAuth flows are enabled.', this); } } if (props.oAuth?.defaultRedirectUri && !Token.isUnresolved(props.oAuth.defaultRedirectUri)) { if (callbackUrls && !callbackUrls.includes(props.oAuth.defaultRedirectUri)) { - throw new Error('defaultRedirectUri must be included in callbackUrls.'); + throw new ValidationError('defaultRedirectUri must be included in callbackUrls.', this); } const defaultRedirectUriPattern = /^(?=.{1,1024}$)[\p{L}\p{M}\p{S}\p{N}\p{P}]+$/u; if (!defaultRedirectUriPattern.test(props.oAuth.defaultRedirectUri)) { - throw new Error(`defaultRedirectUri must match the \`^(?=.{1,1024}$)[\p{L}\p{M}\p{S}\p{N}\p{P}]+$\` pattern, got ${props.oAuth.defaultRedirectUri}`); + 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); } } if (!props.generateSecret && props.enablePropagateAdditionalUserContextData) { - throw new Error('Cannot activate enablePropagateAdditionalUserContextData in an app client without a client secret.'); + throw new ValidationError('Cannot activate enablePropagateAdditionalUserContextData in an app client without a client secret.', this); } this._generateSecret = props.generateSecret; @@ -480,16 +481,14 @@ export class UserPoolClient extends Resource implements IUserPoolClient { */ public get userPoolClientName(): string { if (this._userPoolClientName === undefined) { - throw new Error('userPoolClientName is available only if specified on the UserPoolClient during initialization'); + throw new ValidationError('userPoolClientName is available only if specified on the UserPoolClient during initialization', this); } return this._userPoolClientName; } public get userPoolClientSecret(): SecretValue { if (!this._generateSecret) { - throw new Error( - 'userPoolClientSecret is available only if generateSecret is set to true.', - ); + throw new ValidationError('userPoolClientSecret is available only if generateSecret is set to true.', this); } // Create the Custom Resource that assists in resolving the User Pool Client secret @@ -540,7 +539,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { private configureOAuthFlows(): string[] | undefined { if ((this.oAuthFlows.authorizationCodeGrant || this.oAuthFlows.implicitCodeGrant) && this.oAuthFlows.clientCredentials) { - throw new Error('clientCredentials OAuth flow cannot be selected along with codeGrant or implicitGrant.'); + throw new ValidationError('clientCredentials OAuth flow cannot be selected along with codeGrant or implicitGrant.', this); } const oAuthFlows: string[] = []; if (this.oAuthFlows.clientCredentials) { oAuthFlows.push('client_credentials'); } @@ -614,7 +613,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { private validateDuration(name: string, min: Duration, max: Duration, value?: Duration) { if (value === undefined) { return; } if (value.toMilliseconds() < min.toMilliseconds() || value.toMilliseconds() > max.toMilliseconds()) { - throw new Error(`${name}: Must be a duration between ${min.toHumanString()} and ${max.toHumanString()} (inclusive); received ${value.toHumanString()}.`); + throw new ValidationError(`${name}: Must be a duration between ${min.toHumanString()} and ${max.toHumanString()} (inclusive); received ${value.toHumanString()}.`, this); } } } diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-domain.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-domain.ts index 229de8d624918..d686b51887c8e 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-domain.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-domain.ts @@ -4,6 +4,7 @@ import { IUserPool } from './user-pool'; import { UserPoolClient } from './user-pool-client'; import { ICertificate } from '../../aws-certificatemanager'; import { IResource, Resource, Stack, Token } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { AwsCustomResource, AwsCustomResourcePolicy, AwsSdkCall, PhysicalResourceId } from '../../custom-resources'; /** @@ -126,14 +127,14 @@ export class UserPoolDomain extends Resource implements IUserPoolDomain { super(scope, id); if (!!props.customDomain === !!props.cognitoDomain) { - throw new Error('One of, and only one of, cognitoDomain or customDomain must be specified'); + throw new ValidationError('One of, and only one of, cognitoDomain or customDomain must be specified', this); } if (props.cognitoDomain?.domainPrefix && !Token.isUnresolved(props.cognitoDomain?.domainPrefix) && !/^[a-z0-9-]+$/.test(props.cognitoDomain.domainPrefix)) { - throw new Error('domainPrefix for cognitoDomain can contain only lowercase alphabets, numbers and hyphens'); + throw new ValidationError('domainPrefix for cognitoDomain can contain only lowercase alphabets, numbers and hyphens', this); } this.isCognitoDomain = !!props.cognitoDomain; @@ -214,7 +215,7 @@ export class UserPoolDomain extends Resource implements IUserPoolDomain { } else if (client.oAuthFlows.implicitCodeGrant) { responseType = 'token'; } else { - throw new Error('signInUrl is not supported for clients without authorizationCodeGrant or implicitCodeGrant flow enabled'); + throw new ValidationError('signInUrl is not supported for clients without authorizationCodeGrant or implicitCodeGrant flow enabled', this); } const path = options.signInPath ?? '/login'; return `${this.baseUrl(options)}${path}?client_id=${client.userPoolClientId}&response_type=${responseType}&redirect_uri=${options.redirectUri}`; diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-email.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-email.ts index 884b25b5570ec..8b95462236566 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-email.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-email.ts @@ -1,6 +1,7 @@ import { Construct } from 'constructs'; import { toASCII as punycodeEncode } from 'punycode/'; import { Stack, Token } from '../../core'; +import { UnscopedValidationError, ValidationError } from '../../core/lib/errors'; /** * Configuration for Cognito sending emails via Amazon SES @@ -159,7 +160,7 @@ class SESEmail extends UserPoolEmail { const region = Stack.of(scope).region; if (Token.isUnresolved(region) && !this.options.sesRegion) { - throw new Error('Your stack region cannot be determined so "sesRegion" is required in SESOptions'); + throw new ValidationError('Your stack region cannot be determined so "sesRegion" is required in SESOptions', scope); } let from = encodeAndTest(this.options.fromEmail); @@ -171,7 +172,7 @@ class SESEmail extends UserPoolEmail { if (this.options.sesVerifiedDomain) { const domainFromEmail = this.options.fromEmail.split('@').pop(); if (domainFromEmail !== this.options.sesVerifiedDomain) { - throw new Error('"fromEmail" contains a different domain than the "sesVerifiedDomain"'); + throw new ValidationError('"fromEmail" contains a different domain than the "sesVerifiedDomain"', scope); } } @@ -194,7 +195,7 @@ function encodeAndTest(input: string | undefined): string | undefined { if (input) { const local = input.split('@')[0]; if (!/[\p{ASCII}]+/u.test(local)) { - throw new Error('the local part of the email address must use ASCII characters only'); + throw new UnscopedValidationError('the local part of the email address must use ASCII characters only'); } return punycodeEncode(input); } else { diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts index 52b55a12e83e5..bb89b1ab48759 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts @@ -3,6 +3,7 @@ import { CfnUserPoolGroup } from './cognito.generated'; import { IUserPool } from './user-pool'; import { IRole } from '../../aws-iam'; import { IResource, Resource, Token } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; /** * Represents a user pool group. @@ -90,13 +91,13 @@ export class UserPoolGroup extends Resource implements IUserPoolGroup { if (props.description !== undefined && !Token.isUnresolved(props.description) && (props.description.length > 2048)) { - throw new Error(`\`description\` must be between 0 and 2048 characters. Received: ${props.description.length} characters`); + throw new ValidationError(`\`description\` must be between 0 and 2048 characters. Received: ${props.description.length} characters`, this); } if (props.precedence !== undefined && !Token.isUnresolved(props.precedence) && (props.precedence < 0 || props.precedence > 2 ** 31 - 1)) { - throw new Error(`\`precedence\` must be between 0 and 2^31-1. Received: ${props.precedence}`); + throw new ValidationError(`\`precedence\` must be between 0 and 2^31-1. Received: ${props.precedence}`, this); } if ( @@ -104,7 +105,7 @@ export class UserPoolGroup extends Resource implements IUserPoolGroup { !Token.isUnresolved(props.groupName) && !/^[\p{L}\p{M}\p{S}\p{N}\p{P}]{1,128}$/u.test(props.groupName) ) { - throw new Error('\`groupName\` must be between 1 and 128 characters and can include letters, numbers, and symbols.'); + throw new ValidationError('\`groupName\` must be between 1 and 128 characters and can include letters, numbers, and symbols.', this); } const resource = new CfnUserPoolGroup(this, 'Resource', { diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/apple.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/apple.ts index 1b3c23c54e04b..ff21453bddbc7 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/apple.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/apple.ts @@ -3,6 +3,7 @@ import { UserPoolIdentityProviderProps } from './base'; import { CfnUserPoolIdentityProvider } from '../cognito.generated'; import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base'; import { SecretValue } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; /** * Properties to initialize UserPoolAppleIdentityProvider @@ -56,7 +57,7 @@ export class UserPoolIdentityProviderApple extends UserPoolIdentityProviderBase // Exactly one of the properties must be configured if ((!props.privateKey && !props.privateKeyValue) || (props.privateKey && props.privateKeyValue)) { - throw new Error('Exactly one of "privateKey" or "privateKeyValue" must be configured.'); + throw new ValidationError('Exactly one of "privateKey" or "privateKeyValue" must be configured.', this); } const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/google.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/google.ts index 9c76fb1d05ed7..73ff047546ef5 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/google.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/google.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { UserPoolIdentityProviderProps } from './base'; import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base'; import { SecretValue } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { CfnUserPoolIdentityProvider } from '../cognito.generated'; /** @@ -49,7 +50,7 @@ export class UserPoolIdentityProviderGoogle extends UserPoolIdentityProviderBase // at least one of the properties must be configured if ((!props.clientSecret && !props.clientSecretValue) || (props.clientSecret && props.clientSecretValue)) { - throw new Error('Exactly one of "clientSecret" or "clientSecretValue" must be configured.'); + throw new ValidationError('Exactly one of "clientSecret" or "clientSecretValue" must be configured.', this); } const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/oidc.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/oidc.ts index 72e6e92c5ed4e..7940f1bbbe6ee 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/oidc.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/oidc.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { UserPoolIdentityProviderProps } from './base'; import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base'; import { Names, Token } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { CfnUserPoolIdentityProvider } from '../cognito.generated'; /** @@ -134,12 +135,12 @@ export class UserPoolIdentityProviderOidc extends UserPoolIdentityProviderBase { private getProviderName(name?: string): string { if (name) { if (!Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) { - throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`); + throw new ValidationError(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`, this); } // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html#cfn-cognito-userpoolidentityprovider-providername // u is for unicode if (!name.match(/^[^_\p{Z}][\p{L}\p{M}\p{S}\p{N}\p{P}][^_\p{Z}]+$/u)) { - throw new Error(`Expected provider name must match [^_\p{Z}][\p{L}\p{M}\p{S}\p{N}\p{P}][^_\p{Z}]+, received ${name}`); + 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); } return name; } diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/saml.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/saml.ts index 554084c4e7ee3..9122aeb78c41d 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/saml.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/saml.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { UserPoolIdentityProviderProps } from './base'; import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base'; import { Names, Token } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { CfnUserPoolIdentityProvider } from '../cognito.generated'; /** @@ -163,7 +164,7 @@ export class UserPoolIdentityProviderSaml extends UserPoolIdentityProviderBase { private validateName(name?: string) { if (name && !Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) { - throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`); + throw new ValidationError(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`, this); } } } diff --git a/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts b/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts index 94f4ed75449ba..d08a4a14bc913 100644 --- a/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts +++ b/packages/aws-cdk-lib/aws-cognito/lib/user-pool.ts @@ -932,7 +932,7 @@ export class UserPool extends UserPoolBase { const arnParts = Stack.of(scope).splitArn(userPoolArn, ArnFormat.SLASH_RESOURCE_NAME); if (!arnParts.resourceName) { - throw new Error('invalid user pool ARN'); + throw new ValidationError('invalid user pool ARN', scope); } const userPoolId = arnParts.resourceName; @@ -997,7 +997,7 @@ export class UserPool extends UserPoolBase { case 'customSmsSender': case 'customEmailSender': if (!this.triggers.kmsKeyId) { - throw new Error('you must specify a KMS key if you are using customSmsSender or customEmailSender.'); + throw new ValidationError('you must specify a KMS key if you are using customSmsSender or customEmailSender.', this); } trigger = props.lambdaTriggers[t]; const version = 'V1_0'; @@ -1042,7 +1042,7 @@ export class UserPool extends UserPoolBase { const passwordPolicy = this.configurePasswordPolicy(props); if (props.email && props.emailSettings) { - throw new Error('you must either provide "email" or "emailSettings", but not both'); + throw new ValidationError('you must either provide "email" or "emailSettings", but not both', this); } const emailConfiguration = props.email ? props.email._bind(this) : undefinedIfNoKeys({ from: encodePuny(props.emailSettings?.from), @@ -1054,7 +1054,7 @@ export class UserPool extends UserPoolBase { props.featurePlan && props.featurePlan !== FeaturePlan.LITE && props.advancedSecurityMode && props.advancedSecurityMode !== AdvancedSecurityMode.OFF ) { - throw new Error('you cannot enable Advanced Security Mode when feature plan is Essentials or higher.'); + throw new ValidationError('you cannot enable Advanced Security Mode when feature plan is Essentials or higher.', this); } const userPool = new CfnUserPool(this, 'Resource', { @@ -1102,10 +1102,10 @@ export class UserPool extends UserPoolBase { */ public addTrigger(operation: UserPoolOperation, fn: lambda.IFunction, lambdaVersion?: LambdaVersion): void { if (operation.operationName in this.triggers) { - throw new Error(`A trigger for the operation ${operation.operationName} already exists.`); + throw new ValidationError(`A trigger for the operation ${operation.operationName} already exists.`, this); } if (operation !== UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG && lambdaVersion === LambdaVersion.V2_0) { - throw new Error('Only the `PRE_TOKEN_GENERATION_CONFIG` operation supports V2_0 lambda version.'); + throw new ValidationError('Only the `PRE_TOKEN_GENERATION_CONFIG` operation supports V2_0 lambda version.', this); } this.addLambdaPermission(fn, operation.operationName); @@ -1113,7 +1113,7 @@ export class UserPool extends UserPoolBase { case 'customEmailSender': case 'customSmsSender': if (!this.triggers.kmsKeyId) { - throw new Error('you must specify a KMS key if you are using customSmsSender or customEmailSender.'); + throw new ValidationError('you must specify a KMS key if you are using customSmsSender or customEmailSender.', this); } (this.triggers as any)[operation.operationName] = { lambdaArn: fn.functionArn, @@ -1148,11 +1148,11 @@ export class UserPool extends UserPoolBase { if (message && !Token.isUnresolved(message)) { if (!message.includes(CODE_TEMPLATE)) { - throw new Error(`MFA message must contain the template string '${CODE_TEMPLATE}'`); + throw new ValidationError(`MFA message must contain the template string '${CODE_TEMPLATE}'`, this); } if (message.length > MAX_LENGTH) { - throw new Error(`MFA message must be between ${CODE_TEMPLATE.length} and ${MAX_LENGTH} characters`); + throw new ValidationError(`MFA message must be between ${CODE_TEMPLATE.length} and ${MAX_LENGTH} characters`, this); } } @@ -1175,10 +1175,10 @@ export class UserPool extends UserPoolBase { if (emailStyle === VerificationEmailStyle.CODE) { const emailMessage = props.userVerification?.emailBody ?? `The verification code to your new account is ${CODE_TEMPLATE}`; if (!Token.isUnresolved(emailMessage) && emailMessage.indexOf(CODE_TEMPLATE) < 0) { - throw new Error(`Verification email body must contain the template string '${CODE_TEMPLATE}'`); + throw new ValidationError(`Verification email body must contain the template string '${CODE_TEMPLATE}'`, this); } if (!Token.isUnresolved(smsMessage) && smsMessage.indexOf(CODE_TEMPLATE) < 0) { - throw new Error(`SMS message must contain the template string '${CODE_TEMPLATE}'`); + throw new ValidationError(`SMS message must contain the template string '${CODE_TEMPLATE}'`, this); } return { defaultEmailOption: VerificationEmailStyle.CODE, @@ -1190,7 +1190,7 @@ export class UserPool extends UserPoolBase { const emailMessage = props.userVerification?.emailBody ?? `Verify your account by clicking on ${VERIFY_EMAIL_TEMPLATE}`; if (!Token.isUnresolved(emailMessage) && !VERIFY_EMAIL_REGEX.test(emailMessage)) { - throw new Error(`Verification email body must contain the template string '${VERIFY_EMAIL_TEMPLATE}'`); + throw new ValidationError(`Verification email body must contain the template string '${VERIFY_EMAIL_TEMPLATE}'`, this); } return { defaultEmailOption: VerificationEmailStyle.LINK, @@ -1209,7 +1209,7 @@ export class UserPool extends UserPoolBase { const signIn: SignInAliases = props.signInAliases ?? { username: true }; if (signIn.preferredUsername && !signIn.username) { - throw new Error('username signIn must be enabled if preferredUsername is enabled'); + throw new ValidationError('username signIn must be enabled if preferredUsername is enabled', this); } if (signIn.username) { @@ -1239,7 +1239,7 @@ export class UserPool extends UserPoolBase { private smsConfiguration(props: UserPoolProps): CfnUserPool.SmsConfigurationProperty | undefined { if (props.enableSmsRole === false && props.smsRole) { - throw new Error('enableSmsRole cannot be disabled when smsRole is specified'); + throw new ValidationError('enableSmsRole cannot be disabled when smsRole is specified', this); } if (props.smsRole) { @@ -1316,11 +1316,11 @@ export class UserPool extends UserPoolBase { private configurePasswordPolicy(props: UserPoolProps): CfnUserPool.PasswordPolicyProperty | undefined { const tempPasswordValidity = props.passwordPolicy?.tempPasswordValidity; if (tempPasswordValidity !== undefined && tempPasswordValidity.toDays() > Duration.days(365).toDays()) { - throw new Error(`tempPasswordValidity cannot be greater than 365 days (received: ${tempPasswordValidity.toDays()})`); + throw new ValidationError(`tempPasswordValidity cannot be greater than 365 days (received: ${tempPasswordValidity.toDays()})`, this); } const minLength = props.passwordPolicy ? props.passwordPolicy.minLength ?? 8 : undefined; if (minLength !== undefined && (minLength < 6 || minLength > 99)) { - throw new Error(`minLength for password must be between 6 and 99 (received: ${minLength})`); + throw new ValidationError(`minLength for password must be between 6 and 99 (received: ${minLength})`, this); } const passwordHistorySize = props.passwordPolicy?.passwordHistorySize; if (passwordHistorySize !== undefined) { @@ -1422,7 +1422,7 @@ export class UserPool extends UserPoolBase { case AccountRecovery.PHONE_AND_EMAIL: return undefined; default: - throw new Error(`Unsupported AccountRecovery type - ${accountRecovery}`); + throw new ValidationError(`Unsupported AccountRecovery type - ${accountRecovery}`, this); } } @@ -1448,11 +1448,11 @@ export class UserPool extends UserPoolBase { private validateEmailMfa(props: UserPoolProps) { if (props.email === undefined || this.emailConfiguration?.emailSendingAccount !== 'DEVELOPER') { - throw new Error('To enable email-based MFA, set `email` property to the Amazon SES email-sending configuration.'); + throw new ValidationError('To enable email-based MFA, set `email` property to the Amazon SES email-sending configuration.', this); } if (props.featurePlan === FeaturePlan.LITE && (!props.advancedSecurityMode || props.advancedSecurityMode === AdvancedSecurityMode.OFF)) { - throw new Error('To enable email-based MFA, set `featurePlan` to `FeaturePlan.ESSENTIALS` or `FeaturePlan.PLUS`.'); + throw new ValidationError('To enable email-based MFA, set `featurePlan` to `FeaturePlan.ESSENTIALS` or `FeaturePlan.PLUS`.', this); } } }