cdk/lib/user-benefits.ts (151 lines of code) (raw):

import { GuApiGatewayWithLambdaByPath } from '@guardian/cdk'; import type { GuStackProps } from '@guardian/cdk/lib/constructs/core'; import { GuStack } from '@guardian/cdk/lib/constructs/core'; import { GuCname } from '@guardian/cdk/lib/constructs/dns'; import { GuLambdaFunction } from '@guardian/cdk/lib/constructs/lambda'; import type { App } from 'aws-cdk-lib'; import { Duration, Fn } from 'aws-cdk-lib'; import { CfnBasePathMapping, CfnDomainName, UsagePlan, } from 'aws-cdk-lib/aws-apigateway'; import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { CfnRecordSet } from 'aws-cdk-lib/aws-route53'; import { allowedOriginsForStage } from '../../handlers/user-benefits/src/cors'; import { nodeVersion } from './node-version'; export interface UserBenefitsProps extends GuStackProps { stack: string; stage: string; certificateId: string; internalDomainName: string; publicDomainName: string; hostedZoneId: string; supporterProductDataTable: string; } export class UserBenefits extends GuStack { constructor(scope: App, id: string, props: UserBenefitsProps) { super(scope, id, props); const app = 'user-benefits'; const commonEnvironmentVariables = { App: app, Stack: this.stack, Stage: this.stage, }; const supporterProductDataTablePolicy = new PolicyStatement({ actions: ['dynamodb:Query'], resources: [Fn.importValue(props.supporterProductDataTable)], }); const commonLambdaProps = { app, fileName: `${app}.zip`, initialPolicy: [supporterProductDataTablePolicy], runtime: nodeVersion, memorySize: 1024, timeout: Duration.seconds(300), environment: commonEnvironmentVariables, }; const userBenefitsMeLambda = new GuLambdaFunction( this, `user-benefits-me-lambda`, { description: 'An API Gateway triggered lambda to get the benefits of a user identified by a JWT', functionName: `user-benefits-me-${this.stage}`, handler: 'index.benefitsMeHandler', ...commonLambdaProps, }, ); const userBenefitsIdentityIdLambda = new GuLambdaFunction( this, `user-benefits-identity-id-lambda`, { description: 'An API Gateway triggered lambda to get the benefits of the user identified in the request path', functionName: `user-benefits-identity-id-${this.stage}`, handler: 'index.benefitsIdentityIdHandler', ...commonLambdaProps, }, ); const userBenefitsListLambda = new GuLambdaFunction( this, `user-benefits-list-lambda`, { description: 'An API Gateway triggered lambda to return the full list of benefits for each product in html or json format', functionName: `user-benefits-list-${this.stage}`, handler: 'index.benefitsListHandler', ...commonLambdaProps, }, ); const apiGateway = new GuApiGatewayWithLambdaByPath(this, { app, targets: [ { // Auth is handled by the lambda which validates a JWT path: '/benefits/me', httpMethod: 'GET', lambda: userBenefitsMeLambda, }, { path: '/benefits/{identityId+}', httpMethod: 'GET', lambda: userBenefitsIdentityIdLambda, apiKeyRequired: true, }, { path: '/benefits/list', httpMethod: 'GET', lambda: userBenefitsListLambda, }, ], defaultCorsPreflightOptions: { allowHeaders: ['*'], allowMethods: ['GET'], allowOrigins: allowedOriginsForStage(this.stage), }, monitoringConfiguration: { http5xxAlarm: { tolerated5xxPercentage: 5 }, snsTopicName: `alarms-handler-topic-${this.stage}`, }, }); // ---- API Key ---- // const usagePlan = new UsagePlan(this, 'UserBenefitsUsagePlan', { name: `user-benefits-api-usage-plan-${this.stage}`, apiStages: [ { api: apiGateway.api, stage: apiGateway.api.deploymentStage, }, ], }); const apiKey = apiGateway.api.addApiKey(`${app}-api-key-${this.stage}`, { apiKeyName: `${app}-api-key-${this.stage}`, }); usagePlan.addApiKey(apiKey); // ---- DNS ---- // const certificateArn = `arn:aws:acm:eu-west-1:${this.account}:certificate/${props.certificateId}`; const cfnDomainName = new CfnDomainName(this, 'DomainName', { domainName: props.internalDomainName, regionalCertificateArn: certificateArn, endpointConfiguration: { types: ['REGIONAL'], }, }); new CfnBasePathMapping(this, 'BasePathMapping', { domainName: cfnDomainName.ref, restApiId: apiGateway.api.restApiId, stage: apiGateway.api.deploymentStage.stageName, }); new CfnRecordSet(this, 'DNSRecord', { name: props.internalDomainName, type: 'CNAME', hostedZoneId: props.hostedZoneId, ttl: '120', resourceRecords: [cfnDomainName.attrRegionalDomainName], }); new GuCname(this, 'NS1 DNS entry', { app: app, domainName: props.publicDomainName, ttl: Duration.hours(1), resourceRecord: 'guardian.map.fastly.net', }); } }