Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cognito): throw ValidationError instead of untyped errors #33170

Merged
merged 1 commit into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/aws-cdk-lib/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 3 additions & 2 deletions packages/aws-cdk-lib/aws-cognito/lib/user-pool-attr.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand Down
23 changes: 11 additions & 12 deletions packages/aws-cdk-lib/aws-cognito/lib/user-pool-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 ?? {
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'); }
Expand Down Expand Up @@ -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);
}
}
}
7 changes: 4 additions & 3 deletions packages/aws-cdk-lib/aws-cognito/lib/user-pool-domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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}`;
Expand Down
7 changes: 4 additions & 3 deletions packages/aws-cdk-lib/aws-cognito/lib/user-pool-email.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}

Expand All @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions packages/aws-cdk-lib/aws-cognito/lib/user-pool-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -90,21 +91,21 @@ 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 (
props.groupName !== undefined &&
!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', {
Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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', {
Expand Down
5 changes: 3 additions & 2 deletions packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk-lib/aws-cognito/lib/user-pool-idps/saml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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);
}
}
}
Loading
Loading