Skip to content

Commit d05bdae

Browse files
committed
fix(rest): enable cors preflight
Fixes #1055
1 parent 80fbcc7 commit d05bdae

File tree

3 files changed

+51
-7
lines changed

3 files changed

+51
-7
lines changed

packages/rest/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@types/http-errors": "^1.6.1",
2929
"@types/node": "^8.5.9",
3030
"body": "^5.1.0",
31+
"cors": "^2.8.4",
3132
"debug": "^3.1.0",
3233
"http-errors": "^1.6.1",
3334
"js-yaml": "^3.9.1",
@@ -39,6 +40,7 @@
3940
"@loopback/openapi-spec-builder": "^0.3.0",
4041
"@loopback/repository": "^0.2.1",
4142
"@loopback/testlab": "^0.3.0",
43+
"@types/cors": "^2.8.3",
4244
"@types/debug": "0.0.30",
4345
"@types/js-yaml": "^3.9.1",
4446
"@types/lodash": "^4.14.96"

packages/rest/src/rest-server.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {ParsedRequest} from './internal-types';
1111
import {OpenApiSpec, OperationObject} from '@loopback/openapi-v3-types';
1212
import {ServerRequest, ServerResponse, createServer} from 'http';
1313
import * as Http from 'http';
14+
import * as cors from 'cors';
1415
import {Application, CoreBindings, Server} from '@loopback/core';
1516
import {getControllerSpec} from '@loopback/openapi-v3';
1617
import {HttpHandler} from './http-handler';
@@ -159,9 +160,24 @@ export class RestServer extends Context implements Server {
159160
) {
160161
// allow CORS support for all endpoints so that users
161162
// can test with online SwaggerUI instance
162-
response.setHeader('Access-Control-Allow-Origin', '*');
163-
response.setHeader('Access-Control-Allow-Credentials', 'true');
164-
response.setHeader('Access-Control-Allow-Max-Age', '86400');
163+
164+
const corsOptions = options.cors || {
165+
origin: '*',
166+
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
167+
preflightContinue: false,
168+
optionsSuccessStatus: 204,
169+
maxAge: 86400,
170+
credentials: true,
171+
};
172+
173+
// FIXME: `cors` expects Express Request/Response but the implementation
174+
// at https://github.com/expressjs/cors/blob/master/lib/index.js only uses
175+
// http.ServerRequest/ServerResponse
176+
// tslint:disable-next-line:no-any
177+
cors(corsOptions)(request as any, response as any, () => {});
178+
if (request.method === 'OPTIONS') {
179+
return Promise.resolve();
180+
}
165181

166182
if (
167183
request.method === 'GET' &&
@@ -588,6 +604,7 @@ export class RestServer extends Context implements Server {
588604
export interface RestServerConfig {
589605
host?: string;
590606
port?: number;
607+
cors?: cors.CorsOptions;
591608
apiExplorerUrl?: string;
592609
sequence?: Constructor<SequenceHandler>;
593610
}

packages/rest/test/integration/rest-server.integration.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,35 @@ describe('RestServer (integration)', () => {
3737
.expect(500);
3838
});
3939

40+
it('allows cors', async () => {
41+
const server = await givenAServer({rest: {port: 0}});
42+
server.handler((sequence, request, response) => {
43+
response.write('Hello');
44+
response.end();
45+
});
46+
47+
await createClientForHandler(server.handleHttp)
48+
.get('/')
49+
.expect(200, 'Hello')
50+
.expect('Access-Control-Allow-Origin', '*')
51+
.expect('Access-Control-Allow-Credentials', 'true');
52+
});
53+
54+
it('allows cors preflight', async () => {
55+
const server = await givenAServer({rest: {port: 0}});
56+
server.handler((sequence, request, response) => {
57+
response.write('Hello');
58+
response.end();
59+
});
60+
61+
await createClientForHandler(server.handleHttp)
62+
.options('/')
63+
.expect(204)
64+
.expect('Access-Control-Allow-Origin', '*')
65+
.expect('Access-Control-Allow-Credentials', 'true')
66+
.expect('Access-Control-Max-Age', '86400');
67+
});
68+
4069
it('exposes "GET /openapi.json" endpoint', async () => {
4170
const server = await givenAServer({rest: {port: 0}});
4271
const greetSpec = {
@@ -75,7 +104,6 @@ describe('RestServer (integration)', () => {
75104
});
76105
expect(response.get('Access-Control-Allow-Origin')).to.equal('*');
77106
expect(response.get('Access-Control-Allow-Credentials')).to.equal('true');
78-
expect(response.get('Access-Control-Allow-Max-Age')).to.equal('86400');
79107
});
80108

81109
it('exposes "GET /openapi.yaml" endpoint', async () => {
@@ -115,7 +143,6 @@ servers:
115143
expect(yaml.safeLoad(response.text)).to.eql(expected);
116144
expect(response.get('Access-Control-Allow-Origin')).to.equal('*');
117145
expect(response.get('Access-Control-Allow-Credentials')).to.equal('true');
118-
expect(response.get('Access-Control-Allow-Max-Age')).to.equal('86400');
119146
});
120147

121148
it('exposes "GET /swagger-ui" endpoint', async () => {
@@ -145,7 +172,6 @@ servers:
145172
expect(response.get('Location')).match(url);
146173
expect(response.get('Access-Control-Allow-Origin')).to.equal('*');
147174
expect(response.get('Access-Control-Allow-Credentials')).to.equal('true');
148-
expect(response.get('Access-Control-Allow-Max-Age')).to.equal('86400');
149175
});
150176

151177
it('exposes "GET /swagger-ui" endpoint with apiExplorerUrl', async () => {
@@ -177,7 +203,6 @@ servers:
177203
expect(response.get('Location')).match(url);
178204
expect(response.get('Access-Control-Allow-Origin')).to.equal('*');
179205
expect(response.get('Access-Control-Allow-Credentials')).to.equal('true');
180-
expect(response.get('Access-Control-Allow-Max-Age')).to.equal('86400');
181206
});
182207

183208
async function givenAServer(options?: ApplicationConfig) {

0 commit comments

Comments
 (0)