123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.addRateLimit = exports.DEFAULT_ALLOWED_HEADERS = void 0;
- exports.allowCrossDomain = allowCrossDomain;
- exports.allowMethodOverride = allowMethodOverride;
- exports.checkIp = void 0;
- exports.enforceMasterKeyAccess = enforceMasterKeyAccess;
- exports.handleParseErrors = handleParseErrors;
- exports.handleParseHeaders = handleParseHeaders;
- exports.handleParseSession = void 0;
- exports.promiseEnforceMasterKeyAccess = promiseEnforceMasterKeyAccess;
- exports.promiseEnsureIdempotency = promiseEnsureIdempotency;
- var _cache = _interopRequireDefault(require("./cache"));
- var _node = _interopRequireDefault(require("parse/node"));
- var _Auth = _interopRequireDefault(require("./Auth"));
- var _Config = _interopRequireDefault(require("./Config"));
- var _ClientSDK = _interopRequireDefault(require("./ClientSDK"));
- var _logger = _interopRequireDefault(require("./logger"));
- var _rest = _interopRequireDefault(require("./rest"));
- var _MongoStorageAdapter = _interopRequireDefault(require("./Adapters/Storage/Mongo/MongoStorageAdapter"));
- var _PostgresStorageAdapter = _interopRequireDefault(require("./Adapters/Storage/Postgres/PostgresStorageAdapter"));
- var _expressRateLimit = _interopRequireDefault(require("express-rate-limit"));
- var _Definitions = require("./Options/Definitions");
- var _pathToRegexp = require("path-to-regexp");
- var _rateLimitRedis = _interopRequireDefault(require("rate-limit-redis"));
- var _redis = require("redis");
- var _net = require("net");
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
- const DEFAULT_ALLOWED_HEADERS = exports.DEFAULT_ALLOWED_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, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control';
- const getMountForRequest = function (req) {
- const mountPathLength = req.originalUrl.length - req.url.length;
- const mountPath = req.originalUrl.slice(0, mountPathLength);
- return req.protocol + '://' + req.get('host') + mountPath;
- };
- const getBlockList = (ipRangeList, store) => {
- if (store.get('blockList')) return store.get('blockList');
- const blockList = new _net.BlockList();
- ipRangeList.forEach(fullIp => {
- if (fullIp === '::/0' || fullIp === '::') {
- store.set('allowAllIpv6', true);
- return;
- }
- if (fullIp === '0.0.0.0/0' || fullIp === '0.0.0.0') {
- store.set('allowAllIpv4', true);
- return;
- }
- const [ip, mask] = fullIp.split('/');
- if (!mask) {
- blockList.addAddress(ip, (0, _net.isIPv4)(ip) ? 'ipv4' : 'ipv6');
- } else {
- blockList.addSubnet(ip, Number(mask), (0, _net.isIPv4)(ip) ? 'ipv4' : 'ipv6');
- }
- });
- store.set('blockList', blockList);
- return blockList;
- };
- const checkIp = (ip, ipRangeList, store) => {
- const incomingIpIsV4 = (0, _net.isIPv4)(ip);
- const blockList = getBlockList(ipRangeList, store);
- if (store.get(ip)) return true;
- if (store.get('allowAllIpv4') && incomingIpIsV4) return true;
- if (store.get('allowAllIpv6') && !incomingIpIsV4) return true;
- const result = blockList.check(ip, incomingIpIsV4 ? 'ipv4' : 'ipv6');
-
-
- if (ipRangeList.includes(ip) && result) {
- store.set(ip, result);
- }
- return result;
- };
- exports.checkIp = checkIp;
- function handleParseHeaders(req, res, next) {
- var mount = getMountForRequest(req);
- let context = {};
- if (req.get('X-Parse-Cloud-Context') != null) {
- try {
- context = JSON.parse(req.get('X-Parse-Cloud-Context'));
- if (Object.prototype.toString.call(context) !== '[object Object]') {
- throw 'Context is not an object';
- }
- } catch (e) {
- return malformedContext(req, res);
- }
- }
- var info = {
- appId: req.get('X-Parse-Application-Id'),
- sessionToken: req.get('X-Parse-Session-Token'),
- masterKey: req.get('X-Parse-Master-Key'),
- maintenanceKey: req.get('X-Parse-Maintenance-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'),
- context: context
- };
- var basicAuth = httpAuth(req);
- if (basicAuth) {
- var basicAuthAppId = basicAuth.appId;
- if (_cache.default.get(basicAuthAppId)) {
- info.appId = basicAuthAppId;
- info.masterKey = basicAuth.masterKey || info.masterKey;
- info.javascriptKey = basicAuth.javascriptKey || info.javascriptKey;
- }
- }
- if (req.body) {
-
-
- delete req.body._noBody;
- }
- var fileViaJSON = false;
- if (!info.appId || !_cache.default.get(info.appId)) {
-
- if (req.body instanceof Buffer) {
-
-
-
-
-
- try {
- req.body = JSON.parse(req.body);
- } catch (e) {
- return invalidRequest(req, res);
- }
- fileViaJSON = true;
- }
- if (req.body) {
- delete req.body._RevocableSession;
- }
- if (req.body && req.body._ApplicationId && _cache.default.get(req.body._ApplicationId) && (!info.masterKey || _cache.default.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;
-
-
- 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) {
- if (req.body._context instanceof Object) {
- info.context = req.body._context;
- } else {
- try {
- info.context = JSON.parse(req.body._context);
- if (Object.prototype.toString.call(info.context) !== '[object Object]') {
- throw 'Context is not an object';
- }
- } catch (e) {
- return malformedContext(req, res);
- }
- }
- 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 (info.sessionToken && typeof info.sessionToken !== 'string') {
- info.sessionToken = info.sessionToken.toString();
- }
- if (info.clientVersion) {
- info.clientSDK = _ClientSDK.default.fromString(info.clientVersion);
- }
- if (fileViaJSON) {
- req.fileData = req.body.fileData;
-
- var base64 = req.body.base64;
- req.body = Buffer.from(base64, 'base64');
- }
- const clientIp = getClientIp(req);
- const config = _Config.default.get(info.appId, mount);
- if (config.state && config.state !== 'ok') {
- res.status(500);
- res.json({
- code: _node.default.Error.INTERNAL_SERVER_ERROR,
- error: `Invalid server state: ${config.state}`
- });
- return;
- }
- info.app = _cache.default.get(info.appId);
- req.config = config;
- req.config.headers = req.headers || {};
- req.config.ip = clientIp;
- req.info = info;
- const isMaintenance = req.config.maintenanceKey && info.maintenanceKey === req.config.maintenanceKey;
- if (isMaintenance) {
- var _req$config;
- if (checkIp(clientIp, req.config.maintenanceKeyIps || [], req.config.maintenanceKeyIpsStore)) {
- req.auth = new _Auth.default.Auth({
- config: req.config,
- installationId: info.installationId,
- isMaintenance: true
- });
- next();
- return;
- }
- const log = ((_req$config = req.config) === null || _req$config === void 0 ? void 0 : _req$config.loggerController) || _logger.default;
- log.error(`Request using maintenance key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'maintenanceKeyIps'.`);
- }
- let isMaster = info.masterKey === req.config.masterKey;
- if (isMaster && !checkIp(clientIp, req.config.masterKeyIps || [], req.config.masterKeyIpsStore)) {
- var _req$config2;
- const log = ((_req$config2 = req.config) === null || _req$config2 === void 0 ? void 0 : _req$config2.loggerController) || _logger.default;
- log.error(`Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.`);
- isMaster = false;
- const error = new Error();
- error.status = 403;
- error.message = `unauthorized`;
- throw error;
- }
- if (isMaster) {
- req.auth = new _Auth.default.Auth({
- config: req.config,
- installationId: info.installationId,
- isMaster: true
- });
- return handleRateLimit(req, res, next);
- }
- var isReadOnlyMaster = info.masterKey === req.config.readOnlyMasterKey;
- if (typeof req.config.readOnlyMasterKey != 'undefined' && req.config.readOnlyMasterKey && isReadOnlyMaster) {
- req.auth = new _Auth.default.Auth({
- config: req.config,
- installationId: info.installationId,
- isMaster: true,
- isReadOnly: true
- });
- return handleRateLimit(req, res, next);
- }
-
-
- const keys = ['clientKey', 'javascriptKey', 'dotNetKey', 'restAPIKey'];
- const oneKeyConfigured = keys.some(function (key) {
- return req.config[key] !== undefined;
- });
- const oneKeyMatches = keys.some(function (key) {
- return req.config[key] !== undefined && info[key] === req.config[key];
- });
- if (oneKeyConfigured && !oneKeyMatches) {
- return invalidRequest(req, res);
- }
- if (req.url == '/login') {
- delete info.sessionToken;
- }
- if (req.userFromJWT) {
- req.auth = new _Auth.default.Auth({
- config: req.config,
- installationId: info.installationId,
- isMaster: false,
- user: req.userFromJWT
- });
- return handleRateLimit(req, res, next);
- }
- if (!info.sessionToken) {
- req.auth = new _Auth.default.Auth({
- config: req.config,
- installationId: info.installationId,
- isMaster: false
- });
- }
- handleRateLimit(req, res, next);
- }
- const handleRateLimit = async (req, res, next) => {
- const rateLimits = req.config.rateLimits || [];
- try {
- await Promise.all(rateLimits.map(async limit => {
- const pathExp = new RegExp(limit.path);
- if (pathExp.test(req.url)) {
- await limit.handler(req, res, err => {
- if (err) {
- if (err.code === _node.default.Error.CONNECTION_FAILED) {
- throw err;
- }
- req.config.loggerController.error('An unknown error occured when attempting to apply the rate limiter: ', err);
- }
- });
- }
- }));
- } catch (error) {
- res.status(429);
- res.json({
- code: _node.default.Error.CONNECTION_FAILED,
- error: error.message
- });
- return;
- }
- next();
- };
- const handleParseSession = async (req, res, next) => {
- try {
- const info = req.info;
- if (req.auth || req.url === '/sessions/me') {
- next();
- return;
- }
- let requestAuth = null;
- if (info.sessionToken && req.url === '/upgradeToRevocableSession' && info.sessionToken.indexOf('r:') != 0) {
- requestAuth = await _Auth.default.getAuthForLegacySessionToken({
- config: req.config,
- installationId: info.installationId,
- sessionToken: info.sessionToken
- });
- } else {
- requestAuth = await _Auth.default.getAuthForSessionToken({
- config: req.config,
- installationId: info.installationId,
- sessionToken: info.sessionToken
- });
- }
- req.auth = requestAuth;
- next();
- } catch (error) {
- if (error instanceof _node.default.Error) {
- next(error);
- return;
- }
-
- req.config.loggerController.error('error getting auth for sessionToken', error);
- throw new _node.default.Error(_node.default.Error.UNKNOWN_ERROR, error);
- }
- };
- exports.handleParseSession = handleParseSession;
- function getClientIp(req) {
- return req.ip;
- }
- function httpAuth(req) {
- if (!(req.req || req).headers.authorization) return;
- var header = (req.req || req).headers.authorization;
- var appId, masterKey, javascriptKey;
-
- 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 Buffer.from(str, 'base64').toString();
- }
- function allowCrossDomain(appId) {
- return (req, res, next) => {
- const config = _Config.default.get(appId, getMountForRequest(req));
- let allowHeaders = DEFAULT_ALLOWED_HEADERS;
- if (config && config.allowHeaders) {
- allowHeaders += `, ${config.allowHeaders.join(', ')}`;
- }
- const baseOrigins = typeof (config === null || config === void 0 ? void 0 : config.allowOrigin) === 'string' ? [config.allowOrigin] : (config === null || config === void 0 ? void 0 : 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');
-
- if ('OPTIONS' == req.method) {
- res.sendStatus(200);
- } else {
- next();
- }
- };
- }
- 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();
- }
- function handleParseErrors(err, req, res, next) {
- const log = req.config && req.config.loggerController || _logger.default;
- if (err instanceof _node.default.Error) {
- if (req.config && req.config.enableExpressErrorHandler) {
- return next(err);
- }
- let httpStatus;
-
- switch (err.code) {
- case _node.default.Error.INTERNAL_SERVER_ERROR:
- httpStatus = 500;
- break;
- case _node.default.Error.OBJECT_NOT_FOUND:
- httpStatus = 404;
- break;
- default:
- httpStatus = 400;
- }
- res.status(httpStatus);
- res.json({
- code: err.code,
- error: err.message
- });
- log.error('Parse error: ', err);
- } else if (err.status && err.message) {
- res.status(err.status);
- res.json({
- error: err.message
- });
- if (!(process && process.env.TESTING)) {
- next(err);
- }
- } else {
- log.error('Uncaught internal server error.', err, err.stack);
- res.status(500);
- res.json({
- code: _node.default.Error.INTERNAL_SERVER_ERROR,
- message: 'Internal server error.'
- });
- if (!(process && process.env.TESTING)) {
- next(err);
- }
- }
- }
- function enforceMasterKeyAccess(req, res, next) {
- if (!req.auth.isMaster) {
- res.status(403);
- res.end('{"error":"unauthorized: master key is required"}');
- return;
- }
- next();
- }
- function promiseEnforceMasterKeyAccess(request) {
- if (!request.auth.isMaster) {
- const error = new Error();
- error.status = 403;
- error.message = 'unauthorized: master key is required';
- throw error;
- }
- return Promise.resolve();
- }
- const addRateLimit = (route, config, cloud) => {
- if (typeof config === 'string') {
- config = _Config.default.get(config);
- }
- for (const key in route) {
- if (!_Definitions.RateLimitOptions[key]) {
- throw `Invalid rate limit option "${key}"`;
- }
- }
- if (!config.rateLimits) {
- config.rateLimits = [];
- }
- const redisStore = {
- connectionPromise: Promise.resolve(),
- store: null
- };
- if (route.redisUrl) {
- const client = (0, _redis.createClient)({
- url: route.redisUrl
- });
- redisStore.connectionPromise = async () => {
- if (client.isOpen) {
- return;
- }
- try {
- await client.connect();
- } catch (e) {
- var _config;
- const log = ((_config = config) === null || _config === void 0 ? void 0 : _config.loggerController) || _logger.default;
- log.error(`Could not connect to redisURL in rate limit: ${e}`);
- }
- };
- redisStore.connectionPromise();
- redisStore.store = new _rateLimitRedis.default({
- sendCommand: async (...args) => {
- await redisStore.connectionPromise();
- return client.sendCommand(args);
- }
- });
- }
- let transformPath = route.requestPath.split('/*').join('/(.*)');
- if (transformPath === '*') {
- transformPath = '(.*)';
- }
- config.rateLimits.push({
- path: (0, _pathToRegexp.pathToRegexp)(transformPath),
- handler: (0, _expressRateLimit.default)({
- windowMs: route.requestTimeWindow,
- max: route.requestCount,
- message: route.errorResponseMessage || _Definitions.RateLimitOptions.errorResponseMessage.default,
- handler: (request, response, next, options) => {
- throw {
- code: _node.default.Error.CONNECTION_FAILED,
- message: options.message
- };
- },
- skip: request => {
- var _request$auth;
- if (request.ip === '127.0.0.1' && !route.includeInternalRequests) {
- return true;
- }
- if (route.includeMasterKey) {
- return false;
- }
- if (route.requestMethods) {
- if (Array.isArray(route.requestMethods)) {
- if (!route.requestMethods.includes(request.method)) {
- return true;
- }
- } else {
- const regExp = new RegExp(route.requestMethods);
- if (!regExp.test(request.method)) {
- return true;
- }
- }
- }
- return (_request$auth = request.auth) === null || _request$auth === void 0 ? void 0 : _request$auth.isMaster;
- },
- keyGenerator: async request => {
- if (route.zone === _node.default.Server.RateLimitZone.global) {
- return request.config.appId;
- }
- const token = request.info.sessionToken;
- if (route.zone === _node.default.Server.RateLimitZone.session && token) {
- return token;
- }
- if (route.zone === _node.default.Server.RateLimitZone.user && token) {
- var _request$auth2;
- if (!request.auth) {
- await new Promise(resolve => handleParseSession(request, null, resolve));
- }
- if ((_request$auth2 = request.auth) !== null && _request$auth2 !== void 0 && (_request$auth2 = _request$auth2.user) !== null && _request$auth2 !== void 0 && _request$auth2.id && request.zone === 'user') {
- return request.auth.user.id;
- }
- }
- return request.config.ip;
- },
- store: redisStore.store
- }),
- cloud
- });
- _Config.default.put(config);
- };
- exports.addRateLimit = addRateLimit;
- function promiseEnsureIdempotency(req) {
-
- if (!(req.config.database.adapter instanceof _MongoStorageAdapter.default || req.config.database.adapter instanceof _PostgresStorageAdapter.default)) {
- return Promise.resolve();
- }
-
- const config = req.config;
- const requestId = ((req || {}).headers || {})['x-parse-request-id'];
- const {
- paths,
- ttl
- } = config.idempotencyOptions;
- if (!requestId || !config.idempotencyOptions) {
- return Promise.resolve();
- }
-
-
- const reqPath = req.path.replace(/^\/|\/$/, '');
-
- let match = false;
- for (const path of paths) {
-
- const regex = new RegExp(path.charAt(0) === '^' ? path : '^' + path);
- if (reqPath.match(regex)) {
- match = true;
- break;
- }
- }
- if (!match) {
- return Promise.resolve();
- }
-
- const expiryDate = new Date(new Date().setSeconds(new Date().getSeconds() + ttl));
- return _rest.default.create(config, _Auth.default.master(config), '_Idempotency', {
- reqId: requestId,
- expire: _node.default._encode(expiryDate)
- }).catch(e => {
- if (e.code == _node.default.Error.DUPLICATE_VALUE) {
- throw new _node.default.Error(_node.default.Error.DUPLICATE_REQUEST, 'Duplicate request');
- }
- throw e;
- });
- }
- function invalidRequest(req, res) {
- res.status(403);
- res.end('{"error":"unauthorized"}');
- }
- function malformedContext(req, res) {
- res.status(400);
- res.json({
- code: _node.default.Error.INVALID_JSON,
- error: 'Invalid object for context.'
- });
- }
|