packages/aws-cdk-lib/aws-appsync/lib/eventapi.ts (425 lines of code) (raw):
import { Construct } from 'constructs';
import { IApi, ApiBase } from './api-base';
import {
AppSyncLogConfig,
AppSyncFieldLogLevel,
AppSyncDomainOptions,
AppSyncEventResource,
} from './appsync-common';
import { CfnApi, CfnApiKey, CfnDomainName, CfnDomainNameApiAssociation } from './appsync.generated';
import {
IAppSyncAuthConfig,
createAPIKey,
AppSyncCognitoConfig,
AppSyncLambdaAuthorizerConfig,
AppSyncOpenIdConnectConfig,
AppSyncAuthorizationType,
AppSyncAuthProvider,
} from './auth-config';
import { ChannelNamespace, ChannelNamespaceOptions } from './channel-namespace';
import {
AppSyncDataSourceOptions,
AppSyncHttpDataSourceOptions,
AppSyncDynamoDbDataSource,
AppSyncHttpDataSource,
AppSyncLambdaDataSource,
AppSyncRdsDataSource,
AppSyncOpenSearchDataSource,
AppSyncEventBridgeDataSource,
} from './data-source-common';
import { ITable } from '../../aws-dynamodb';
import { IEventBus } from '../../aws-events';
import { Grant, IGrantable, ManagedPolicy, ServicePrincipal, Role } from '../../aws-iam';
import { IFunction } from '../../aws-lambda';
import { ILogGroup, LogGroup, LogRetention, RetentionDays } from '../../aws-logs';
import { IDomain } from '../../aws-opensearchservice';
import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds';
import { ISecret } from '../../aws-secretsmanager';
import { Lazy, Names, Stack, Token, ValidationError } from '../../core';
import { addConstructMetadata } from '../../core/lib/metadata-resource';
/**
* Authorization configuration for the Event API
*/
export interface EventApiAuthConfig {
/**
* Auth providers for use in connection,
* publish, and subscribe operations.
* @default - API Key authorization
*/
readonly authProviders?: AppSyncAuthProvider[];
/**
* Connection auth modes
* @default - API Key authorization
*/
readonly connectionAuthModeTypes?: AppSyncAuthorizationType[];
/**
* Default publish auth modes
* @default - API Key authorization
*/
readonly defaultPublishAuthModeTypes?: AppSyncAuthorizationType[];
/**
* Default subscribe auth modes
* @default - API Key authorization
*/
readonly defaultSubscribeAuthModeTypes?: AppSyncAuthorizationType[];
}
/**
* Authorization configuration helper methods
*/
class AppSyncEventApiAuthConfig implements IAppSyncAuthConfig {
/**
* Set up OIDC Authorization configuration for GraphQL APIs and Event APIs
* @param config - the configuration input for OpenID Connect auth mode
* @returns CfnApi.OpenIDConnectConfigProperty | undefined
*/
setupOpenIdConnectConfig(config?: AppSyncOpenIdConnectConfig): CfnApi.OpenIDConnectConfigProperty | undefined {
if (!config) return undefined;
return {
authTtl: config.tokenExpiryFromAuth,
clientId: config.clientId,
iatTtl: config.tokenExpiryFromIssue,
issuer: config.oidcProvider,
};
}
/**
* Set up Cognito Authorization configuration for Event APIs
* @param config - the configuration input for Cognito auth mode
* @returns CfnApi.CognitoConfigProperty | undefined
*/
setupCognitoConfig(config?: AppSyncCognitoConfig): CfnApi.CognitoConfigProperty | undefined {
if (!config) return undefined;
return {
userPoolId: config.userPool.userPoolId,
awsRegion: config.userPool.env.region,
appIdClientRegex: config.appIdClientRegex,
};
}
/**
* Set up Lambda Authorization configuration for GraphQL APIs and Event APIs
* @param config - the configuration input for Lambda auth mode
* @returns CfnApi.LambdaAuthorizerConfigProperty | undefined
*/
setupLambdaAuthorizerConfig(config?: AppSyncLambdaAuthorizerConfig): CfnApi.LambdaAuthorizerConfigProperty | undefined {
if (!config) return undefined;
return {
authorizerResultTtlInSeconds: config.resultsCacheTtl?.toSeconds(),
authorizerUri: config.handler.functionArn,
identityValidationExpression: config.validationRegex,
};
}
}
/**
* Interface for Event API
*/
export interface IEventApi extends IApi {
/**
* The Authorization Types for this Event Api
*/
readonly authProviderTypes: AppSyncAuthorizationType[];
/**
* The domain name of the Api's HTTP endpoint.
*
* @attribute
*/
readonly httpDns: string;
/**
* The domain name of the Api's real-time endpoint.
*
* @attribute
*/
readonly realtimeDns: string;
/**
* add a new channel namespace.
* @param id the id of the channel namespace
* @param options the options for the channel namespace
* @returns the channel namespace
*/
addChannelNamespace(id: string, options?: ChannelNamespaceOptions): ChannelNamespace;
/**
* Add a new DynamoDB data source to this API
*
* @param id The data source's id
* @param table The DynamoDB table backing this data source
* @param options The optional configuration for this data source
*/
addDynamoDbDataSource(id: string, table: ITable, options?: AppSyncDataSourceOptions): AppSyncDynamoDbDataSource;
/**
* add a new http data source to this API
*
* @param id The data source's id
* @param endpoint The http endpoint
* @param options The optional configuration for this data source
*/
addHttpDataSource(id: string, endpoint: string, options?: AppSyncHttpDataSourceOptions): AppSyncHttpDataSource;
/**
* Add an EventBridge data source to this api
* @param id The data source's id
* @param eventBus The EventBridge EventBus on which to put events
* @param options The optional configuration for this data source
*/
addEventBridgeDataSource(id: string, eventBus: IEventBus, options?: AppSyncDataSourceOptions): AppSyncEventBridgeDataSource;
/**
* add a new Lambda data source to this API
*
* @param id The data source's id
* @param lambdaFunction The Lambda function to call to interact with this data source
* @param options The optional configuration for this data source
*/
addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: AppSyncDataSourceOptions): AppSyncLambdaDataSource;
/**
* add a new Rds data source to this API
*
* @param id The data source's id
* @param serverlessCluster The database cluster to interact with this data source
* @param secretStore The secret store that contains the username and password for the database cluster
* @param databaseName The optional name of the database to use within the cluster
* @param options The optional configuration for this data source
*/
addRdsDataSource(
id: string,
serverlessCluster: IServerlessCluster | IDatabaseCluster,
secretStore: ISecret,
databaseName?: string,
options?: AppSyncDataSourceOptions
): AppSyncRdsDataSource;
/**
* Add a new OpenSearch data source to this API
*
* @param id The data source's id
* @param domain The OpenSearch domain for this data source
* @param options The optional configuration for this data source
*/
addOpenSearchDataSource(id: string, domain: IDomain, options?: AppSyncDataSourceOptions): AppSyncOpenSearchDataSource;
/**
* Adds an IAM policy statement associated with this Event API to an IAM
* principal's policy.
*
* @param grantee The principal
* @param resources The set of resources to allow (i.e. ...:[region]:[accountId]:apis/EventApiId/...)
* @param actions The actions that should be granted to the principal (i.e. appsync:EventPublish )
*/
grant(grantee: IGrantable, resources: AppSyncEventResource, ...actions: string[]): Grant;
/**
* Adds an IAM policy statement for EventPublish access to this EventApi to an IAM
* principal's policy.
*
* @param grantee The principal
*/
grantPublish(grantee: IGrantable): Grant;
/**
* Adds an IAM policy statement for EventSubscribe access to this EventApi to an IAM
* principal's policy.
*
* @param grantee The principal
*/
grantSubscribe(grantee: IGrantable): Grant;
/**
* Adds an IAM policy statement to publish and subscribe to this API for an IAM principal's policy.
*
* @param grantee The principal
*/
grantPublishAndSubscribe(grantee: IGrantable): Grant;
/**
* Adds an IAM policy statement for EventConnect access to this EventApi to an IAM principal's policy.
*
* @param grantee The principal
*/
grantConnect(grantee: IGrantable): Grant;
}
/**
* Base Class for Event API
*/
export abstract class EventApiBase extends ApiBase implements IEventApi {
/**
* The domain name of the Api's HTTP endpoint.
*/
public abstract readonly httpDns: string;
/**
* The domain name of the Api's real-time endpoint.
*/
public abstract readonly realtimeDns: string;
/**
* The Authorization Types for this Event Api
*/
public abstract readonly authProviderTypes: AppSyncAuthorizationType[];
/**
* add a new Channel Namespace to this API
*/
public addChannelNamespace(id: string, options?: ChannelNamespaceOptions): ChannelNamespace {
return new ChannelNamespace(this, id, {
api: this,
channelNamespaceName: options?.channelNamespaceName ?? id,
...options,
});
}
/**
* add a new DynamoDB data source to this API
*
* @param id The data source's id
* @param table The DynamoDB table backing this data source
* @param options The optional configuration for this data source
*/
public addDynamoDbDataSource(id: string, table: ITable, options?: AppSyncDataSourceOptions): AppSyncDynamoDbDataSource {
return new AppSyncDynamoDbDataSource(this, id, {
api: this,
table,
name: options?.name,
description: options?.description,
});
}
/**
* add a new http data source to this API
*
* @param id The data source's id
* @param endpoint The http endpoint
* @param options The optional configuration for this data source
*/
public addHttpDataSource(id: string, endpoint: string, options?: AppSyncHttpDataSourceOptions): AppSyncHttpDataSource {
return new AppSyncHttpDataSource(this, id, {
api: this,
endpoint,
name: options?.name,
description: options?.description,
authorizationConfig: options?.authorizationConfig,
});
}
/**
* add a new Lambda data source to this API
*
* @param id The data source's id
* @param lambdaFunction The Lambda function to call to interact with this data source
* @param options The optional configuration for this data source
*/
public addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: AppSyncDataSourceOptions): AppSyncLambdaDataSource {
return new AppSyncLambdaDataSource(this, id, {
api: this,
lambdaFunction,
name: options?.name,
description: options?.description,
});
}
/**
* add a new Rds data source to this API
* @param id The data source's id
* @param serverlessCluster The database cluster to interact with this data source
* @param secretStore The secret store that contains the username and password for the database cluster
* @param databaseName The optional name of the database to use within the cluster
* @param options The optional configuration for this data source
*/
public addRdsDataSource(
id: string,
serverlessCluster: IServerlessCluster | IDatabaseCluster,
secretStore: ISecret,
databaseName?: string,
options?: AppSyncDataSourceOptions,
): AppSyncRdsDataSource {
return new AppSyncRdsDataSource(this, id, {
api: this,
name: options?.name,
description: options?.description,
serverlessCluster,
secretStore,
databaseName,
});
}
/**
* Add an EventBridge data source to this api
* @param id The data source's id
* @param eventBus The EventBridge EventBus on which to put events
* @param options The optional configuration for this data source
*/
addEventBridgeDataSource(id: string, eventBus: IEventBus, options?: AppSyncDataSourceOptions): AppSyncEventBridgeDataSource {
return new AppSyncEventBridgeDataSource(this, id, {
api: this,
eventBus,
name: options?.name,
description: options?.description,
});
}
/**
* add a new OpenSearch data source to this API
*
* @param id The data source's id
* @param domain The OpenSearch domain for this data source
* @param options The optional configuration for this data source
*/
public addOpenSearchDataSource(id: string, domain: IDomain, options?: AppSyncDataSourceOptions): AppSyncOpenSearchDataSource {
return new AppSyncOpenSearchDataSource(this, id, {
api: this,
name: options?.name,
description: options?.description,
domain,
});
}
/**
* Adds an IAM policy statement associated with this Event API to an IAM
* principal's policy.
*
* @param grantee The principal
* @param resources The set of resources to allow (i.e. ...:[region]:[accountId]:apis/EventApiId/...)
* @param actions The actions that should be granted to the principal (i.e. appsync:EventPublish )
*/
public grant(grantee: IGrantable, resources: AppSyncEventResource, ...actions: string[]): Grant {
if (!this.authProviderTypes.includes(AppSyncAuthorizationType.IAM)) {
throw new ValidationError('Cannot use grant method because IAM Authorization mode is missing in the auth providers on this API.',
this,
);
}
return Grant.addToPrincipal({
grantee,
actions,
resourceArns: resources.resourceArns(this),
scope: this,
});
}
/**
* Adds an IAM policy statement for EventPublish access to this EventApi to an IAM
* principal's policy. This grants publish permission for all channels within the API.
*
* @param grantee The principal
*/
public grantPublish(grantee: IGrantable): Grant {
return this.grant(grantee, AppSyncEventResource.allChannelNamespaces(), 'appsync:EventPublish');
}
/**
* Adds an IAM policy statement for EventSubscribe access to this EventApi to an IAM
* principal's policy. This grants subscribe permission for all channels within the API.
*
* @param grantee The principal
*/
public grantSubscribe(grantee: IGrantable): Grant {
return this.grant(grantee, AppSyncEventResource.allChannelNamespaces(), 'appsync:EventSubscribe');
}
/**
* Adds an IAM policy statement to publish and subscribe to this API for an IAM principal's policy.
* This grants publish & subscribe permission for all channels within the API.
*
* @param grantee The principal
*/
public grantPublishAndSubscribe(grantee: IGrantable): Grant {
return this.grant(grantee, AppSyncEventResource.allChannelNamespaces(), 'appsync:EventPublish', 'appsync:EventSubscribe');
}
/**
* Adds an IAM policy statement for EventConnect access to this EventApi to an IAM principal's policy.
*
* @param grantee The principal
*/
public grantConnect(grantee: IGrantable): Grant {
return this.grant(grantee, AppSyncEventResource.forAPI(), 'appsync:EventConnect');
}
}
/**
* Properties for an AppSync Event API
*/
export interface EventApiProps {
/**
* the name of the Event API
*/
readonly apiName: string;
/**
* Optional authorization configuration
*
* @default - API Key authorization
*/
readonly authorizationConfig?: EventApiAuthConfig;
/**
* Logging configuration for this api
*
* @default - None
*/
readonly logConfig?: AppSyncLogConfig;
/**
* 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;
/**
* The domain name configuration for the Event 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?: AppSyncDomainOptions;
}
/**
* Attributes for Event API imports
*/
export interface EventApiAttributes {
/**
* the name of the Event API
* @default - not needed to import API
*/
readonly apiName?: string;
/**
* an unique AWS AppSync Event API identifier
* i.e. 'lxz775lwdrgcndgz3nurvac7oa'
*/
readonly apiId: string;
/**
* the ARN of the Event API
* @default - constructed arn
*/
readonly apiArn?: string;
/**
* the domain name of the Api's HTTP endpoint.
*/
readonly httpDns: string;
/**
* the domain name of the Api's real-time endpoint.
*/
readonly realtimeDns: string;
/**
* The Authorization Types for this Event Api
* @default - none, required to construct event rules from imported APIs
*/
readonly authProviderTypes?: AppSyncAuthorizationType[];
}
/**
* An AppSync Event API
*
* @resource AWS::AppSync::Api
*/
export class EventApi extends EventApiBase {
/**
* Import a Event API through this function
*
* @param scope scope
* @param id id
* @param attrs Event API Attributes of an API
*/
public static fromEventApiAttributes(scope: Construct, id: string, attrs: EventApiAttributes): IEventApi {
const arn =
attrs.apiArn ??
Stack.of(scope).formatArn({
service: 'appsync',
resource: 'apis',
resourceName: attrs.apiId,
});
class Import extends EventApiBase {
public readonly apiId = attrs.apiId;
public readonly apiArn = arn;
public readonly httpDns = attrs.httpDns;
public readonly realtimeDns = attrs.realtimeDns;
public readonly authProviderTypes = attrs.authProviderTypes ?? [];
}
return new Import(scope, id);
}
/**
* an unique AWS AppSync Event API identifier
* i.e. 'lxz775lwdrgcndgz3nurvac7oa'
*/
public readonly apiId: string;
/**
* the ARN of the API
*/
public readonly apiArn: string;
/**
* the domain name of the Api's HTTP endpoint.
*/
public readonly httpDns: string;
/**
* the domain name of the Api's real-time endpoint.
*/
public readonly realtimeDns: string;
/**
* The Authorization Types for this Event Api
*/
public readonly authProviderTypes: AppSyncAuthorizationType[];
/**
* The connection auth modes for this Event Api
*/
public readonly connectionModeTypes: AppSyncAuthorizationType[];
/**
* The default publish auth modes for this Event Api
*/
public readonly defaultPublishModeTypes: AppSyncAuthorizationType[];
/**
* The default subscribe auth modes for this Event Api
*/
public readonly defaultSubscribeModeTypes: AppSyncAuthorizationType[];
/**
* The configured API keys, if present.
* The key of this object is an apiKey name (apiKeyConfig.name) if specified, `Default` otherwise.
*
* @default - no api key
* @attribute ApiKeys
*/
public readonly apiKeys: { [key: string]: CfnApiKey } = {};
/**
* the CloudWatch Log Group for this API
*/
public readonly logGroup: ILogGroup;
private api: CfnApi;
private eventConfig: CfnApi.EventConfigProperty;
private domainNameResource?: CfnDomainName;
constructor(scope: Construct, id: string, props: EventApiProps) {
if (props.apiName !== undefined && !Token.isUnresolved(props.apiName)) {
if (props.apiName.length < 1 || props.apiName.length > 50) {
throw new ValidationError(`\`apiName\` must be between 1 and 50 characters, got: ${props.apiName.length} characters.`, scope);
}
}
super(scope, id, {
physicalName: props.apiName ?? Lazy.string({
produce: () =>
Names.uniqueResourceName(this, {
maxLength: 50,
separator: '-',
}),
}),
});
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
const defaultAuthType: AppSyncAuthorizationType = AppSyncAuthorizationType.API_KEY;
const defaultAuthProviders: AppSyncAuthProvider[] = [{ authorizationType: defaultAuthType }];
const authProviders = props.authorizationConfig?.authProviders ?? defaultAuthProviders;
this.authProviderTypes = this.setupAuthProviderTypes(authProviders);
const connectionAuthModeTypes: AppSyncAuthorizationType[] =
props.authorizationConfig?.connectionAuthModeTypes ?? this.authProviderTypes;
const defaultPublishAuthModeTypes: AppSyncAuthorizationType[] =
props.authorizationConfig?.defaultPublishAuthModeTypes ?? this.authProviderTypes;
const defaultSubscribeAuthModeTypes: AppSyncAuthorizationType[] =
props.authorizationConfig?.defaultSubscribeAuthModeTypes ?? this.authProviderTypes;
this.connectionModeTypes = connectionAuthModeTypes;
this.defaultPublishModeTypes = defaultPublishAuthModeTypes;
this.defaultSubscribeModeTypes = defaultSubscribeAuthModeTypes;
this.validateEventApiConfiguration(props, authProviders);
this.eventConfig = {
authProviders: this.mapAuthorizationProviders(authProviders),
connectionAuthModes: this.mapAuthorizationConfig(connectionAuthModeTypes),
defaultPublishAuthModes: this.mapAuthorizationConfig(defaultPublishAuthModeTypes),
defaultSubscribeAuthModes: this.mapAuthorizationConfig(defaultSubscribeAuthModeTypes),
logConfig: this.setupLogConfig(props.logConfig),
};
this.api = new CfnApi(this, 'Resource', {
name: this.physicalName,
ownerContact: props.ownerContact,
eventConfig: this.eventConfig,
});
this.apiId = this.api.attrApiId;
this.apiArn = this.api.attrApiArn;
this.httpDns = this.api.attrDnsHttp;
this.realtimeDns = this.api.attrDnsRealtime;
const apiKeyConfigs = authProviders.filter((mode) => mode.authorizationType === AppSyncAuthorizationType.API_KEY);
for (const mode of apiKeyConfigs) {
this.apiKeys[mode.apiKeyConfig?.name ?? 'Default'] = createAPIKey(this, this.apiId, mode.apiKeyConfig);
}
if (authProviders.some((mode) => mode.authorizationType === AppSyncAuthorizationType.LAMBDA)) {
const config = authProviders.find((mode: AppSyncAuthProvider) => {
return mode.authorizationType === AppSyncAuthorizationType.LAMBDA && mode.lambdaAuthorizerConfig;
})?.lambdaAuthorizerConfig;
config?.handler.addPermission(`${id}-appsync`, {
principal: new ServicePrincipal('appsync.amazonaws.com'),
action: 'lambda:InvokeFunction',
sourceArn: this.apiArn,
});
}
if (props.domainName) {
this.domainNameResource = new CfnDomainName(this, 'DomainName', {
domainName: props.domainName.domainName,
certificateArn: props.domainName.certificate.certificateArn,
description: `domain for ${props.apiName} Event API`,
});
const domainNameAssociation = new CfnDomainNameApiAssociation(this, 'DomainAssociation', {
domainName: props.domainName.domainName,
apiId: this.apiId,
});
domainNameAssociation.addDependency(this.domainNameResource);
}
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);
}
}
/**
* Validate Event API configuration
*/
private validateEventApiConfiguration(props: EventApiProps, authProviders: AppSyncAuthProvider[]) {
this.validateOwnerContact(props.ownerContact);
this.validateAuthorizationProps(authProviders);
this.validateAuthorizationConfig(authProviders, this.connectionModeTypes);
this.validateAuthorizationConfig(authProviders, this.defaultPublishModeTypes);
this.validateAuthorizationConfig(authProviders, this.defaultSubscribeModeTypes);
}
/**
* Validate ownerContact property
*/
private validateOwnerContact(ownerContact?: string) {
if (ownerContact === undefined || Token.isUnresolved(ownerContact)) return;
if (ownerContact.length < 1 || ownerContact.length > 256) {
throw new ValidationError(`\`ownerContact\` must be between 1 and 256 characters, got: ${ownerContact.length} characters.`, this);
}
const ownerContactPattern = /^[A-Za-z0-9_\-\ \.]+$/;
if (!ownerContactPattern.test(ownerContact)) {
throw new ValidationError(`\`ownerContact\` must contain only alphanumeric characters, underscores, hyphens, spaces, and periods, got: ${ownerContact}`, this);
}
}
private setupLogConfig(config?: AppSyncLogConfig) {
if (!config) return;
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: AppSyncFieldLogLevel = config.fieldLogLevel ?? AppSyncFieldLogLevel.NONE;
return {
cloudWatchLogsRoleArn: logsRoleArn,
logLevel: fieldLogLevel,
};
}
private setupAuthProviderTypes(authProviders?: AppSyncAuthProvider[]) {
if (!authProviders || authProviders.length === 0) return [AppSyncAuthorizationType.API_KEY];
const modes = authProviders.map((mode) => mode.authorizationType);
return modes;
}
private mapAuthorizationProviders(authProviders: AppSyncAuthProvider[]) {
const authConfig: IAppSyncAuthConfig = new AppSyncEventApiAuthConfig();
return authProviders.reduce<CfnApi.AuthProviderProperty[]>((acc, mode) => {
acc.push({
authType: mode.authorizationType,
cognitoConfig: authConfig.setupCognitoConfig(mode.cognitoConfig),
openIdConnectConfig: authConfig.setupOpenIdConnectConfig(mode.openIdConnectConfig),
lambdaAuthorizerConfig: authConfig.setupLambdaAuthorizerConfig(mode.lambdaAuthorizerConfig),
});
return acc;
}, []);
}
private mapAuthorizationConfig(authModes: AppSyncAuthorizationType[]) {
return authModes.map((mode) => ({ authType: mode }));
}
private validateAuthorizationProps(authProviders: AppSyncAuthProvider[]) {
const keyConfigs = authProviders.filter((mode) => mode.authorizationType === AppSyncAuthorizationType.API_KEY);
const someWithNoNames = keyConfigs.some((config) => !config.apiKeyConfig?.name);
if (keyConfigs.length > 1 && someWithNoNames) {
throw new ValidationError('You must specify key names when configuring more than 1 API key.', this);
}
if (authProviders.filter((authProvider) => authProvider.authorizationType === AppSyncAuthorizationType.LAMBDA).length > 1) {
throw new ValidationError(
'You can only have a single AWS Lambda function configured to authorize your API. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html',
this,
);
}
if (authProviders.filter((authProvider) => authProvider.authorizationType === AppSyncAuthorizationType.IAM).length > 1) {
throw new ValidationError("You can't duplicate IAM configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html", this);
}
authProviders.map((authProvider) => {
if (authProvider.authorizationType === AppSyncAuthorizationType.OIDC && !authProvider.openIdConnectConfig) {
throw new ValidationError('OPENID_CONNECT authorization type is specified but OIDC Authorizer Configuration is missing in the AuthProvider', this);
}
if (authProvider.authorizationType === AppSyncAuthorizationType.USER_POOL && !authProvider.cognitoConfig) {
throw new ValidationError('AMAZON_COGNITO_USER_POOLS authorization type is specified but Cognito Authorizer Configuration is missing in the AuthProvider', this);
}
if (authProvider.authorizationType === AppSyncAuthorizationType.LAMBDA && !authProvider.lambdaAuthorizerConfig) {
throw new ValidationError('AWS_LAMBDA authorization type is specified but Lambda Authorizer Configuration is missing in the AuthProvider', this);
}
});
}
private validateAuthorizationConfig(authProviders: AppSyncAuthProvider[], authTypes: AppSyncAuthorizationType[]) {
for (const authType of authTypes) {
if (!authProviders.find((authProvider) => authProvider.authorizationType === authType)) {
throw new ValidationError(`Missing authorization configuration for ${authType}`, this);
}
}
if (authTypes.length === 0) {
throw new ValidationError('Empty AuthModeTypes array is not allowed, if specifying, you must specify a valid mode', this);
}
}
/**
* 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;
}
/**
* The HTTP Endpoint of the associated custom domain
*/
public get customHttpEndpoint(): string {
if (!this.domainNameResource) {
throw new ValidationError('Cannot retrieve the appSyncDomainName without a domainName configuration', this);
}
return `https://${this.domainNameResource.attrDomainName}/event`;
}
/**
* The Realtime Endpoint of the associated custom domain
*/
public get customRealtimeEndpoint(): string {
if (!this.domainNameResource) {
throw new ValidationError('Cannot retrieve the appSyncDomainName without a domainName configuration', this);
}
return `wss://${this.domainNameResource.attrDomainName}/event/realtime`;
}
}