Files
kami-parse-server/src/middlewares.js

294 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import AppCache from './cache';
import log from './logger';
import Parse from 'parse/node';
import auth from './Auth';
import Config from './Config';
import ClientSDK from './ClientSDK';
// Checks that the request is authorized for this app and checks user
// auth too.
// The bodyparser should run before this middleware.
// Adds info to the request:
// req.config - the Config for this app
// req.auth - the Auth for this request
export function handleParseHeaders(req, res, next) {
var mountPathLength = req.originalUrl.length - req.url.length;
var mountPath = req.originalUrl.slice(0, mountPathLength);
var mount = req.protocol + '://' + req.get('host') + mountPath;
var info = {
appId: req.get('X-Parse-Application-Id'),
sessionToken: req.get('X-Parse-Session-Token'),
masterKey: req.get('X-Parse-Master-Key'),
installationId: req.get('X-Parse-Installation-Id'),
clientKey: req.get('X-Parse-Client-Key'),
javascriptKey: req.get('X-Parse-Javascript-Key'),
dotNetKey: req.get('X-Parse-Windows-Key'),
restAPIKey: req.get('X-Parse-REST-API-Key'),
clientVersion: req.get('X-Parse-Client-Version')
};
var basicAuth = httpAuth(req);
if (basicAuth) {
var basicAuthAppId = basicAuth.appId;
if (AppCache.get(basicAuthAppId)) {
info.appId = basicAuthAppId;
info.masterKey = basicAuth.masterKey || info.masterKey;
info.javascriptKey = basicAuth.javascriptKey || info.javascriptKey;
}
}
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;
}
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.
req.body = JSON.parse(req.body);
fileViaJSON = true;
}
if (req.body) {
delete req.body._RevocableSession;
}
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._ContentType) {
req.headers['content-type'] = req.body._ContentType;
delete req.body._ContentType;
}
} else {
return invalidRequest(req, res);
}
}
if (info.clientVersion) {
info.clientSDK = ClientSDK.fromString(info.clientVersion);
}
if (fileViaJSON) {
// We need to repopulate req.body with a buffer
var base64 = req.body.base64;
req.body = new Buffer(base64, 'base64');
}
info.app = AppCache.get(info.appId);
req.config = new Config(info.appId, mount);
req.info = info;
var isMaster = (info.masterKey === req.config.masterKey);
if (isMaster) {
req.auth = new auth.Auth({ config: req.config, installationId: info.installationId, isMaster: true });
next();
return;
}
// Client keys are not required in parse-server, but if any have been configured in the server, validate them
// to preserve original behavior.
let keys = ["clientKey", "javascriptKey", "dotNetKey", "restAPIKey"];
// We do it with mismatching keys to support no-keys config
var keyMismatch = keys.reduce(function(mismatch, key){
// check if set in the config and compare
if (req.config[key] && info[key] !== req.config[key]) {
mismatch++;
}
return mismatch;
}, 0);
// All keys mismatch
if (keyMismatch == keys.length) {
return invalidRequest(req, res);
}
if (req.url == "/login") {
delete info.sessionToken;
}
if (!info.sessionToken) {
req.auth = new auth.Auth({ config: req.config, installationId: info.installationId, isMaster: false });
next();
return;
}
return Promise.resolve().then(() => {
// handle the upgradeToRevocableSession path on it's own
if (info.sessionToken &&
req.url === '/upgradeToRevocableSession' &&
info.sessionToken.indexOf('r:') != 0) {
return auth.getAuthForLegacySessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
} else {
return auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
}
}).then((auth) => {
if (auth) {
req.auth = auth;
next();
}
})
.catch((error) => {
if(error instanceof Parse.Error) {
next(error);
return;
}
else {
// TODO: Determine the correct error scenario.
log.error('error getting auth for sessionToken', error);
throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error);
}
});
}
function httpAuth(req) {
if (!(req.req || req).headers.authorization)
return ;
var header = (req.req || req).headers.authorization;
var appId, masterKey, javascriptKey;
// parse header
var authPrefix = 'basic ';
var match = header.toLowerCase().indexOf(authPrefix);
if (match == 0) {
var encodedAuth = header.substring(authPrefix.length, header.length);
var credentials = decodeBase64(encodedAuth).split(':');
if (credentials.length == 2) {
appId = credentials[0];
var key = credentials[1];
var jsKeyPrefix = 'javascript-key=';
var matchKey = key.indexOf(jsKeyPrefix)
if (matchKey == 0) {
javascriptKey = key.substring(jsKeyPrefix.length, key.length);
}
else {
masterKey = key;
}
}
}
return {appId: appId, masterKey: masterKey, javascriptKey: javascriptKey};
}
function decodeBase64(str) {
return new Buffer(str, 'base64').toString()
}
export function allowCrossDomain(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, Content-Type');
// intercept OPTIONS method
if ('OPTIONS' == req.method) {
res.sendStatus(200);
}
else {
next();
}
};
export function allowMethodOverride(req, res, next) {
if (req.method === 'POST' && req.body._method) {
req.originalMethod = req.method;
req.method = req.body._method;
delete req.body._method;
}
next();
};
export function handleParseErrors(err, req, res, next) {
// TODO: Add logging as those errors won't make it to the PromiseRouter
if (err instanceof Parse.Error) {
var httpStatus;
// TODO: fill out this mapping
switch (err.code) {
case Parse.Error.INTERNAL_SERVER_ERROR:
httpStatus = 500;
break;
case Parse.Error.OBJECT_NOT_FOUND:
httpStatus = 404;
break;
default:
httpStatus = 400;
}
res.status(httpStatus);
res.json({code: err.code, error: err.message});
} else if (err.status && err.message) {
res.status(err.status);
res.json({error: err.message});
} else {
log.error('Uncaught internal server error.', err, err.stack);
res.status(500);
res.json({code: Parse.Error.INTERNAL_SERVER_ERROR,
message: 'Internal server error.'});
}
next(err);
};
export function enforceMasterKeyAccess(req, res, next) {
if (!req.auth.isMaster) {
res.status(403);
res.end('{"error":"unauthorized: master key is required"}');
return;
}
next();
}
export function promiseEnforceMasterKeyAccess(request) {
if (!request.auth.isMaster) {
let error = new Error();
error.status = 403;
error.message = "unauthorized: master key is required";
throw error;
}
return Promise.resolve();
}
function invalidRequest(req, res) {
res.status(403);
res.end('{"error":"unauthorized"}');
}