Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
ApiGatewayV1: cors
Browse files Browse the repository at this point in the history
  • Loading branch information
fwang committed Sep 28, 2024
1 parent d7134ec commit 79b2074
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 29 deletions.
23 changes: 13 additions & 10 deletions platform/src/components/aws/apigatewayv1-base-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,19 @@ export function createMethod(
return authArgs.apply(
(authArgs) =>
new apigateway.Method(
`${name}Method`,
{
restApi: output(api).id,
resourceId: resourceId,
httpMethod: method,
authorization: authArgs.authorization,
authorizerId: authArgs.authorizerId,
authorizationScopes: authArgs.authorizationScopes,
},
{ parent },
...transform(
args.transform?.method,
`${name}Method`,
{
restApi: output(api).id,
resourceId: resourceId,
httpMethod: method,
authorization: authArgs.authorization,
authorizerId: authArgs.authorizerId,
authorizationScopes: authArgs.authorizationScopes,
},
{ parent },
),
),
);
}
13 changes: 12 additions & 1 deletion platform/src/components/aws/apigatewayv1-integration-route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ComponentResourceOptions, Input, output } from "@pulumi/pulumi";
import {
ComponentResourceOptions,
Input,
Output,
output,
} from "@pulumi/pulumi";
import { Component, transform } from "../component";
import { ApiGatewayV1IntegrationArgs } from "./apigatewayv1";
import { apigateway } from "@pulumi/aws";
Expand All @@ -25,6 +30,7 @@ export interface Args extends ApiGatewayV1BaseRouteArgs {
* You'll find this component returned by the `routeIntegration` method of the `ApiGatewayV1` component.
*/
export class ApiGatewayV1IntegrationRoute extends Component {
private readonly method: Output<apigateway.Method>;
private readonly integration: apigateway.Integration;

constructor(name: string, args: Args, opts?: ComponentResourceOptions) {
Expand All @@ -36,6 +42,7 @@ export class ApiGatewayV1IntegrationRoute extends Component {
const method = createMethod(name, args, self);
const integration = createIntegration();

this.method = method;
this.integration = integration;

function createIntegration() {
Expand Down Expand Up @@ -72,6 +79,10 @@ export class ApiGatewayV1IntegrationRoute extends Component {
* The API Gateway REST API integration.
*/
integration: this.integration,
/**
* The API Gateway REST API method.
*/
method: this.method,
};
}
}
Expand Down
6 changes: 6 additions & 0 deletions platform/src/components/aws/apigatewayv1-lambda-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface Args extends ApiGatewayV1BaseRouteArgs {
export class ApiGatewayV1LambdaRoute extends Component {
private readonly fn: FunctionBuilder;
private readonly permission: lambda.Permission;
private readonly method: Output<apigateway.Method>;
private readonly integration: apigateway.Integration;

constructor(name: string, args: Args, opts?: ComponentResourceOptions) {
Expand All @@ -53,6 +54,7 @@ export class ApiGatewayV1LambdaRoute extends Component {

this.fn = fn;
this.permission = permission;
this.method = method;
this.integration = integration;

function createFunction() {
Expand Down Expand Up @@ -118,6 +120,10 @@ export class ApiGatewayV1LambdaRoute extends Component {
* The API Gateway REST API integration.
*/
integration: this.integration,
/**
* The API Gateway REST API method.
*/
method: this.method,
};
}
}
Expand Down
165 changes: 154 additions & 11 deletions platform/src/components/aws/apigatewayv1.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ComponentResourceOptions,
Output,
Resource,
all,
interpolate,
jsonStringify,
Expand Down Expand Up @@ -234,6 +235,18 @@ export interface ApiGatewayV1Args {
*/
vpcEndpointIds?: Input<Input<string>[]>;
}>;
/**
* Enable the CORS (Cross-origin resource sharing) settings for your REST API.
* @default `true`
* @example
* Disable CORS.
* ```js
* {
* cors: false
* }
* ```
*/
cors?: Input<boolean>;
/**
* Configure the [API Gateway logs](https://docs.aws.amazon.com/apigateway/latest/developerguide/view-cloudwatch-log-events-in-cloudwatch-console.html) in CloudWatch. By default, access logs are enabled and kept forever.
* @default `{retention: "forever"}`
Expand Down Expand Up @@ -504,6 +517,10 @@ export interface ApiGatewayV1RouteArgs {
* resources.
*/
transform?: {
/**
* Transform the API Gateway REST API method resource.
*/
method?: Transform<apigateway.MethodArgs>;
/**
* Transform the API Gateway REST API integration resource.
*/
Expand Down Expand Up @@ -629,7 +646,6 @@ export class ApiGatewayV1 extends Component implements Link.Linkable {
private apigDomain?: apigateway.DomainName;
private apiMapping?: Output<apigateway.BasePathMapping>;
private region: Output<string>;
private triggers: Record<string, Output<string>> = {};
private resources: Record<string, Output<string>> = {};
private routes: (ApiGatewayV1LambdaRoute | ApiGatewayV1IntegrationRoute)[] =
[];
Expand Down Expand Up @@ -818,12 +834,6 @@ export class ApiGatewayV1 extends Component implements Link.Linkable {
) {
const { method, path } = this.parseRoute(route);
this.createResource(path);
this.triggers[`${method}${path}`] = output(handler).apply((handler) =>
JSON.stringify({
handler: typeof handler === "string" ? handler : handler.handler,
args,
}),
);

const transformed = transform(
this.constructorArgs.transform?.route?.args,
Expand Down Expand Up @@ -890,7 +900,6 @@ export class ApiGatewayV1 extends Component implements Link.Linkable {
) {
const { method, path } = this.parseRoute(route);
this.createResource(path);
this.triggers[`${method}${path}`] = jsonStringify({ integration, args });

const transformed = transform(
this.constructorArgs.transform?.route?.args,
Expand Down Expand Up @@ -980,6 +989,7 @@ export class ApiGatewayV1 extends Component implements Link.Linkable {
},
{ parent: this },
);

this.resources[subPath] = resource.id;
}
}
Expand Down Expand Up @@ -1063,11 +1073,12 @@ export class ApiGatewayV1 extends Component implements Link.Linkable {
const args = this.constructorArgs;
const parent = this;
const api = this.api;
const triggers = this.triggers;
const routes = this.routes;
const endpointType = this.endpointType;
const accessLog = normalizeAccessLog();
const domain = normalizeDomain();
const corsRoutes = createCorsRoutes();
const corsResponses = createCorsResponses();
const deployment = createDeployment();
const logGroup = createLogGroup();
const stage = createStage();
Expand Down Expand Up @@ -1118,16 +1129,148 @@ export class ApiGatewayV1 extends Component implements Link.Linkable {
});
}

function createCorsRoutes() {
const resourceIds = routes.map(
(route) => route.nodes.integration.resourceId,
);

return all([args.cors, resourceIds]).apply(([cors, resourceIds]) => {
if (cors === false) return [];

// filter unique resource ids
const uniqueResourceIds = [...new Set(resourceIds)];

// create cors integrations for the paths
return uniqueResourceIds.map((resourceId) => {
const method = new apigateway.Method(
`${name}CorsMethod${resourceId}`,
{
restApi: api.id,
resourceId,
httpMethod: "OPTIONS",
authorization: "NONE",
},
{ parent },
);

const methodResponse = new apigateway.MethodResponse(
`${name}CorsMethodResponse${resourceId}`,
{
restApi: api.id,
resourceId,
httpMethod: method.httpMethod,
statusCode: "204",
responseParameters: {
"method.response.header.Access-Control-Allow-Headers": true,
"method.response.header.Access-Control-Allow-Methods": true,
"method.response.header.Access-Control-Allow-Origin": true,
},
},
{ parent },
);

const integration = new apigateway.Integration(
`${name}CorsIntegration${resourceId}`,
{
restApi: api.id,
resourceId,
httpMethod: method.httpMethod,
type: "MOCK",
requestTemplates: {
"application/json": "{ statusCode: 200 }",
},
},
{ parent },
);

const integrationResponse = new apigateway.IntegrationResponse(
`${name}CorsIntegrationResponse${resourceId}`,
{
restApi: api.id,
resourceId,
httpMethod: method.httpMethod,
statusCode: methodResponse.statusCode,
responseParameters: {
"method.response.header.Access-Control-Allow-Headers": "'*'",
"method.response.header.Access-Control-Allow-Methods":
"'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'",
"method.response.header.Access-Control-Allow-Origin": "'*'",
},
},
{ parent },
);

return { method, methodResponse, integration, integrationResponse };
});
});
}

function createCorsResponses() {
return output(args.cors).apply((cors) => {
if (cors === false) return [];

return ["4XX", "5XX"].map(
(type) =>
new apigateway.Response(
`${name}Cors${type}Response`,
{
restApiId: api.id,
responseType: `DEFAULT_${type}`,
responseParameters: {
"gatewayresponse.header.Access-Control-Allow-Origin": "'*'",
"gatewayresponse.header.Access-Control-Allow-Headers": "'*'",
},
responseTemplates: {
"application/json":
'{"message":$context.error.messageString}',
},
},
{ parent },
),
);
});
}

function createDeployment() {
const resources = all([corsRoutes, corsResponses]).apply(
([corsRoutes, corsResponses]) =>
[
corsRoutes.map((v) => Object.values(v)),
corsResponses,
routes.map((route) => [
route.nodes.integration,
route.nodes.method,
]),
].flat(3),
);

// filter serializable output values
const resourcesSanitized = all([resources]).apply(([resources]) =>
resources.map((resource) =>
Object.fromEntries(
Object.entries(resource).filter(
([k, v]) => !k.startsWith("_") && typeof v !== "function",
),
),
),
);

return new apigateway.Deployment(
...transform(
args.transform?.deployment,
`${name}Deployment`,
{
restApi: api.id,
triggers,
triggers: all([resourcesSanitized]).apply(([resources]) =>
Object.fromEntries(
resources.map((resource) => [
resource.urn,
JSON.stringify(resource),
]),
),
),
},
{ parent, dependsOn: routes.map((route) => route.nodes.integration) },
{ parent },
),
);
}
Expand Down
8 changes: 1 addition & 7 deletions platform/src/components/aws/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,7 @@ import { buildNode } from "../../runtime/node.js";
import { bootstrap } from "./helpers/bootstrap.js";
import { Duration, DurationMinutes, toSeconds } from "../duration.js";
import { Size, toMBs } from "../size.js";
import {
$print,
Component,
Prettify,
Transform,
transform,
} from "../component.js";
import { Component, Prettify, Transform, transform } from "../component.js";
import { Link } from "../link.js";
import { VisibleError } from "../error.js";
import type { Input } from "../input.js";
Expand Down
3 changes: 3 additions & 0 deletions platform/src/components/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,11 @@ export class Component extends ComponentResource {
"aws:apigateway/deployment:Deployment",
"aws:apigateway/domainName:DomainName",
"aws:apigateway/integration:Integration",
"aws:apigateway/integrationResponse:IntegrationResponse",
"aws:apigateway/method:Method",
"aws:apigateway/methodResponse:MethodResponse",
"aws:apigateway/resource:Resource",
"aws:apigateway/response:Response",
"aws:apigateway/stage:Stage",
"aws:apigatewayv2/apiMapping:ApiMapping",
"aws:apigatewayv2/domainName:DomainName",
Expand Down

0 comments on commit 79b2074

Please sign in to comment.