packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts (431 lines of code) (raw):

import { Construct } from 'constructs'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema, CfnDomainName, CfnDomainNameApiAssociation, CfnSourceApiAssociation } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase, Visibility, AuthorizationType } from './graphqlapi-base'; import { ISchema, SchemaFile } from './schema'; import { MergeType, addSourceApiAutoMergePermission, addSourceGraphQLPermission } from './source-api-association'; import { ICertificate } from '../../aws-certificatemanager'; import { IUserPool } from '../../aws-cognito'; import { ManagedPolicy, Role, IRole, ServicePrincipal } from '../../aws-iam'; import { IFunction } from '../../aws-lambda'; import { ILogGroup, LogGroup, LogRetention, RetentionDays } from '../../aws-logs'; import { CfnResource, Duration, Expiration, FeatureFlags, IResolvable, Lazy, Stack, Token, ValidationError } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import * as cxapi from '../../cx-api'; /** * Interface to specify default or additional authorization(s) */ export interface AuthorizationMode { /** * One of possible four values AppSync supports * * @see https://docs.aws.amazon.com/appsync/latest/devguide/security.html * * @default - `AuthorizationType.API_KEY` */ readonly authorizationType: AuthorizationType; /** * If authorizationType is `AuthorizationType.USER_POOL`, this option is required. * @default - none */ readonly userPoolConfig?: UserPoolConfig; /** * If authorizationType is `AuthorizationType.API_KEY`, this option can be configured. * @default - name: 'DefaultAPIKey' | description: 'Default API Key created by CDK' */ readonly apiKeyConfig?: ApiKeyConfig; /** * If authorizationType is `AuthorizationType.OIDC`, this option is required. * @default - none */ readonly openIdConnectConfig?: OpenIdConnectConfig; /** * If authorizationType is `AuthorizationType.LAMBDA`, this option is required. * @default - none */ readonly lambdaAuthorizerConfig?: LambdaAuthorizerConfig; } /** * enum with all possible values for Cognito user-pool default actions */ export enum UserPoolDefaultAction { /** * ALLOW access to API */ ALLOW = 'ALLOW', /** * DENY access to API */ DENY = 'DENY', } /** * Configuration for Cognito user-pools in AppSync */ export interface UserPoolConfig { /** * The Cognito user pool to use as identity source */ readonly userPool: IUserPool; /** * the optional app id regex * * @default - None */ readonly appIdClientRegex?: string; /** * Default auth action * * @default ALLOW */ readonly defaultAction?: UserPoolDefaultAction; } /** * Configuration for API Key authorization in AppSync */ export interface ApiKeyConfig { /** * Unique name of the API Key * @default - 'DefaultAPIKey' */ readonly name?: string; /** * Description of API key * @default - 'Default API Key created by CDK' */ readonly description?: string; /** * The time from creation time after which the API key expires. * It must be a minimum of 1 day and a maximum of 365 days from date of creation. * Rounded down to the nearest hour. * * @default - 7 days rounded down to nearest hour */ readonly expires?: Expiration; } /** * Configuration for OpenID Connect authorization in AppSync */ export interface OpenIdConnectConfig { /** * The number of milliseconds an OIDC token is valid after being authenticated by OIDC provider. * `auth_time` claim in OIDC token is required for this validation to work. * @default - no validation */ readonly tokenExpiryFromAuth?: number; /** * The number of milliseconds an OIDC token is valid after being issued to a user. * This validation uses `iat` claim of OIDC token. * @default - no validation */ readonly tokenExpiryFromIssue?: number; /** * The client identifier of the Relying party at the OpenID identity provider. * A regular expression can be specified so AppSync can validate against multiple client identifiers at a time. * @example - 'ABCD|CDEF' // where ABCD and CDEF are two different clientId * @default - * (All) */ readonly clientId?: string; /** * The issuer for the OIDC configuration. The issuer returned by discovery must exactly match the value of `iss` in the OIDC token. */ readonly oidcProvider: string; } /** * Configuration for Lambda authorization in AppSync. Note that you can only have a single AWS Lambda function configured to authorize your API. */ export interface LambdaAuthorizerConfig { /** * The authorizer lambda function. * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appsync-graphqlapi-lambdaauthorizerconfig.html */ readonly handler: IFunction; /** * How long the results are cached. * Disable caching by setting this to 0. * * @default Duration.minutes(5) */ readonly resultsCacheTtl?: Duration; /** * A regular expression for validation of tokens before the Lambda function is called. * * @default - no regex filter will be applied. */ readonly validationRegex?: string; } /** * Configuration of the API authorization modes. */ export interface AuthorizationConfig { /** * Optional authorization configuration * * @default - API Key authorization */ readonly defaultAuthorization?: AuthorizationMode; /** * Additional authorization modes * * @default - No other modes */ readonly additionalAuthorizationModes?: AuthorizationMode[]; } /** * log-level for fields in AppSync */ export enum FieldLogLevel { /** * Resolver logging is disabled */ NONE = 'NONE', /** * Only Error messages appear in logs */ ERROR = 'ERROR', /** * Info and Error messages appear in logs */ INFO = 'INFO', /** * Debug, Info, and Error messages, appear in logs */ DEBUG = 'DEBUG', /** * All messages (Debug, Error, Info, and Trace) appear in logs */ ALL = 'ALL', } /** * Logging configuration for AppSync */ export interface LogConfig { /** * exclude verbose content * * @default false */ readonly excludeVerboseContent?: boolean | IResolvable; /** * log level for fields * * @default - Use AppSync default */ readonly fieldLogLevel?: FieldLogLevel; /** * The role for CloudWatch Logs * * @default - None */ readonly role?: IRole; /** * The number of days log events are kept in CloudWatch Logs. * By default AppSync keeps the logs infinitely. When updating this property, * unsetting it doesn't remove the log retention policy. * To remove the retention policy, set the value to `INFINITE` * * @default RetentionDays.INFINITE */ readonly retention?: RetentionDays; } /** * Domain name configuration for AppSync */ export interface DomainOptions { /** * The certificate to use with the domain name. */ readonly certificate: ICertificate; /** * The actual domain name. For example, `api.example.com`. */ readonly domainName: string; } /** * Additional API configuration for creating a AppSync Merged API */ export interface SourceApiOptions { /** * Definition of source APIs associated with this Merged API */ readonly sourceApis: SourceApi[]; /** * IAM Role used to validate access to source APIs at runtime and to update the merged API endpoint with the source API changes * * @default - An IAM Role with acccess to source schemas will be created */ readonly mergedApiExecutionRole?: Role; } /** * Configuration of source API */ export interface SourceApi { /** * Source API that is associated with the merged API */ readonly sourceApi: IGraphqlApi; /** * Merging option used to associate the source API to the Merged API * * @default - Auto merge. The merge is triggered automatically when the source API has changed */ readonly mergeType?: MergeType; /** * Description of the Source API asssociation. */ readonly description?: string; } /** * AppSync definition. Specify how you want to define your AppSync API. */ export abstract class Definition { /** * Schema from schema object. * @param schema SchemaFile.fromAsset(filePath: string) allows schema definition through schema.graphql file * @returns Definition with schema from file */ public static fromSchema(schema: ISchema): Definition { return { schema, }; } /** * Schema from file, allows schema definition through schema.graphql file * @param filePath the file path of the schema file * @returns Definition with schema from file */ public static fromFile(filePath: string): Definition { return this.fromSchema(SchemaFile.fromAsset(filePath)); } /** * Schema from existing AppSync APIs - used for creating a AppSync Merged API * @param sourceApiOptions Configuration for AppSync Merged API * @returns Definition with for AppSync Merged API */ public static fromSourceApis(sourceApiOptions: SourceApiOptions): Definition { return { sourceApiOptions, }; } /** * Schema, when AppSync API is created from schema file */ readonly schema?: ISchema; /** * Source APIs for Merged API */ readonly sourceApiOptions?: SourceApiOptions; } /** * Properties for an AppSync GraphQL API */ export interface GraphqlApiProps { /** * the name of the GraphQL API */ readonly name: string; /** * Optional authorization configuration * * @default - API Key authorization */ readonly authorizationConfig?: AuthorizationConfig; /** * Logging configuration for this api * * @default - None */ readonly logConfig?: LogConfig; /** * Definition (schema file or source APIs) for this GraphQL Api */ readonly definition?: Definition; /** * GraphQL schema definition. Specify how you want to define your schema. * * SchemaFile.fromAsset(filePath: string) allows schema definition through schema.graphql file * * @default - schema will be generated code-first (i.e. addType, addObjectType, etc.) * @deprecated use Definition.schema instead */ readonly schema?: ISchema; /** * A flag indicating whether or not X-Ray tracing is enabled for the GraphQL API. * * @default - false */ readonly xrayEnabled?: boolean; /** * A value indicating whether the API is accessible from anywhere (GLOBAL) or can only be access from a VPC (PRIVATE). * * @default - GLOBAL */ readonly visibility?: Visibility; /** * The domain name configuration for the GraphQL API * * The Route 53 hosted zone and CName DNS record must be configured in addition to this setting to * enable custom domain URL * * @default - no domain name */ readonly domainName?: DomainOptions; /** * A value indicating whether the API to enable (ENABLED) or disable (DISABLED) introspection. * * @default IntrospectionConfig.ENABLED */ readonly introspectionConfig?: IntrospectionConfig; /** * A number indicating the maximum depth resolvers should be accepted when handling queries. * Value must be withing range of 0 to 75 * * @default - The default value is 0 (or unspecified) which indicates no maximum depth. */ readonly queryDepthLimit?: number; /** * A number indicating the maximum number of resolvers that should be accepted when handling queries. * Value must be withing range of 0 to 10000 * * @default - The default value is 0 (or unspecified), which will set the limit to 10000 */ readonly resolverCountLimit?: number; /** * A map containing the list of resources with their properties and environment variables. * * There are a few rules you must follow when creating keys and values: * - Keys must begin with a letter. * - Keys must be between 2 and 64 characters long. * - Keys can only contain letters, numbers, and the underscore character (_). * - Values can be up to 512 characters long. * - You can configure up to 50 key-value pairs in a GraphQL API. * * @default - No environment variables. */ readonly environmentVariables?: { [key: string]: string }; /** * The owner contact information for an API resource. * * This field accepts any string input with a length of 0 - 256 characters. * * @default - No owner contact. */ readonly ownerContact?: string; } /** * Attributes for GraphQL imports */ export interface GraphqlApiAttributes { /** * an unique AWS AppSync GraphQL API identifier * i.e. 'lxz775lwdrgcndgz3nurvac7oa' */ readonly graphqlApiId: string; /** * the arn for the GraphQL Api * @default - autogenerated arn */ readonly graphqlApiArn?: string; /** * The GraphQl endpoint arn for the GraphQL API * * @default - none, required to construct event rules from imported APIs */ readonly graphQLEndpointArn?: string; /** * The GraphQl API visibility * * @default - GLOBAL */ readonly visibility?: Visibility; /** * The Authorization Types for this GraphQL Api * * @default - none, required to construct event rules from imported APIs */ readonly modes?: AuthorizationType[]; } /** * Introspection configuration for a GraphQL API */ export enum IntrospectionConfig { /** * Enable introspection */ ENABLED = 'ENABLED', /** * Disable introspection */ DISABLED = 'DISABLED', } /** * An AppSync GraphQL API * * @resource AWS::AppSync::GraphQLApi */ export class GraphqlApi extends GraphqlApiBase { /** * Import a GraphQL API through this function * * @param scope scope * @param id id * @param attrs GraphQL API Attributes of an API */ public static fromGraphqlApiAttributes(scope: Construct, id: string, attrs: GraphqlApiAttributes): IGraphqlApi { const arn = attrs.graphqlApiArn ?? Stack.of(scope).formatArn({ service: 'appsync', resource: `apis/${attrs.graphqlApiId}`, }); class Import extends GraphqlApiBase { public readonly apiId = attrs.graphqlApiId; public readonly arn = arn; // the GraphQL endpoint ARN is not required to identify an AppSync GraphQL API // this value is only needed to construct event rules. public readonly graphQLEndpointArn = attrs.graphQLEndpointArn ?? ''; public readonly visibility = attrs.visibility ?? Visibility.GLOBAL; public readonly modes = attrs.modes ?? []; constructor(s: Construct, i: string) { super(s, i); } } return new Import(scope, id); } /** * an unique AWS AppSync GraphQL API identifier * i.e. 'lxz775lwdrgcndgz3nurvac7oa' */ public readonly apiId: string; /** * the ARN of the API */ public readonly arn: string; /** * The GraphQL endpoint ARN */ public readonly graphQLEndpointArn: string; /** * the URL of the endpoint created by AppSync * * @attribute GraphQlUrl */ public readonly graphqlUrl: string; /** * the name of the API */ public readonly name: string; /** * the visibility of the API */ public readonly visibility: Visibility; /** * the schema attached to this api (only available for GraphQL APIs, not available for merged APIs) */ public get schema(): ISchema { if (this.definition.schema) { return this.definition.schema; } throw new ValidationError('Schema does not exist for AppSync merged APIs.', this); } /** * The Authorization Types for this GraphQL Api */ public readonly modes: AuthorizationType[]; /** * the configured API key, if present * * @default - no api key */ public readonly apiKey?: string; /** * the CloudWatch Log Group for this API */ public readonly logGroup: ILogGroup; private definition: Definition; private schemaResource?: CfnGraphQLSchema; private api: CfnGraphQLApi; private apiKeyResource?: CfnApiKey; private domainNameResource?: CfnDomainName; private mergedApiExecutionRole?: IRole; private environmentVariables: { [key: string]: string } = {}; constructor(scope: Construct, id: string, props: GraphqlApiProps) { super(scope, id); // Enhanced CDK Analytics Telemetry addConstructMetadata(this, props); const defaultMode = props.authorizationConfig?.defaultAuthorization ?? { authorizationType: AuthorizationType.API_KEY }; const additionalModes = props.authorizationConfig?.additionalAuthorizationModes ?? []; const modes = [defaultMode, ...additionalModes]; this.modes = modes.map((mode) => mode.authorizationType); this.validateAuthorizationProps(modes); if (!props.schema && !props.definition) { throw new ValidationError('You must specify a GraphQL schema or source APIs in property definition.', this); } if ((props.schema !== undefined) === (props.definition !== undefined)) { throw new ValidationError('You cannot specify both properties schema and definition.', this); } if (props.queryDepthLimit !== undefined && (props.queryDepthLimit < 0 || props.queryDepthLimit > 75)) { throw new ValidationError('You must specify a query depth limit between 0 and 75.', this); } if (props.resolverCountLimit !== undefined && (props.resolverCountLimit < 0 || props.resolverCountLimit > 10000)) { throw new ValidationError('You must specify a resolver count limit between 0 and 10000.', this); } if (!Token.isUnresolved(props.ownerContact) && props.ownerContact !== undefined && (props.ownerContact.length > 256)) { throw new ValidationError('You must specify `ownerContact` as a string of 256 characters or less.', this); } this.definition = props.schema ? Definition.fromSchema(props.schema) : props.definition!; if (this.definition.sourceApiOptions) { this.setupMergedApiExecutionRole(this.definition.sourceApiOptions); } if (props.environmentVariables !== undefined) { Object.entries(props.environmentVariables).forEach(([key, value]) => { this.addEnvironmentVariable(key, value); }); } this.node.addValidation({ validate: () => this.validateEnvironmentVariables() }); this.visibility = props.visibility ?? Visibility.GLOBAL; this.api = new CfnGraphQLApi(this, 'Resource', { name: props.name, authenticationType: defaultMode.authorizationType, logConfig: this.setupLogConfig(props.logConfig), openIdConnectConfig: this.setupOpenIdConnectConfig(defaultMode.openIdConnectConfig), userPoolConfig: this.setupUserPoolConfig(defaultMode.userPoolConfig), lambdaAuthorizerConfig: this.setupLambdaAuthorizerConfig(defaultMode.lambdaAuthorizerConfig), additionalAuthenticationProviders: this.setupAdditionalAuthorizationModes(additionalModes), xrayEnabled: props.xrayEnabled, visibility: props.visibility, mergedApiExecutionRoleArn: this.mergedApiExecutionRole?.roleArn, apiType: this.definition.sourceApiOptions ? 'MERGED' : undefined, introspectionConfig: props.introspectionConfig, queryDepthLimit: props.queryDepthLimit, resolverCountLimit: props.resolverCountLimit, environmentVariables: Lazy.any({ produce: () => this.renderEnvironmentVariables() }), ownerContact: props.ownerContact, }); this.apiId = this.api.attrApiId; this.arn = this.api.attrArn; this.graphqlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; this.graphQLEndpointArn = this.api.attrGraphQlEndpointArn; if (this.definition.schema) { this.schemaResource = new CfnGraphQLSchema(this, 'Schema', this.definition.schema.bind(this)); } else { this.setupSourceApiAssociations(); } if (props.domainName) { this.domainNameResource = new CfnDomainName(this, 'DomainName', { domainName: props.domainName.domainName, certificateArn: props.domainName.certificate.certificateArn, description: `domain for ${this.name} at ${this.graphqlUrl}`, }); const domainNameAssociation = new CfnDomainNameApiAssociation(this, 'DomainAssociation', { domainName: props.domainName.domainName, apiId: this.apiId, }); domainNameAssociation.addDependency(this.domainNameResource); } if (modes.some((mode) => mode.authorizationType === AuthorizationType.API_KEY)) { const config = modes.find((mode: AuthorizationMode) => { return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig; })?.apiKeyConfig; this.apiKeyResource = this.createAPIKey(config); if (this.schemaResource) { this.apiKeyResource.addDependency(this.schemaResource); } this.apiKey = this.apiKeyResource.attrApiKey; } if (modes.some((mode) => mode.authorizationType === AuthorizationType.LAMBDA)) { const config = modes.find((mode: AuthorizationMode) => { return mode.authorizationType === AuthorizationType.LAMBDA && mode.lambdaAuthorizerConfig; })?.lambdaAuthorizerConfig; if (FeatureFlags.of(this).isEnabled(cxapi.APPSYNC_GRAPHQLAPI_SCOPE_LAMBDA_FUNCTION_PERMISSION)) { config?.handler.addPermission(`${id}-appsync`, { principal: new ServicePrincipal('appsync.amazonaws.com'), action: 'lambda:InvokeFunction', sourceArn: this.arn, }); } else { config?.handler.addPermission(`${id}-appsync`, { principal: new ServicePrincipal('appsync.amazonaws.com'), action: 'lambda:InvokeFunction', }); } } const logGroupName = `/aws/appsync/apis/${this.apiId}`; if (props.logConfig) { const logRetention = new LogRetention(this, 'LogRetention', { logGroupName: logGroupName, retention: props.logConfig?.retention ?? RetentionDays.INFINITE, }); this.logGroup = LogGroup.fromLogGroupArn(this, 'LogGroup', logRetention.logGroupArn); } else { this.logGroup = LogGroup.fromLogGroupName(this, 'LogGroup', logGroupName); } } private setupSourceApiAssociations() { this.definition.sourceApiOptions?.sourceApis.forEach(sourceApiConfig => { const mergeType = sourceApiConfig.mergeType ?? MergeType.AUTO_MERGE; let sourceApiIdentifier = sourceApiConfig.sourceApi.apiId; let mergedApiIdentifier = this.apiId; // This is protected by a feature flag because if there is an existing source api association that used the api id, // updating it to use ARN as identifier leads to a resource replacement. ARN is recommended going forward because it allows support // for both same account and cross account use cases. if (FeatureFlags.of(this).isEnabled(cxapi.APPSYNC_ENABLE_USE_ARN_IDENTIFIER_SOURCE_API_ASSOCIATION)) { sourceApiIdentifier = sourceApiConfig.sourceApi.arn; mergedApiIdentifier = this.arn; } const association = new CfnSourceApiAssociation(this, `${sourceApiConfig.sourceApi.node.id}Association`, { sourceApiIdentifier: sourceApiIdentifier, mergedApiIdentifier: mergedApiIdentifier, sourceApiAssociationConfig: { mergeType: mergeType, }, description: sourceApiConfig.description, }); // Add dependency because the schema must be created first to create the source api association. sourceApiConfig.sourceApi.addSchemaDependency(association); // Add permissions to merged api execution role const executionRole = this.mergedApiExecutionRole as IRole; addSourceGraphQLPermission(association, executionRole); if (mergeType === MergeType.AUTO_MERGE) { addSourceApiAutoMergePermission(association, executionRole); } }); } private setupMergedApiExecutionRole(sourceApiOptions: SourceApiOptions) { if (sourceApiOptions.mergedApiExecutionRole) { this.mergedApiExecutionRole = sourceApiOptions.mergedApiExecutionRole; } else { this.mergedApiExecutionRole = new Role(this, 'MergedApiExecutionRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com'), }); } } private validateAuthorizationProps(modes: AuthorizationMode[]) { if (modes.filter((mode) => mode.authorizationType === AuthorizationType.LAMBDA).length > 1) { throw new ValidationError('You can only have a single AWS Lambda function configured to authorize your API.', this); } modes.map((mode) => { if (mode.authorizationType === AuthorizationType.OIDC && !mode.openIdConnectConfig) { throw new ValidationError('Missing OIDC Configuration', this); } if (mode.authorizationType === AuthorizationType.USER_POOL && !mode.userPoolConfig) { throw new ValidationError('Missing User Pool Configuration', this); } if (mode.authorizationType === AuthorizationType.LAMBDA && !mode.lambdaAuthorizerConfig) { throw new ValidationError('Missing Lambda Configuration', this); } }); if (modes.filter((mode) => mode.authorizationType === AuthorizationType.API_KEY).length > 1) { throw new ValidationError('You can\'t duplicate API_KEY configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html', this); } if (modes.filter((mode) => mode.authorizationType === AuthorizationType.IAM).length > 1) { throw new ValidationError('You can\'t duplicate IAM configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html', this); } } /** * Add schema dependency to a given construct * * @param construct the dependee */ @MethodMetadata() public addSchemaDependency(construct: CfnResource): boolean { if (this.schemaResource) { construct.addDependency(this.schemaResource); } return true; } /** * Add an environment variable to the construct. */ @MethodMetadata() public addEnvironmentVariable(key: string, value: string) { if (this.definition.sourceApiOptions) { throw new ValidationError('Environment variables are not supported for merged APIs', this); } if (!Token.isUnresolved(key) && !/^[A-Za-z]+\w*$/.test(key)) { throw new ValidationError(`Key '${key}' must begin with a letter and can only contain letters, numbers, and underscores`, this); } if (!Token.isUnresolved(key) && (key.length < 2 || key.length > 64)) { throw new ValidationError(`Key '${key}' must be between 2 and 64 characters long, got ${key.length}`, this); } if (!Token.isUnresolved(value) && value.length > 512) { throw new ValidationError(`Value for '${key}' is too long. Values can be up to 512 characters long, got ${value.length}`, this); } this.environmentVariables[key] = value; } private validateEnvironmentVariables() { const errors: string[] = []; const entries = Object.entries(this.environmentVariables); if (entries.length > 50) { errors.push(`Only 50 environment variables can be set, got ${entries.length}`); } return errors; } private renderEnvironmentVariables() { return Object.entries(this.environmentVariables).length > 0 ? this.environmentVariables : undefined; } private setupLogConfig(config?: LogConfig) { if (!config) return undefined; const logsRoleArn: string = config.role?.roleArn ?? new Role(this, 'ApiLogsRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com'), managedPolicies: [ ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'), ], }).roleArn; const fieldLogLevel: FieldLogLevel = config.fieldLogLevel ?? FieldLogLevel.NONE; return { cloudWatchLogsRoleArn: logsRoleArn, excludeVerboseContent: config.excludeVerboseContent, fieldLogLevel: fieldLogLevel, }; } private setupOpenIdConnectConfig(config?: OpenIdConnectConfig) { if (!config) return undefined; return { authTtl: config.tokenExpiryFromAuth, clientId: config.clientId, iatTtl: config.tokenExpiryFromIssue, issuer: config.oidcProvider, }; } private setupUserPoolConfig(config?: UserPoolConfig) { if (!config) return undefined; return { userPoolId: config.userPool.userPoolId, awsRegion: config.userPool.env.region, appIdClientRegex: config.appIdClientRegex, defaultAction: config.defaultAction || UserPoolDefaultAction.ALLOW, }; } private setupLambdaAuthorizerConfig(config?: LambdaAuthorizerConfig) { if (!config) return undefined; return { authorizerResultTtlInSeconds: config.resultsCacheTtl?.toSeconds(), authorizerUri: config.handler.functionArn, identityValidationExpression: config.validationRegex, }; } private setupAdditionalAuthorizationModes(modes?: AuthorizationMode[]) { if (!modes || modes.length === 0) return undefined; return modes.reduce<CfnGraphQLApi.AdditionalAuthenticationProviderProperty[]>((acc, mode) => [ ...acc, { authenticationType: mode.authorizationType, userPoolConfig: this.setupUserPoolConfig(mode.userPoolConfig), openIdConnectConfig: this.setupOpenIdConnectConfig(mode.openIdConnectConfig), lambdaAuthorizerConfig: this.setupLambdaAuthorizerConfig(mode.lambdaAuthorizerConfig), }, ], []); } private createAPIKey(config?: ApiKeyConfig) { if (config?.expires?.isBefore(Duration.days(1)) || config?.expires?.isAfter(Duration.days(365))) { throw Error('API key expiration must be between 1 and 365 days.'); } const expires = config?.expires ? config?.expires.toEpoch() : undefined; return new CfnApiKey(this, `${config?.name || 'Default'}ApiKey`, { expires, description: config?.description, apiId: this.apiId, }); } /** * The AppSyncDomainName of the associated custom domain */ public get appSyncDomainName(): string { if (!this.domainNameResource) { throw new ValidationError('Cannot retrieve the appSyncDomainName without a domainName configuration', this); } return this.domainNameResource.attrAppSyncDomainName; } }