Skip to content

Don't require app ID header in request for single-app deployment #6645

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

Open
wants to merge 3 commits into
base: alpha
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,27 @@ describe('middlewares', () => {
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should success without x-parse-application-id in header when there is only one app', (done) => {
AppCache.del('test');
delete fakeReq.body._ApplicationId;
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should success with x-parse-application-id match an app when there are multiple apps', (done) => {
fakeReq.headers['x-parse-application-id'] = fakeReq.body._ApplicationId;
delete fakeReq.body._ApplicationId;
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should faile with x-parse-application-id not match any app when there are multiple apps', () => {
fakeReq.headers['x-parse-application-id'] = 'do-not-match-an-app';
delete fakeReq.body._ApplicationId;
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
});
124 changes: 63 additions & 61 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,73 +49,59 @@ export function handleParseHeaders(req, res, next) {
}
}

var fileViaJSON = false;

// To support client SDKs that always POST instead of use proper HTTP calls,
// try parse req.body Buffer as JSON and read _ApplicationId and other keys from it
if (!info.appId && req.body instanceof Buffer) {
try {
req.body = JSON.parse(req.body);
fileViaJSON = true;
} catch (e) {
// Happens when upload non JSON file to /parse/file endpoint
}
}

// Remove certain things sent by client
if (req.body) {
// Unity SDK sends a _noBody key which needs to be removed.
// Unclear at this point if action needs to be taken.
delete req.body._noBody;
// _Revocable Session should always be ignored
// https://github.com/parse-community/parse-server/issues/1548
delete req.body._RevocableSession;
}

var fileViaJSON = false;

if (!info.appId || !AppCache.get(info.appId)) {
// See if we can find the app id on the body.
if (req.body instanceof Buffer) {
// The only chance to find the app id is if this is a file
// upload that actually is a JSON body. So try to parse it.
// https://github.com/parse-community/parse-server/issues/6589
// It is also possible that the client is trying to upload a file but forgot
// to provide x-parse-app-id in header and parse a binary file will fail
try {
req.body = JSON.parse(req.body);
} catch (e) {
return invalidRequest(req, res);
}
fileViaJSON = true;
if (req.body && req.body._ApplicationId) {
info.appId = req.body._ApplicationId;
delete req.body._ApplicationId;
info.javascriptKey = req.body._JavaScriptKey || '';
delete req.body._JavaScriptKey;
// TODO: test that the REST API formats generated by the other
// SDKs are handled ok
if (req.body._ClientVersion) {
info.clientVersion = req.body._ClientVersion;
delete req.body._ClientVersion;
}

if (req.body) {
delete req.body._RevocableSession;
if (req.body._InstallationId) {
info.installationId = req.body._InstallationId;
delete req.body._InstallationId;
}

if (
req.body &&
req.body._ApplicationId &&
AppCache.get(req.body._ApplicationId) &&
(!info.masterKey ||
AppCache.get(req.body._ApplicationId).masterKey === info.masterKey)
) {
info.appId = req.body._ApplicationId;
info.javascriptKey = req.body._JavaScriptKey || '';
delete req.body._ApplicationId;
delete req.body._JavaScriptKey;
// TODO: test that the REST API formats generated by the other
// SDKs are handled ok
if (req.body._ClientVersion) {
info.clientVersion = req.body._ClientVersion;
delete req.body._ClientVersion;
}
if (req.body._InstallationId) {
info.installationId = req.body._InstallationId;
delete req.body._InstallationId;
}
if (req.body._SessionToken) {
info.sessionToken = req.body._SessionToken;
delete req.body._SessionToken;
}
if (req.body._MasterKey) {
info.masterKey = req.body._MasterKey;
delete req.body._MasterKey;
}
if (req.body._context && req.body._context instanceof Object) {
info.context = req.body._context;
delete req.body._context;
}
if (req.body._ContentType) {
req.headers['content-type'] = req.body._ContentType;
delete req.body._ContentType;
}
} else {
return invalidRequest(req, res);
if (req.body._SessionToken) {
info.sessionToken = req.body._SessionToken;
delete req.body._SessionToken;
}
if (req.body._MasterKey) {
info.masterKey = req.body._MasterKey;
delete req.body._MasterKey;
}
if (req.body._context && req.body._context instanceof Object) {
info.context = req.body._context;
delete req.body._context;
}
if (req.body._ContentType) {
req.headers['content-type'] = req.body._ContentType;
delete req.body._ContentType;
}
}

Expand All @@ -136,8 +122,24 @@ export function handleParseHeaders(req, res, next) {

const clientIp = getClientIp(req);

info.app = AppCache.get(info.appId);
req.config = Config.get(info.appId, mount);
let appId;
if (info.appId) {
// Request provided appId, just use that
appId = info.appId;
} else if (Object.keys(AppCache.cache).length === 1) {
// Request did not provide appId, but there is only one app anyway, just use that
appId = Object.keys(AppCache.cache)[0];
} else {
// Request did not provide appId and there are multiple apps, we do not know which one to use
return invalidRequest(req, res);
}

info.app = AppCache.get(appId);
if (!info.app) {
// The appId user provided is not valid
return invalidRequest(req, res);
}
req.config = Config.get(appId, mount);
req.config.headers = req.headers || {};
req.config.ip = clientIp;
req.info = info;
Expand Down