Skip to content

feat: Allow multiple origins for header Access-Control-Allow-Origin #8517

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

Merged
merged 10 commits into from
May 1, 2023
6 changes: 6 additions & 0 deletions resources/buildConfigDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ function mapperFor(elt, t) {
if (type == 'NumberOrBoolean') {
return wrap(t.identifier('numberOrBooleanParser'));
}
if (type === 'StringOrStringArray') {
return wrap(t.identifier('arrayParser'));
}
return wrap(t.identifier('objectParser'));
}
}
Expand Down Expand Up @@ -278,6 +281,9 @@ function inject(t, list) {
const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name;
type = `Adapter<${adapterType}>`;
}
if (type === 'StringOrStringArray') {
type = 'String|String[]';
}
comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`;
const obj = t.objectExpression(props);
return t.objectProperty(t.stringLiteral(elt.name), obj);
Expand Down
29 changes: 29 additions & 0 deletions spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,35 @@ describe('middlewares', () => {
expect(headers['Access-Control-Allow-Origin']).toEqual('https://parseplatform.org/');
});

it('should support multiple origins if several are defined in allowOrigin as an array', () => {
AppCache.put(fakeReq.body._ApplicationId, {
allowOrigin: ['https://a.com', 'https://b.com', 'https://c.com'],
});
const headers = {};
const res = {
header: (key, value) => {
headers[key] = value;
},
};
const allowCrossDomain = middlewares.allowCrossDomain(fakeReq.body._ApplicationId);
// Test with the first domain
fakeReq.headers.origin = 'https://a.com';
allowCrossDomain(fakeReq, res, () => {});
expect(headers['Access-Control-Allow-Origin']).toEqual('https://a.com');
// Test with the second domain
fakeReq.headers.origin = 'https://b.com';
allowCrossDomain(fakeReq, res, () => {});
expect(headers['Access-Control-Allow-Origin']).toEqual('https://b.com');
// Test with the third domain
fakeReq.headers.origin = 'https://c.com';
allowCrossDomain(fakeReq, res, () => {});
expect(headers['Access-Control-Allow-Origin']).toEqual('https://c.com');
// Test with an unauthorized domain
fakeReq.headers.origin = 'https://unauthorized.com';
allowCrossDomain(fakeReq, res, () => {});
expect(headers['Access-Control-Allow-Origin']).toEqual('https://a.com');
});

it('should use user provided on field userFromJWT', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
Expand Down
4 changes: 3 additions & 1 deletion src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ module.exports.ParseServerOptions = {
},
allowOrigin: {
env: 'PARSE_SERVER_ALLOW_ORIGIN',
help: 'Sets the origin to Access-Control-Allow-Origin',
help:
'Sets origins for Access-Control-Allow-Origin. This can be a string for a single origin or an array of strings for multiple origins.',
action: parsers.arrayParser,
},
analyticsAdapter: {
env: 'PARSE_SERVER_ANALYTICS_ADAPTER',
Expand Down
2 changes: 1 addition & 1 deletion src/Options/docs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Adapter<T> = string | any | T;
type NumberOrBoolean = number | boolean;
type NumberOrString = number | string;
type ProtectedFields = any;
type StringOrStringArray = string | string[];
type RequestKeywordDenylist = {
key: string | any,
value: any,
Expand All @@ -61,8 +62,8 @@ export interface ParseServerOptions {
appName: ?string;
/* Add headers to Access-Control-Allow-Headers */
allowHeaders: ?(string[]);
/* Sets the origin to Access-Control-Allow-Origin */
allowOrigin: ?string;
/* Sets origins for Access-Control-Allow-Origin. This can be a string for a single origin or an array of strings for multiple origins. */
allowOrigin: ?StringOrStringArray;
/* Adapter module for the analytics */
analyticsAdapter: ?Adapter<AnalyticsAdapter>;
/* Adapter module for the files sub-system */
Expand Down
9 changes: 7 additions & 2 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,13 @@ export function allowCrossDomain(appId) {
if (config && config.allowHeaders) {
allowHeaders += `, ${config.allowHeaders.join(', ')}`;
}
const allowOrigin = (config && config.allowOrigin) || '*';
res.header('Access-Control-Allow-Origin', allowOrigin);

const baseOrigins =
typeof config?.allowOrigin === 'string' ? [config.allowOrigin] : config?.allowOrigin ?? ['*'];
const requestOrigin = req.headers.origin;
const allowOrigins =
requestOrigin && baseOrigins.includes(requestOrigin) ? requestOrigin : baseOrigins[0];
res.header('Access-Control-Allow-Origin', allowOrigins);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', allowHeaders);
res.header('Access-Control-Expose-Headers', 'X-Parse-Job-Status-Id, X-Parse-Push-Status-Id');
Expand Down