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

CORS Issue #545

Closed
allroundexperts opened this issue Jul 15, 2020 · 16 comments
Closed

CORS Issue #545

allroundexperts opened this issue Jul 15, 2020 · 16 comments

Comments

@allroundexperts
Copy link

I don't think you should need to touch gatewayResponses. You should basically just follow https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html and set that up.

Since you're using an eventHandler with the Lambda callback integration, you need to include the Access-Control-Allow-Origin header in your callback. Separately, you'll also want to have an integration route (mock) for OPTIONS that returns the appropriate headers.

How can I achieve that?
It seems that the target requires uri as a required parameter. However, for mock integrations, I can't see any value for that parameter. I also could not find anything on how to setup header mappings for the mock request. Another issue that I am facing is that there is no way to specify multiple methods on a single path (eg POST and OPTIONS for /pet path).

Your help will be greatly appreciated!

Thanks

@leezen
Copy link
Contributor

leezen commented Jul 15, 2020

I published a video for a simple Lambda-based way of setting up CORS: https://www.youtube.com/watch?v=typ-AJQGKKI

I'll try to spend next week's episode on setting up the mock integration style that doesn't use a Lambda on the OPTIONS method.

@allroundexperts
Copy link
Author

Would be great if you could give a hint. I'm sort of blocked right now. Using a lambda seems to be an overkill for this.

@allroundexperts
Copy link
Author

Using gatewayResponse parameters doesn't seem like a good solution as well. I don't want to emit a 200 status for real errors just to allow cater CORS.

@leezen
Copy link
Contributor

leezen commented Jul 15, 2020

Take a look at https://www.pulumi.com/docs/reference/pkg/aws/apigateway/integrationresponse/ -- you'll want to setup a mock integration response that returns the appropriate headers

@leezen
Copy link
Contributor

leezen commented Jul 15, 2020

@allroundexperts
Copy link
Author

Okay, can I use these with the awsx package?

@leezen
Copy link
Contributor

leezen commented Jul 15, 2020

Yes, these should hang off the RestApi exposed by awsx.apigateway.API: https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/apigateway/#API-restAPI

@allroundexperts
Copy link
Author

That does it for me. Thanks a lot @leezen!

@allroundexperts
Copy link
Author

For anyone looking for a solution, here is what I did as hinted by @leezen.

// alias is the stack env. In my case, it's called dev.

type Endpoint = {
  path: string;
  method: "POST" | "GET" | "PUT";
  handler: lambda.Function;
  auth?: UserPool;
};

const addCors = (endpoints: Endpoint[], { restAPI }: awsx.apigateway.API) => {
  const { id } = restAPI;

  endpoints.map(async ({ path }) => {
    const name = path.replace("{", "").replace("}", "").replace("/", "-");

    const resource = id.apply((resolvedId) =>
      apigateway.getResource({
        path: `/${path}`,
        restApiId: resolvedId,
      }),
    );

    const method = new apigateway.Method(`api-method-${name}`, {
      authorization: "NONE",
      httpMethod: "OPTIONS",
      resourceId: resource.id,
      restApi: id,
    });

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

    const response200 = new apigateway.MethodResponse(
      `api-response-200-${name}`,
      {
        httpMethod: method.httpMethod,
        resourceId: resource.id,
        restApi: id,
        statusCode: "200",
        responseParameters: {
          "method.response.header.Access-Control-Allow-Origin": true,
          "method.response.header.Access-Control-Allow-Methods": true,
          "method.response.header.Access-Control-Allow-Headers": true,
        },
      },
      { dependsOn: integration },
    );

    new apigateway.IntegrationResponse(
      `api-integration-response-${name}`,
      {
        httpMethod: method.httpMethod,
        resourceId: resource.id,
        responseTemplates: { "application/json": `{}` },
        responseParameters: {
          "method.response.header.Access-Control-Allow-Origin": "'*'",
          "method.response.header.Access-Control-Allow-Methods": "'*'",
          "method.response.header.Access-Control-Allow-Headers": "'*'",
        },
        restApi: id,
        statusCode: response200.statusCode,
      },
      { dependsOn: response200 },
    );
  });
};

export const createApiEndpoints = (endpoints: Endpoint[]) => {
  const api = new awsx.apigateway.API(NAME, {
    stageName: STAGE,
    restApiArgs: {
      binaryMediaTypes: [],
    },
    routes: endpoints.map(({ path, method, handler, auth }) => ({
      path,
      method,
      eventHandler: handler,
      ...(auth && {
        authorizers: [
          apigatewayX.getCognitoAuthorizer({
            providerARNs: [auth],
          }),
        ],
      }),
    })),
  });

  if (alias === "dev") addCors(endpoints, api);

  return api;
};

@geekyme
Copy link

geekyme commented Feb 4, 2021

Thanks for your solution @allroundexperts . I was able to apply the methods to my existing API endpoints.

One problem though - if i have catch all endpoints like /{proxy+}, the OPTIONS appears to be overriden. The catch all endpoints is created by the cross walk module automatically as shown in this issue.

Screenshot 2021-02-04 at 3 35 33 PM

@oscarmeanwell
Copy link

oscarmeanwell commented Jul 15, 2021

I added this to my API routes to solve the issue. /{proxy+} means all endpoints

 {
    path: '/{proxy+}',
    method: 'OPTIONS',
    eventHandler: async () => {
      return {
        body: '',
        statusCode: 200,
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Credentials': 'true',
          'Access-Control-Allow-Methods':
          'GET, POST, OPTIONS, PUT, PATCH, DELETE',
          'Access-Control-Allow-Headers':
          'Origin, X-Requested-With, Content-Type, Accept, Authorization',
       },
      }
    },
},

@geekyme
Copy link

geekyme commented Nov 29, 2021

In addition to what @oscarmeanwell mentioned, ensure that your endpoint also returns the same access control headers otherwise it won't work. For example like this:

const api = new awsx.apigateway.API("api", {
    routes: [
        {
            path: "/testcors",
            method: "GET",
            eventHandler: async (event) => {
                // This code runs in an AWS Lambda anytime `/` is hit.
                return {
                    statusCode: 200,
                    body: "Hello, API Gateway!",
                    headers: {
                      "Access-Control-Allow-Origin": "*",
                      "Access-Control-Allow-Credentials": "true",
                      "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, PATCH, DELETE",
                      "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
                    },
                };
            },
        },
      {
        path: "/{proxy+}",
        method: "OPTIONS",
        eventHandler: async () => {
          return {
            body: "",
            statusCode: 200,
            headers: {
              "Access-Control-Allow-Origin": "*",
              "Access-Control-Allow-Credentials": "true",
              "Access-Control-Allow-Methods":
                "GET, POST, OPTIONS, PUT, PATCH, DELETE",
              "Access-Control-Allow-Headers":
                "Origin, X-Requested-With, Content-Type, Accept, Authorization",
            },
          }
        },
      },
    ],
  })

@djsd123
Copy link

djsd123 commented Mar 20, 2022

Followed the advice of both @leezen #545 (comment) and @allroundexperts #545 (comment). So many thanks 🙏,
especially to @allroundexperts for providing an example. Examples provide context and sometimes context is everything.

I consider myself more a sys-admin and am not nearly as competent a dev as @allroundexperts so structured mine slightly differently. Apologies in advance for any mistakes. Happy to respond to any queries.

Rest API

Create the rest api using awsx.apigateway.API

const restApi = new awsx.apigateway.API(`chime-rest-api`, {
      stageName: 'Prod',

      routes: [
        {
          path: '/join',
          method: 'POST',
          authorizers: authoriseFunction,
          eventHandler: eventJoinFunction,
        },
        {
          path: '/getattendee',
          method: 'POST',
          authorizers: authoriseFunction,
          eventHandler: eventFunctionGetattendee,
        },
      ],
    });

Config to configure CORS

I chose to define a class to create the MOCK endpoint that returns CORS headers. Credit to @allroundexperts

import * as pulumi from '@pulumi/pulumi';
import { apigateway } from '@pulumi/aws';

///// Creates a MOCK Endpoint to return CORS headers /////

type CORSArgs = {
  restApiId: string;
  resourceId: string;
};

export class CORS extends pulumi.ComponentResource {
  constructor(
      name: string,
      args: CORSArgs,
      opts: pulumi.ComponentResourceOptions = {}
  ) {
    super('pkg:gdsgroup:components:MockEndpoint', name, args, opts);

    const method = new apigateway.Method(
        `api-method-${args.resourceId}`,
        {
          authorization: 'NONE',
          httpMethod: 'OPTIONS',
          resourceId: args.resourceId,
          restApi: args.restApiId,
        },
        { parent: this }
    );

    const integration = new apigateway.Integration(
        `api-integration-${args.resourceId}`,
        {
          httpMethod: method.httpMethod,
          resourceId: args.resourceId,
          restApi: args.restApiId,
          type: 'MOCK',
          requestTemplates: {
            'application/json': `{ statusCode: 200 }`,
          },
        },
        { dependsOn: method, parent: this }
    );

    const response200 = new apigateway.MethodResponse(
        `api-response-200-${args.resourceId}`,
        {
          httpMethod: method.httpMethod,
          resourceId: args.resourceId,
          restApi: args.restApiId,
          statusCode: '200',
          responseParameters: {
            'method.response.header.Access-Control-Allow-Origin': true,
            'method.response.header.Access-Control-Allow-Methods': true,
            'method.response.header.Access-Control-Allow-Headers': true,
          },
        },
        { dependsOn: integration, parent: this }
    );

    new apigateway.IntegrationResponse(
        `api-integration-response-${args.resourceId}`,
        {
          httpMethod: method.httpMethod,
          resourceId: args.resourceId,
          responseTemplates: { 'application/json': `{}` },
          responseParameters: {
            'method.response.header.Access-Control-Allow-Origin': "'*'",
            'method.response.header.Access-Control-Allow-Methods':
                "'OPTIONS,POST'",
            'method.response.header.Access-Control-Allow-Headers':
                "'Content-Type,Authorization'",
          },
          restApi: args.restApiId,
          statusCode: response200.statusCode,
        },
        { dependsOn: response200, parent: this }
    );
  }
}

Note that awsx.apigateway.API only returns the resource Id for the root path / so you will need to call getResource to get the resource Id of the resources/paths that you created

i.e.

apigateway.getResource({
    restApiId: restApi.id,
    path: '/join',
})

Use the CORS class to handle CORS on the rest API

Now as @leezen suggested. Hang the MOCK endpoint off the resources created in the above rest api.

    ['join', 'getattendee'].forEach((pathPart) => {
      restApi.restAPI.id.apply((id) => {
        apigateway
          .getResource({
            restApiId: id,
            path: `/${pathPart}`,
          })
          .then((resource) => {
            new CORS(pathPart, {
              restApiId: resource.restApiId,
              resourceId: resource.id,
            });
          });
      });
    });

You should now end with Resources in your api that look like below. Similar to something created with SAM

Screenshot 2022-03-20 at 14 55 19

@jamest-pin
Copy link

I wonder if anyone could point me in any direction to get this working in yaml.

@djsd123
Copy link

djsd123 commented Nov 29, 2023

Good luck with that. I found myself here because Pulumi didn't have much in the way of working examples in their main language TypeScript. Please post if you find anything.

@jamest-pin
Copy link

jamest-pin commented Dec 1, 2023

Good luck with that. I found myself here because Pulumi didn't have much in the way of working examples in their main language TypeScript. Please post if you find anything.

After spending two days on it I gave up and am going to implement the api gateway manually with the apigatewayv2 provider instead of trying to use Crosswalk (awsx).

Something like this https://www.pulumi.com/ai/conversations/a8a10d57-8764-4586-9bdb-3ba2db15fbcd?prompt=Create+an+aws-apigateway.RestAPI+resource

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants