flex-plugin-builder icon indicating copy to clipboard operation
flex-plugin-builder copied to clipboard

[Bug]: Twilio Functions CORS error

Open oarvindjha opened this issue 3 years ago • 8 comments

Summary

Twilio Function Blocked by CORS policy

Detailed Description

Hi,

I have created flex plugin to send SMS through a twilio function and deployed it, its working fine with my twilio test account

but when i am doing same functionality with my production account i i am getting the below issue

Access to fetch at 'https://linen-prawn-3862.twil.io/create-new-sms' from origin 'https://flex.twilio.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
bundle.js:2 POST https://linen-prawn-3862.twil.io/create-new-sms net::ERR_FAILED
startSMS @ bundle.js:2
onClick @ bundle.js:2
u @ twilio-flex.min.js:1519
h @ twilio-flex.min.js:1519
(anonymous) @ twilio-flex.min.js:1519
E @ twilio-flex.min.js:1519
A @ twilio-flex.min.js:1519
M @ twilio-flex.min.js:1519
O @ twilio-flex.min.js:1519
R @ twilio-flex.min.js:1519
Cn @ twilio-flex.min.js:1519
ma @ twilio-flex.min.js:1519
Ue @ twilio-flex.min.js:1519
An @ twilio-flex.min.js:1519
va @ twilio-flex.min.js:1519
Pn @ twilio-flex.min.js:1519
/agent-desktop/WR399…ad9e890b1e5a6a009:1 Uncaught (in promise) TypeError: Failed to fetch

Below is the code of my function

const axios = require('axios');

exports.handler = async function (context, event, callback) {
  console.log('----in function----');
  const response = new Twilio.Response();
  response.appendHeader('Access-Control-Allow-Origin', '*');
  response.appendHeader('Access-Control-Allow-Methods', 'POST GET PUT OPTIONS');
  response.appendHeader('Content-Type', 'application/json');
  response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
 
  const authed = await validateToken(event.Token, context.ACCOUNT_SID, context.AUTH_TOKEN);
  if (typeof authed !== 'object' || !authed.data || authed.data.valid !== true) {
    console.log('couldn\'t auth', event.Token);
    return callback(null, response);
  }

  console.log('successfully authed', authed.data)

  const client = context.getTwilioClient();
  const to = sanitizeNumber(event.To);
  const from = sanitizeNumber(event.From);

  const channelArgs = {
    FlexFlowSid: context.FLEX_FLOW_SID,
    Identity: event.To,
    Target: event.To,
    ChatUserFriendlyName: event.ToFriendlyName || event.To,
    ChatFriendlyName: event.ChatFriendlyName || `${event.To} chat`,
    PreEngagementData: JSON.stringify({ targetWorker: authed.data.identity })
  };

  const channelResponse = await createChannel(channelArgs, context.ACCOUNT_SID, context.AUTH_TOKEN);

  client.messages.create({
    body: event.Message,
    to: to,
    from: from
  }).then(() => {
    console.log('adding message to', channelResponse.data.sid);
    client.chat.services(context.CHAT_SERVICE_SID)
      .channels(channelResponse.data.sid)
      .messages
      .create({
        body: event.Message,
        from: from
      }).then(() => callback(null, response));
  }).catch(err => {
    console.error(err);
    callback(err);
  });
}

Log output

No response

Version of @twilio/flex-ui

1

Version of flex-plugin-scripts

4.5.0

Version of @twilio-labs/plugin-flex

4.0.0

Version of node

14.17.0

Version of npm

7.14.0

OS

Window

Content of package.json

{
  "name": "plugin-sms",
  "version": "7.0.0",
  "private": true,
  "scripts": {
    "postinstall": "flex-plugin pre-script-check",
    "start": "flex-plugin start"
  },
  "dependencies": {
    "@emotion/react": "^11.1.5",
    "flex-plugin-scripts": "^4.5.0-beta.0",
    "react": "16.5.2",
    "react-dom": "16.5.2"
  },
  "devDependencies": {
    "@twilio/flex-ui": "^1",
    "react-test-renderer": "16.5.2"
  }
}

oarvindjha avatar Jul 12 '21 08:07 oarvindjha

@oarvindjha you are using https://github.com/twilio/twilio-flex-token-validator I presume? First off, you can simply your code a bit by using the functionValidator instead of tokenValidator:

const TokenValidator = require('twilio-flex-token-validator').functionValidator;
exports.handler = TokenValidator(function(context, event, callback) {
    // Your normal Twilio Function goes here.
    // This block will only be called if your token is validated, otherwise it returns a 403.
});

This would get rid of your

const authed = await validateToken(event.Token, context.ACCOUNT_SID, context.AUTH_TOKEN);
if (typeof authed !== 'object' || !authed.data || authed.data.valid !== true) {
    console.log('couldn\'t auth', event.Token);
    return callback(null, response);
}

Secondly, can you change response.appendHeader('Access-Control-Allow-Methods', 'POST GET PUT OPTIONS'); to response.appendHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, OPTIONS'); (i.e. with , separating out the methods).

More information is available on https://www.twilio.com/docs/flex/developer/plugins/call-functions

ktalebian avatar Jul 12 '21 17:07 ktalebian

@ktalebian I'm also having this issue with a new instance of a flex workspace and a new instance of a twilio plugin and still getting the issue.

I even tried calling locals and deployed servers that has access control allowed for all origin and the issue still persists.

Edit: Just noticed this: image Using that content type fixed the issue.

rapito avatar Aug 05 '21 20:08 rapito

Ah, good catch. Sorry I didn't notice that before. Can we close this issue then @rapito?

ktalebian avatar Aug 05 '21 21:08 ktalebian

I didnt open the ticket but I strongly believe that documentation needs to address this more aggressively at the very least.

Maybe flex should throw out warning logs to both browser and terminal if fetch is being used without proper content type.

I understand this is already on the document you listed but I have only read official docs for 3 days straight and Im seeing this just now.

Flex + taskrouter docs are simply huge. 😭

rapito avatar Aug 05 '21 22:08 rapito

Also, If I search for CORS errors anywhere on twilio github or docs, I should be able to find the above caveat. But that's not the case. if anything, you go down a deeper rabbit hole that misleads you even further.

rapito avatar Aug 06 '21 14:08 rapito

@rapito I'll bring this up with our Doc team. I'm also going to investigate maybe we should add a fetch helper method to the flex-plugin repo to make it easier for you to send requests to Twilio Functions.

ktalebian avatar Aug 06 '21 21:08 ktalebian

I think this is back. I'm now getting this error when I'm trying to execute a method from a twilio function from the flex instance.

Access to fetch at 'https://myfunction.twil.io/foo' from origin 'https://flex.twilio.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

even while using the same headers, it just started failing out of nowhere:

This is the code on the client:

        url = 'https://myfunction.twil.io/foo';

        fetch(url, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            method: 'POST',
            body: JSON.stringify(body),
        }).then(() => {
// do something
        }).catch(async (e) => {
// do something else
        });

This is the function code that also accepts the headers already and has cors enabled:

  const response = new Twilio.Response();
  response.appendHeader('Access-Control-Allow-Origin', '*');
  response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS, POST, GET');
  response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
  
  // witchcraft  

  callback(null, response);

rapito avatar Nov 15 '21 14:11 rapito

@rapito your fetch call is misconfigured. You can either do

  • 'Content-Type': 'application/x-www-form-urlencoded', with body: new URLSearchParams(body)

OR

  • 'Content-Type': 'application/json', with body: JSON.stringify(body)

If you mix and match those, it will not work.

ktalebian avatar Nov 15 '21 18:11 ktalebian

localhost CORS error using GET and POST methods with Access-Control-Allow-Origin set to either "*" or "http://localhost:3000" Using fetch or axios, result is the same.

Screen Shot 2022-11-28 at 5 09 24 PM

Seems like the preflight may be interrupting this which is why we end up with "err_failed 200" Screen Shot 2022-11-28 at 5 06 18 PM

Very few if any issues when calling this function from insomnia. Screen Shot 2022-11-28 at 5 15 59 PM Screen Shot 2022-11-28 at 5 16 25 PM

JackCrish avatar Nov 28 '22 23:11 JackCrish

localhost CORS error using GET and POST methods with Access-Control-Allow-Origin set to either "*" or "http://localhost:3000" Using fetch or axios, result is the same.

Screen Shot 2022-11-28 at 5 09 24 PM

Seems like the preflight may be interrupting this which is why we end up with "err_failed 200" Screen Shot 2022-11-28 at 5 06 18 PM

Very few if any issues when calling this function from insomnia. Screen Shot 2022-11-28 at 5 15 59 PM Screen Shot 2022-11-28 at 5 16 25 PM

@JackCrish, were you able to solve this bug? I'm having the same problem... :/

RenanDias12 avatar Apr 20 '23 19:04 RenanDias12

I having the same problem. Nothing fix it.

RoixeR avatar Sep 22 '23 00:09 RoixeR

I've solved the problem with this:

Serverles service:

const response = new Twilio.Response(); //eslint-disable-line no-undef
response.appendHeader("Access-Control-Allow-Origin", "*");
response.appendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.appendHeader("Access-Control-Allow-Headers", "Content-Type");
response.appendHeader("Content-Type", "application/json");

front-end request using axios as http client:

const response = await this.httpClient.request({
        url: "your-url-service",
        method: "post",
        body: JSON.stringify({ body }),
        headers: { "Content-Type": "application/json" },
      });

lucas-felinto avatar Sep 26 '23 17:09 lucas-felinto

I've solved the problem with this:

Serverles service:

const response = new Twilio.Response(); //eslint-disable-line no-undef
response.appendHeader("Access-Control-Allow-Origin", "*");
response.appendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.appendHeader("Access-Control-Allow-Headers", "Content-Type");
response.appendHeader("Content-Type", "application/json");

front-end request using axios as http client:

const response = await this.httpClient.request({
        url: "your-url-service",
        method: "post",
        body: JSON.stringify({ body }),
        headers: { "Content-Type": "application/json" },
      });

So, did you set axios on httpClient from constructor?

RoixeR avatar Sep 26 '23 19:09 RoixeR

I've solved the problem with this: Serverles service:

const response = new Twilio.Response(); //eslint-disable-line no-undef
response.appendHeader("Access-Control-Allow-Origin", "*");
response.appendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.appendHeader("Access-Control-Allow-Headers", "Content-Type");
response.appendHeader("Content-Type", "application/json");

front-end request using axios as http client:

const response = await this.httpClient.request({
        url: "your-url-service",
        method: "post",
        body: JSON.stringify({ body }),
        headers: { "Content-Type": "application/json" },
      });

So, did you set axios on httpClient from constructor?

yeap, exactly!

lucas-felinto avatar Sep 26 '23 20:09 lucas-felinto

Hi,

This is a long open item more than a year now, hence closing it. Feel free to reopen it if still required.

anjha91 avatar Oct 04 '23 08:10 anjha91