bug-hunters / oas3-tools

A Node.js and browser module that provides tooling around Swagger.
MIT License
27 stars 50 forks source link

How do we handle cors? #19

Open ctippur opened 4 years ago

ctippur commented 4 years ago

Hello, I am struggling to add CORS support to my app running on swagger server. I tried using swagger-express-middleware but that dint do the trick. Looks like they are not yet supporting openapi 3.0 specs.

Need some pointers please.

I have a middleware code as shown in the example:

'use strict';

var path = require('path');
var http = require('http');

var oas3Tools = require('oas3-tools');
var serverPort = 8080;
const createMiddleware = require('@apidevtools/swagger-express-middleware');

// swaggerRouter configuration
var options = {
    controllers: path.join(__dirname, './controllers')
};

var expressAppConfig = oas3Tools.expressAppConfig(path.join(__dirname, 'api/openapi.yaml'), options);
expressAppConfig.addValidator();
var app = expressAppConfig.getApp();

createMiddleware( __dirname + '/api/openapi.yaml', app, function(err, middleware) {
    app.use(
        middleware.metadata(),
        middleware.CORS(),
        middleware.files(),
        middleware.parseRequest(),
        middleware.validateRequest(),
        middleware.mock()
    );

    // Show the CORS headers as HTML
    app.use(function(req, res, next) {
        res.send('<pre>' + util.inspect(res._headers) + '</pre>');
    });

    app.listen(8080, function() {
        console.log('Go to http://localhost:8080/docs');
    });
});

My curl call is resulting in a 405 error.

 curl -I -v http://localhost:8080/v2/url/my_test_alias
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> HEAD /v2/url/ali_test_alias HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 405 Method Not Allowed
HTTP/1.1 405 Method Not Allowed
< X-Powered-By: Express
X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
Content-Type: application/json; charset=utf-8
< Content-Length: 118
Content-Length: 118
< ETag: W/"76-HftMGD7rVmxkEyOoP10+GyQtLQY"
ETag: W/"76-HftMGD7rVmxkEyOoP10+GyQtLQY"
< Date: Sat, 28 Mar 2020 00:52:39 GMT
Date: Sat, 28 Mar 2020 00:52:39 GMT
< Connection: keep-alive
Connection: keep-alive

< 

I am sure I am doing something wrong. Appreciate any pointers.

ctippur commented 4 years ago

Any takers on this pls?

inap-bannai commented 4 years ago
var cors = require("cors");
...
var app = expressAppConfig.getApp();
app.use(cors());

Hope this would be helpful.

thomasgainant commented 3 years ago

Hello,

The problem comes from the fact that this plugin configure the app in a bad way. Just to deactivate a type of logs, I had to create a pull request with a simple parameter which deactivate logging.

Your problem comes from the configuration of the app in the ExpressAppConfig of the module:

class ExpressAppConfig { constructor(definitionPath, appOptions) { this.definitionPath = definitionPath; this.routingOptions = appOptions.routing; this.setOpenApiValidatorOptions(definitionPath, appOptions); this.app = express(); const spec = fs.readFileSync(definitionPath, 'utf8'); const swaggerDoc = jsyaml.safeLoad(spec); this.app.use(bodyParser.urlencoded()); this.app.use(bodyParser.text()); this.app.use(bodyParser.json()); this.app.use(this.configureLogger(appOptions.logging)); this.app.use(express.json()); this.app.use(express.urlencoded({ extended: false })); this.app.use(cookieParser()); const swaggerUi = new swagger_ui_1.SwaggerUI(swaggerDoc, appOptions.swaggerUI); this.app.use(swaggerUi.serveStaticContent()); this.app.use(OpenApiValidator.middleware(this.openApiValidatorOptions)); this.app.use(new swagger_parameters_1.SwaggerParameters().checkParameters()); this.app.use(new swagger_router_1.SwaggerRouter().initialize(this.routingOptions)); this.app.use(this.errorHandler); }

Here you can see that the whole stack of middleware is injected ("app.use") in the app before defining the routes from the open api contract. The problem is that it is really hard to modify the Express middleware stack once you have set the routes.

Using app.use(cors()); after you received your app from expressAppConfig, like suggested above without testing it, will be simply ignored because the middleware layers added before by the app configurator will fill some unknown logic before getting to the one added by the cors middleware (probably some header "Access-Control-Allow-Origin" related logic).

So, if you modify the module and simply add:

this.app.use(express.urlencoded({ extended: false })); this.app.use(cookieParser()); **this.app.use(cors());** const swaggerUi = new swagger_ui_1.SwaggerUI(swaggerDoc, appOptions.swaggerUI); this.app.use(swaggerUi.serveStaticContent());

You can now use CORS without problems.

In the future, this should be included by default in the module as an option.

HugoMario commented 3 years ago

thanks a lot @thomasgainant for your comment. I'll keep this in mind for future changes.

IselaMorquecho commented 3 years ago

I think that the same happens with errorHandler, I haven't override default error format

bognari commented 3 years ago

my solution for this problem was this:


const openApiApp = expressAppConfig.getApp();

const app = express();

// Add headers
app.use(/.*/, cors());

for (let i = 2; i < openApiApp._router.stack.length; i++) {
    app._router.stack.push(openApiApp._router.stack[i])
}
slavomir-sidor commented 3 years ago

Hi,

i just fixed it in my fork https://github.com/slavomir-sidor/oas3-tools

you can use it in your index.js

'use strict';

var path = require('path'); var http = require('http');

var oas3Tools = require('oas3-tools'); var serverPort = 8080; app=express() // swaggerRouter configuration var options = { routing: { controllers: path.join(__dirname, './controllers') }, cors:{ "optionsSuccessStatus": 200, "credentials": true, "origin": ["http://localhost:4200"], "methods": "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS,CONNECT,TRACE", "exposedHeaders": true, "preflightContinue": false, } };

var expressAppConfig = oas3Tools.expressAppConfig(path.join(__dirname, 'api/openapi.yaml'), options); var app = expressAppConfig.getApp();

// Initialize the Swagger middleware http.createServer(app).listen(serverPort, function () { console.log('Your server is listening on port %d (http://localhost:%d)', serverPort, serverPort); console.log('Swagger-ui is available on http://localhost:%d/docs', serverPort); });

brnprasad commented 2 years ago

Hi Slavomir, Thanks for the updates. In our project we want to use two Swagger definitions, one for the customer and one for internal testing purpose. I want to know how to integrate that in the same project. When we create server side node js code (which consists of the apis to be given to the customer), we get the directory structure as follows: app | openapi.yaml controllers | Default.js service | DefaultService.js utils | writer.js index.js In this directory structure, we edit the index.js and add the code similar what you have explained above.

Now If i create another server side nodejs code (for internal) the same directory structure repeats. Now My question is how to add the code in the secondly generated nodejs code index.js. We may need to use different server port may be 8081. Can you please give your suggestions how to handle this.

matteoxplo commented 2 years ago

my solution for this problem was this:

const openApiApp = expressAppConfig.getApp();

const app = express();

// Add headers
app.use(/.*/, cors());

for (let i = 2; i < openApiApp._router.stack.length; i++) {
    app._router.stack.push(openApiApp._router.stack[i])
}

I've found that when you add a middleware to express with app.use it simply add it to the end of the stack (here you can see the app.use function). The problem with oas3-tools is in the line 50 of express.app.config.js, and happens because middlewares are executed in the same order of the stack array therefore every middleware after that will be "skipped".

I've wrote a workaroudn that firstly add the middleware to the stack, then move the "SwaggerRouter" and the "ErrorHandler" at the end of the stack (after the new middleware)

function insertMiddleware(app, middleware) {
    const stackLength = app._router.stack.length;
    app.use(middleware);
    app._router.stack.push(...app._router.stack.splice(stackLength - 2, 2));
}

insertMiddleware(app, cors());

I'm not an expert therefore i'm not sure that works every time, but i've tried with cors and it works.

P.S. i've noticed that with the newer version the problem should be fixed, but it is not released in npm

fgarciadevelop commented 1 year ago

I still facing with this problem.

I can solve it on local with the solutions proposed, but I can't fix it on my remote server. I deploy the API server with docker-compose and I can't make this patch on the docker image.

Has someone a solution for this?

inoyakaigor commented 8 months ago

For one's who looking for easy solution: go to generated code utils/writer.js and modify resposne.writeHead code:

response.writeHead(code, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS,CONNECT,TRACE"
  });
Naybs808 commented 7 months ago

Have tried all the suggested solutions in this thread in my code and still running into the same issue. Anyone have an updated suggestion?

Edit:

Was a bit put off editing the source code as it seems a bit extreme, most other server implementations I've used have an option you can set that will work for this kind of thing.

Making the change to the source of the oas3-tools source as suggested by @thomasgainant resolved the issue for me. Thank you.

bouchaala-sabri commented 6 months ago

This is how I made it

const app = expressAppConfig.getApp();

app.use(cors());

// Get a reference to the router stack
let stack = app._router.stack;

// Find the indices of corsMiddleware and expressInit
let corsIndex = stack.findIndex(layer => layer.name === 'corsMiddleware');
let expressInitIndex = stack.findIndex(layer => layer.name === 'expressInit');

// Check if both middleware are in the stack
if (corsIndex !== -1 && expressInitIndex !== -1) {
    // Remove corsMiddleware from its current position
    let corsMiddleware = stack.splice(corsIndex, 1)[0];

    // If cors was after expressInit in the stack, decrement expressInitIndex
    if (corsIndex > expressInitIndex) {
        expressInitIndex--;
    }

    // Insert corsMiddleware before expressInit
    stack.splice(expressInitIndex, 0, corsMiddleware);
}
igoriando commented 1 day ago

Thanks @bouchaala-sabri , this is elegant workaround and it works

igoriando commented 1 day ago

For one's who looking for easy solution: go to generated code utils/writer.js and modify resposne.writeHead code:

response.writeHead(code, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS,CONNECT,TRACE"
  });

This works with simple requests, but for all others it will fail to answer preflight check. For non-simple requests, browser sends OPTIONS and expects 2xx response, Express responds with 405 Method not allowed, unless you properly initialize CORS middleware...