-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add CORS headers for swagger/openapi endpoints so that the spec can be rendered with petstore.swagger.io #570
Conversation
42c0172
to
52d65a4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am afraid this pull request makes very little sense to me.
Even if we decide to include GET /explorer
endpoint on LoopBack apps, then I think this should be contributed by an "explorer" extension, it should not be built into core/rest layer.
Maybe I misunderstood your intentions, what am I missing?
packages/core/src/application.ts
Outdated
@@ -147,6 +147,12 @@ export class Application extends Context { | |||
const options = OPENAPI_SPEC_MAPPING[request.url]; | |||
return this._serveOpenApiSpec(request, response, options); | |||
} | |||
|
|||
if (request.method === 'GET' && request.url === '/explorer') { | |||
response.setHeader('Access-Control-Allow-Origin', '*'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we need to set Access-Control headers inside _serveOpenApiSpec
, not here. GET /explorer
would be invoked from the browser as a regular request for an HTML page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the purpose of the story was to allow CORS for all endpoints defined by an application's OpenAPI spec, so that users can test them out using the SwaggerUI instance. With that in mind, I think it should still be set in this method, otherwise it will only be allowed for the /swagger.{json,yaml}
or /openapi.{json,yaml}
endpoints. Two things I'm not sure of though are if there are any security implications by doing so, and if we should allow it to be set by our application developers.
packages/core/src/application.ts
Outdated
|
||
if (request.method === 'GET' && request.url === '/explorer') { | ||
response.setHeader('Access-Control-Allow-Origin', '*'); | ||
response.setHeader('Location', 'http://editor.swagger.io'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In LoopBack 3.x, /explorer
was opening SwaggerUI, not the editor.
This is the live SwaggerUI instance that people can use to get Explorer-like view of their API: http://petstore.swagger.io/ |
@bajtos Thank you. I was thinking we could use |
packages/core/src/application.ts
Outdated
if (options.version === '3.0.0') { | ||
specObj = await swagger2openapi.convertObj(specObj, {direct: true}); | ||
appUrl.replace('swagger', 'openapi'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think replace
will change appUrl
at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I will fix it pending on discussion of #570 (review).
packages/core/src/application.ts
Outdated
response.setHeader('content-type', 'application/json; charset=utf-8'); | ||
response.statusCode = 308; | ||
response.setHeader('Location', 'http://petstore.swagger.io/?url=' | ||
+ appUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. We should not deal with redirect here for the swagger/openapi endpoint. Our responsibility is to serve specs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I misunderstood the story, but I thought we should do it as part of the following acceptance criteria:
Pass generated swagger.json to editor.swagger.io so that users can leverage the existing editor tools on their local application.
@kjdelisle thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my understanding, too. @raymondfeng How do we pass the information along to the server at editor.swagger.io
without modifying the request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two separate concerns:
- Have endpoints from LB to serve openapi/swagger specs
- A UI to render the spec as api explorer
Most UI such as editor.swagger.io
has an option to import URL or set the location. If we want to use redirect to editor.swagger.io
, the path should not be /swagger.json
. At lease we need to have something like /swagger.json?ui=true
. It should NOT be replacing 1 at all.
For the GA version, we'll have /explorer
to serve the UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay it makes sense to me now that we need to preserve the /{openapi,swagger}.{json,yaml}
endpoints. I will take a crack at implementing /swagger.json?ui=true
.
packages/core/src/application.ts
Outdated
@@ -234,16 +237,28 @@ export class Application extends Context { | |||
) { | |||
options = options || {version: '2.0', format: 'json'}; | |||
let specObj = this.getApiSpec(); | |||
let appPort = await this.get('http.port'); | |||
// TODO make this more robust i.e. get host, protocol, etc. dynamically | |||
let appUrl = 'http://127.0.0.1:' + appPort + '/swagger'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hostname and port should be default the Host
header of the http request. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this will not work in production(like) environments, where the app is running behind a load balancer (nginx, Amalgam8, etc.). The Host
header of the http request will be showing the internal hostname not accessible from outside. We could use X-Forwarded-*
family of headers to determine the real public host, but I think it's not worth the effort.
Sorry for the confusion! As I understand this CORS related story, we need to add CORS headers to |
ab9d2ff
to
4d7486b
Compare
@bajtos Thank you for clarifying. Can you PTAL at the latest changes? (cc @raymondfeng @kjdelisle) If we expect the users to manually paste the |
packages/core/src/application.ts
Outdated
@@ -145,7 +148,7 @@ export class Application extends Context { | |||
// content-negotiation to support XML clients, I don't want the OpenAPI | |||
// spec to be converted into an XML response. | |||
const options = OPENAPI_SPEC_MAPPING[request.url]; | |||
return this._serveOpenApiSpec(request, response, options); | |||
return this._serveOpenApiSpec(request, response, options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intentional? The indentation seems to be wrong.
b89f87f
to
14ff8e2
Compare
@@ -135,6 +135,9 @@ export class Application extends Context { | |||
request: ServerRequest, | |||
response: ServerResponse, | |||
) { | |||
// allow CORS support for all endpoints so that users | |||
// can test with online SwaggerUI instance | |||
response.setHeader('Access-Control-Allow-Origin', '*'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I missed it earlier, but is there a reason why we need the header to be *
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the header needs to be set to *
, then LGTM, but I imagine that it should be a bit more restrictive than that, yeah?
@@ -135,6 +135,9 @@ export class Application extends Context { | |||
request: ServerRequest, | |||
response: ServerResponse, | |||
) { | |||
// allow CORS support for all endpoints so that users | |||
// can test with online SwaggerUI instance | |||
response.setHeader('Access-Control-Allow-Origin', '*'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get we need to set the Header for all endpoints for Swagger but I'm not sure if users would want that enabled in production, especially with the *
. Can this be configurable / toggleable via options (ideally ENV variable)? / Can we look into (perhaps as a separate issue) look into turning this into a configurable component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should follow the defaults described by http://loopback.io/doc/en/lb3/middleware.json.html#cors-settings.
Access-Control-Allow-Origin: <Origin request header>
Access-Control-Allow-Credentials: true
Access-Control-Allow-Max-Age: 86400
Please note CORS is only enforced by browsers and it's not a security concern for the server itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
14ff8e2
to
f0c5034
Compare
056da52
to
4882e5a
Compare
packages/core/src/application.ts
Outdated
@@ -135,6 +135,14 @@ export class Application extends Context { | |||
request: ServerRequest, | |||
response: ServerResponse, | |||
) { | |||
// allow CORS support for all endpoints so that users | |||
// can test with online SwaggerUI instance | |||
if (process.env.ENABLE_CORS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 for depending on environment variables.
If you want a feature flag to controller this behaviour, then use app-level configuration, e.g. app.getSync('allow-cors-for-api-spec')
here. Then it's up to the applications (or loopback-boot) to wire this binding to the value of process.env.ENABLE_CORS
.
IMO, CORS should be always enabled for these endpoints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bajtos Thank you for the insight. I think that can be done later, and I am fine for leaving them always enabled for now as well.
packages/core/src/application.ts
Outdated
@@ -147,6 +155,10 @@ export class Application extends Context { | |||
const options = OPENAPI_SPEC_MAPPING[request.url]; | |||
return this._serveOpenApiSpec(request, response, options); | |||
} | |||
if (request.method === 'GET' && request.url && | |||
request.url.match(/\/swagger.json\?ui=true/)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it weird that a query parameter is changing the response so dramatically. Can we use a plain url path like /explorer
or /swagger-ui
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense to me. I will change it to /swagger-ui
.
packages/core/src/application.ts
Outdated
) { | ||
response.statusCode = 308; | ||
response.setHeader('Location', 'http://petstore.swagger.io/?url=' | ||
+ 'http://' + request.headers.host + '/swagger.json'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http://loopback.io/doc/en/contrib/style-guide.html#one-argument-per-line or format the code using Prettier.
ccb9e6d
to
a9d52ff
Compare
a9d52ff
to
79f8fbc
Compare
packages/core/src/application.ts
Outdated
// can test with online SwaggerUI instance | ||
response.setHeader('Access-Control-Allow-Origin', '*'); | ||
response.setHeader('Access-Control-Allow-Credentials', 'true'); | ||
response.setHeader('Access-Control-Allow-Max-Age', '86400'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indentation of code does not match indentation of comments, PTAL.
packages/core/src/application.ts
Outdated
@@ -147,6 +153,10 @@ export class Application extends Context { | |||
const options = OPENAPI_SPEC_MAPPING[request.url]; | |||
return this._serveOpenApiSpec(request, response, options); | |||
} | |||
if (request.method === 'GET' && request.url && | |||
request.url === '/swagger-ui') { | |||
return this._renderOpenApiSpec(request, response); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: perhaps _redirectToSwaggerUI
would be a better name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think that's a more descriptive name. 👍
ebf68f7
to
b134edb
Compare
Add /swagger-ui path which would redirect users to the online SwaggerUI instance with the app's OpenAPI spec loaded and enable CORS so they could access their endpoints from it.
b134edb
to
e80afe6
Compare
Add
/swagger-ui
which would redirect users to the online SwaggerUI instance with the app's OpenAPI spec loaded and CORS enabled so they could access their endpoints from it.Description
Related issues
Checklist
guide