From 010c72319e8f88470bc411706c8a6b6bed9e7b47 Mon Sep 17 00:00:00 2001 From: Wahb Ben Ishak Date: Wed, 3 Aug 2016 16:37:54 +0200 Subject: [PATCH 1/5] allow send push to sns topic --- src/Controllers/PushController.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 6827e77036..27ee7e4b60 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -71,6 +71,9 @@ export class PushController extends AdaptableController { let updateWhere = deepcopy(where); badgeUpdate = () => { + if (updateWhere.arn){ + return; + } updateWhere.deviceType = 'ios'; // Build a real RestQuery so we can use it in RestWrite let restQuery = new RestQuery(config, master(config), '_Installation', updateWhere); @@ -88,6 +91,12 @@ export class PushController extends AdaptableController { onPushStatusSaved(pushStatus.objectId); return badgeUpdate(); }).then(() => { + if (this.adapter.snsConfig && where.arn){ + body.data.badge = null // skip badge increment by bulk push send + return { + results : where + } + } return rest.find(config, auth, '_Installation', where); }).then((response) => { if (!response.results) { From 641d6e799ab7aeb96ce0b74d92878c9f7d4982e8 Mon Sep 17 00:00:00 2001 From: Wahb Ben Ishak Date: Wed, 3 Aug 2016 17:35:40 +0200 Subject: [PATCH 2/5] add lib --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4415a5c02c..0953b0be61 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ node_modules .vscode # Babel.js -lib/ # cache folder .cache From 400a05da8ab74cb10de6bfd5616f145c9763453a Mon Sep 17 00:00:00 2001 From: Wahb Ben Ishak Date: Wed, 3 Aug 2016 17:38:34 +0200 Subject: [PATCH 3/5] add lib --- lib/Adapters/AdapterLoader.js | 43 + lib/Adapters/Analytics/AnalyticsAdapter.js | 31 + lib/Adapters/Cache/CacheAdapter.js | 56 + lib/Adapters/Cache/InMemoryCache.js | 88 ++ lib/Adapters/Cache/InMemoryCacheAdapter.js | 58 + lib/Adapters/Email/MailAdapter.js | 43 + lib/Adapters/Files/FilesAdapter.js | 57 + lib/Adapters/Files/GridStoreAdapter.js | 101 ++ lib/Adapters/Logger/FileLoggerAdapter.js | 157 +++ lib/Adapters/Logger/LoggerAdapter.js | 40 + lib/Adapters/Push/PushAdapter.js | 44 + lib/Adapters/Storage/Mongo/MongoCollection.js | 141 +++ .../Storage/Mongo/MongoSchemaCollection.js | 240 ++++ .../Storage/Mongo/MongoStorageAdapter.js | 492 ++++++++ lib/Adapters/Storage/Mongo/MongoTransform.js | 945 +++++++++++++++ .../Postgres/PostgresStorageAdapter.js | 546 +++++++++ lib/Auth.js | 221 ++++ lib/ClientSDK.js | 42 + lib/Config.js | 224 ++++ lib/Controllers/AdaptableController.js | 96 ++ lib/Controllers/AnalyticsController.js | 61 + lib/Controllers/CacheController.js | 129 ++ lib/Controllers/DatabaseController.js | 1061 +++++++++++++++++ lib/Controllers/FilesController.js | 139 +++ lib/Controllers/HooksController.js | 274 +++++ lib/Controllers/LiveQueryController.js | 69 ++ lib/Controllers/LoggerController.js | 117 ++ lib/Controllers/PushController.js | 258 ++++ lib/Controllers/SchemaCache.js | 96 ++ lib/Controllers/SchemaController.js | 1011 ++++++++++++++++ lib/Controllers/UserController.js | 261 ++++ lib/DatabaseAdapter.js | 27 + lib/LiveQuery/Client.js | 162 +++ lib/LiveQuery/EventEmitterPubSub.js | 92 ++ lib/LiveQuery/Id.js | 34 + lib/LiveQuery/PLog.js | 7 + lib/LiveQuery/ParseCloudCodePublisher.js | 63 + lib/LiveQuery/ParseLiveQueryServer.js | 727 +++++++++++ lib/LiveQuery/ParsePubSub.js | 35 + lib/LiveQuery/ParseWebSocketServer.js | 65 + lib/LiveQuery/QueryTools.js | 282 +++++ lib/LiveQuery/RedisPubSub.js | 27 + lib/LiveQuery/RequestSchema.js | 106 ++ lib/LiveQuery/SessionTokenCache.js | 67 ++ lib/LiveQuery/Subscription.js | 70 ++ lib/LiveQuery/equalObjects.js | 52 + lib/ParseServer.js | 453 +++++++ lib/PromiseRouter.js | 397 ++++++ lib/RestQuery.js | 892 ++++++++++++++ lib/RestWrite.js | 944 +++++++++++++++ lib/Routers/AnalyticsRouter.js | 51 + lib/Routers/ClassesRouter.js | 278 +++++ lib/Routers/FeaturesRouter.js | 90 ++ lib/Routers/FilesRouter.js | 126 ++ lib/Routers/FunctionsRouter.js | 155 +++ lib/Routers/GlobalConfigRouter.js | 79 ++ lib/Routers/HooksRouter.js | 155 +++ lib/Routers/IAPValidationRouter.js | 143 +++ lib/Routers/InstallationsRouter.js | 117 ++ lib/Routers/LogsRouter.js | 96 ++ lib/Routers/PublicAPIRouter.js | 239 ++++ lib/Routers/PurgeRouter.js | 64 + lib/Routers/PushRouter.js | 104 ++ lib/Routers/RolesRouter.js | 97 ++ lib/Routers/SchemasRouter.js | 126 ++ lib/Routers/SessionsRouter.js | 120 ++ lib/Routers/UsersRouter.js | 284 +++++ lib/TestUtils.js | 21 + lib/authDataManager/OAuth1Client.js | 220 ++++ lib/authDataManager/facebook.js | 52 + lib/authDataManager/github.js | 50 + lib/authDataManager/google.js | 43 + lib/authDataManager/index.js | 103 ++ lib/authDataManager/instagram.js | 43 + lib/authDataManager/linkedin.js | 50 + lib/authDataManager/meetup.js | 49 + lib/authDataManager/spotify.js | 58 + lib/authDataManager/twitter.js | 55 + lib/batch.js | 91 ++ lib/cache.js | 11 + lib/cli/cli-definitions.js | 214 ++++ lib/cli/parse-server.js | 93 ++ lib/cli/utils/commander.js | 148 +++ lib/cloud-code/HTTPResponse.js | 65 + lib/cloud-code/Parse.Cloud.js | 62 + lib/cloud-code/httpRequest.js | 107 ++ lib/cryptoUtils.js | 59 + lib/deprecated.js | 11 + lib/index.js | 53 + lib/logger.js | 112 ++ lib/middlewares.js | 292 +++++ lib/password.js | 41 + lib/pushStatusHandler.js | 130 ++ lib/requiredParameter.js | 9 + lib/rest.js | 140 +++ lib/testing-routes.js | 85 ++ lib/triggers.js | 253 ++++ lib/vendor/mongodbUrl.js | 928 ++++++++++++++ 98 files changed, 17615 insertions(+) create mode 100644 lib/Adapters/AdapterLoader.js create mode 100644 lib/Adapters/Analytics/AnalyticsAdapter.js create mode 100644 lib/Adapters/Cache/CacheAdapter.js create mode 100644 lib/Adapters/Cache/InMemoryCache.js create mode 100644 lib/Adapters/Cache/InMemoryCacheAdapter.js create mode 100644 lib/Adapters/Email/MailAdapter.js create mode 100644 lib/Adapters/Files/FilesAdapter.js create mode 100644 lib/Adapters/Files/GridStoreAdapter.js create mode 100644 lib/Adapters/Logger/FileLoggerAdapter.js create mode 100644 lib/Adapters/Logger/LoggerAdapter.js create mode 100644 lib/Adapters/Push/PushAdapter.js create mode 100644 lib/Adapters/Storage/Mongo/MongoCollection.js create mode 100644 lib/Adapters/Storage/Mongo/MongoSchemaCollection.js create mode 100644 lib/Adapters/Storage/Mongo/MongoStorageAdapter.js create mode 100644 lib/Adapters/Storage/Mongo/MongoTransform.js create mode 100644 lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js create mode 100644 lib/Auth.js create mode 100644 lib/ClientSDK.js create mode 100644 lib/Config.js create mode 100644 lib/Controllers/AdaptableController.js create mode 100644 lib/Controllers/AnalyticsController.js create mode 100644 lib/Controllers/CacheController.js create mode 100644 lib/Controllers/DatabaseController.js create mode 100644 lib/Controllers/FilesController.js create mode 100644 lib/Controllers/HooksController.js create mode 100644 lib/Controllers/LiveQueryController.js create mode 100644 lib/Controllers/LoggerController.js create mode 100644 lib/Controllers/PushController.js create mode 100644 lib/Controllers/SchemaCache.js create mode 100644 lib/Controllers/SchemaController.js create mode 100644 lib/Controllers/UserController.js create mode 100644 lib/DatabaseAdapter.js create mode 100644 lib/LiveQuery/Client.js create mode 100644 lib/LiveQuery/EventEmitterPubSub.js create mode 100644 lib/LiveQuery/Id.js create mode 100644 lib/LiveQuery/PLog.js create mode 100644 lib/LiveQuery/ParseCloudCodePublisher.js create mode 100644 lib/LiveQuery/ParseLiveQueryServer.js create mode 100644 lib/LiveQuery/ParsePubSub.js create mode 100644 lib/LiveQuery/ParseWebSocketServer.js create mode 100644 lib/LiveQuery/QueryTools.js create mode 100644 lib/LiveQuery/RedisPubSub.js create mode 100644 lib/LiveQuery/RequestSchema.js create mode 100644 lib/LiveQuery/SessionTokenCache.js create mode 100644 lib/LiveQuery/Subscription.js create mode 100644 lib/LiveQuery/equalObjects.js create mode 100644 lib/ParseServer.js create mode 100644 lib/PromiseRouter.js create mode 100644 lib/RestQuery.js create mode 100644 lib/RestWrite.js create mode 100644 lib/Routers/AnalyticsRouter.js create mode 100644 lib/Routers/ClassesRouter.js create mode 100644 lib/Routers/FeaturesRouter.js create mode 100644 lib/Routers/FilesRouter.js create mode 100644 lib/Routers/FunctionsRouter.js create mode 100644 lib/Routers/GlobalConfigRouter.js create mode 100644 lib/Routers/HooksRouter.js create mode 100644 lib/Routers/IAPValidationRouter.js create mode 100644 lib/Routers/InstallationsRouter.js create mode 100644 lib/Routers/LogsRouter.js create mode 100644 lib/Routers/PublicAPIRouter.js create mode 100644 lib/Routers/PurgeRouter.js create mode 100644 lib/Routers/PushRouter.js create mode 100644 lib/Routers/RolesRouter.js create mode 100644 lib/Routers/SchemasRouter.js create mode 100644 lib/Routers/SessionsRouter.js create mode 100644 lib/Routers/UsersRouter.js create mode 100644 lib/TestUtils.js create mode 100644 lib/authDataManager/OAuth1Client.js create mode 100644 lib/authDataManager/facebook.js create mode 100644 lib/authDataManager/github.js create mode 100644 lib/authDataManager/google.js create mode 100644 lib/authDataManager/index.js create mode 100644 lib/authDataManager/instagram.js create mode 100644 lib/authDataManager/linkedin.js create mode 100644 lib/authDataManager/meetup.js create mode 100644 lib/authDataManager/spotify.js create mode 100644 lib/authDataManager/twitter.js create mode 100644 lib/batch.js create mode 100644 lib/cache.js create mode 100644 lib/cli/cli-definitions.js create mode 100644 lib/cli/parse-server.js create mode 100644 lib/cli/utils/commander.js create mode 100644 lib/cloud-code/HTTPResponse.js create mode 100644 lib/cloud-code/Parse.Cloud.js create mode 100644 lib/cloud-code/httpRequest.js create mode 100644 lib/cryptoUtils.js create mode 100644 lib/deprecated.js create mode 100644 lib/index.js create mode 100644 lib/logger.js create mode 100644 lib/middlewares.js create mode 100644 lib/password.js create mode 100644 lib/pushStatusHandler.js create mode 100644 lib/requiredParameter.js create mode 100644 lib/rest.js create mode 100644 lib/testing-routes.js create mode 100644 lib/triggers.js create mode 100644 lib/vendor/mongodbUrl.js diff --git a/lib/Adapters/AdapterLoader.js b/lib/Adapters/AdapterLoader.js new file mode 100644 index 0000000000..f2c5f0a12c --- /dev/null +++ b/lib/Adapters/AdapterLoader.js @@ -0,0 +1,43 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.loadAdapter = loadAdapter; +function loadAdapter(adapter, defaultAdapter, options) { + if (!adapter) { + if (!defaultAdapter) { + return options; + } + // Load from the default adapter when no adapter is set + return loadAdapter(defaultAdapter, undefined, options); + } else if (typeof adapter === "function") { + try { + return adapter(options); + } catch (e) { + if (e.name === 'TypeError') { + var Adapter = adapter; + return new Adapter(options); + } else { + throw e; + } + } + } else if (typeof adapter === "string") { + adapter = require(adapter); + // If it's define as a module, get the default + if (adapter.default) { + adapter = adapter.default; + } + return loadAdapter(adapter, undefined, options); + } else if (adapter.module) { + return loadAdapter(adapter.module, undefined, adapter.options); + } else if (adapter.class) { + return loadAdapter(adapter.class, undefined, adapter.options); + } else if (adapter.adapter) { + return loadAdapter(adapter.adapter, undefined, adapter.options); + } + // return the adapter as provided + return adapter; +} + +exports.default = loadAdapter; \ No newline at end of file diff --git a/lib/Adapters/Analytics/AnalyticsAdapter.js b/lib/Adapters/Analytics/AnalyticsAdapter.js new file mode 100644 index 0000000000..3c81bfe4ea --- /dev/null +++ b/lib/Adapters/Analytics/AnalyticsAdapter.js @@ -0,0 +1,31 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var AnalyticsAdapter = exports.AnalyticsAdapter = function () { + function AnalyticsAdapter() { + _classCallCheck(this, AnalyticsAdapter); + } + + _createClass(AnalyticsAdapter, [{ + key: "appOpened", + value: function appOpened(parameters, req) { + return Promise.resolve({}); + } + }, { + key: "trackEvent", + value: function trackEvent(eventName, parameters, req) { + return Promise.resolve({}); + } + }]); + + return AnalyticsAdapter; +}(); + +exports.default = AnalyticsAdapter; \ No newline at end of file diff --git a/lib/Adapters/Cache/CacheAdapter.js b/lib/Adapters/Cache/CacheAdapter.js new file mode 100644 index 0000000000..c0a8466a3e --- /dev/null +++ b/lib/Adapters/Cache/CacheAdapter.js @@ -0,0 +1,56 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var CacheAdapter = exports.CacheAdapter = function () { + function CacheAdapter() { + _classCallCheck(this, CacheAdapter); + } + + _createClass(CacheAdapter, [{ + key: "get", + + /** + * Get a value in the cache + * @param key Cache key to get + * @return Promise that will eventually resolve to the value in the cache. + */ + value: function get(key) {} + + /** + * Set a value in the cache + * @param key Cache key to set + * @param value Value to set the key + * @param ttl Optional TTL + */ + + }, { + key: "put", + value: function put(key, value, ttl) {} + + /** + * Remove a value from the cache. + * @param key Cache key to remove + */ + + }, { + key: "del", + value: function del(key) {} + + /** + * Empty a cache + */ + + }, { + key: "clear", + value: function clear() {} + }]); + + return CacheAdapter; +}(); \ No newline at end of file diff --git a/lib/Adapters/Cache/InMemoryCache.js b/lib/Adapters/Cache/InMemoryCache.js new file mode 100644 index 0000000000..168510719e --- /dev/null +++ b/lib/Adapters/Cache/InMemoryCache.js @@ -0,0 +1,88 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var DEFAULT_CACHE_TTL = 5 * 1000; + +var InMemoryCache = exports.InMemoryCache = function () { + function InMemoryCache(_ref) { + var _ref$ttl = _ref.ttl; + var ttl = _ref$ttl === undefined ? DEFAULT_CACHE_TTL : _ref$ttl; + + _classCallCheck(this, InMemoryCache); + + this.ttl = ttl; + this.cache = Object.create(null); + } + + _createClass(InMemoryCache, [{ + key: "get", + value: function get(key) { + var record = this.cache[key]; + if (record == null) { + return null; + } + + // Has Record and isnt expired + if (isNaN(record.expire) || record.expire >= Date.now()) { + return record.value; + } + + // Record has expired + delete this.cache[key]; + return null; + } + }, { + key: "put", + value: function put(key, value) { + var _this = this; + + var ttl = arguments.length <= 2 || arguments[2] === undefined ? this.ttl : arguments[2]; + + if (ttl < 0 || isNaN(ttl)) { + ttl = NaN; + } + + var record = { + value: value, + expire: ttl + Date.now() + }; + + if (!isNaN(record.expire)) { + record.timeout = setTimeout(function () { + _this.del(key); + }, ttl); + } + + this.cache[key] = record; + } + }, { + key: "del", + value: function del(key) { + var record = this.cache[key]; + if (record == null) { + return; + } + + if (record.timeout) { + clearTimeout(record.timeout); + } + delete this.cache[key]; + } + }, { + key: "clear", + value: function clear() { + this.cache = Object.create(null); + } + }]); + + return InMemoryCache; +}(); + +exports.default = InMemoryCache; \ No newline at end of file diff --git a/lib/Adapters/Cache/InMemoryCacheAdapter.js b/lib/Adapters/Cache/InMemoryCacheAdapter.js new file mode 100644 index 0000000000..fb9b00425d --- /dev/null +++ b/lib/Adapters/Cache/InMemoryCacheAdapter.js @@ -0,0 +1,58 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.InMemoryCacheAdapter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _InMemoryCache = require('./InMemoryCache'); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var InMemoryCacheAdapter = exports.InMemoryCacheAdapter = function () { + function InMemoryCacheAdapter(ctx) { + _classCallCheck(this, InMemoryCacheAdapter); + + this.cache = new _InMemoryCache.InMemoryCache(ctx); + } + + _createClass(InMemoryCacheAdapter, [{ + key: 'get', + value: function get(key) { + var _this = this; + + return new Promise(function (resolve, reject) { + var record = _this.cache.get(key); + if (record == null) { + return resolve(null); + } + + return resolve(JSON.parse(record)); + }); + } + }, { + key: 'put', + value: function put(key, value, ttl) { + this.cache.put(key, JSON.stringify(value), ttl); + return Promise.resolve(); + } + }, { + key: 'del', + value: function del(key) { + this.cache.del(key); + return Promise.resolve(); + } + }, { + key: 'clear', + value: function clear() { + this.cache.clear(); + return Promise.resolve(); + } + }]); + + return InMemoryCacheAdapter; +}(); + +exports.default = InMemoryCacheAdapter; \ No newline at end of file diff --git a/lib/Adapters/Email/MailAdapter.js b/lib/Adapters/Email/MailAdapter.js new file mode 100644 index 0000000000..90bfc2647d --- /dev/null +++ b/lib/Adapters/Email/MailAdapter.js @@ -0,0 +1,43 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/* + Mail Adapter prototype + A MailAdapter should implement at least sendMail() + */ +var MailAdapter = exports.MailAdapter = function () { + function MailAdapter() { + _classCallCheck(this, MailAdapter); + } + + _createClass(MailAdapter, [{ + key: "sendMail", + + /* + * A method for sending mail + * @param options would have the parameters + * - to: the recipient + * - text: the raw text of the message + * - subject: the subject of the email + */ + value: function sendMail(options) {} + + /* You can implement those methods if you want + * to provide HTML templates etc... + */ + // sendVerificationEmail({ link, appName, user }) {} + // sendPasswordResetEmail({ link, appName, user }) {} + + }]); + + return MailAdapter; +}(); + +exports.default = MailAdapter; \ No newline at end of file diff --git a/lib/Adapters/Files/FilesAdapter.js b/lib/Adapters/Files/FilesAdapter.js new file mode 100644 index 0000000000..dba1be21e6 --- /dev/null +++ b/lib/Adapters/Files/FilesAdapter.js @@ -0,0 +1,57 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Files Adapter +// +// Allows you to change the file storage mechanism. +// +// Adapter classes must implement the following functions: +// * createFile(config, filename, data) +// * getFileData(config, filename) +// * getFileLocation(config, request, filename) +// +// Default is GridStoreAdapter, which requires mongo +// and for the API server to be using the DatabaseController with Mongo +// database adapter. + +var FilesAdapter = exports.FilesAdapter = function () { + function FilesAdapter() { + _classCallCheck(this, FilesAdapter); + } + + _createClass(FilesAdapter, [{ + key: "createFile", + + /* this method is responsible to store the file in order to be retrived later by it's file name + * + * @param filename the filename to save + * @param data the buffer of data from the file + * @param contentType the supposed contentType + * @discussion the contentType can be undefined if the controller was not able to determine it + * + * @return a promise that should fail if the storage didn't succeed + * + */ + value: function createFile(filename, data, contentType) {} + }, { + key: "deleteFile", + value: function deleteFile(filename) {} + }, { + key: "getFileData", + value: function getFileData(filename) {} + }, { + key: "getFileLocation", + value: function getFileLocation(config, filename) {} + }]); + + return FilesAdapter; +}(); + +exports.default = FilesAdapter; \ No newline at end of file diff --git a/lib/Adapters/Files/GridStoreAdapter.js b/lib/Adapters/Files/GridStoreAdapter.js new file mode 100644 index 0000000000..6445fb72f6 --- /dev/null +++ b/lib/Adapters/Files/GridStoreAdapter.js @@ -0,0 +1,101 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.GridStoreAdapter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _mongodb = require('mongodb'); + +var _FilesAdapter2 = require('./FilesAdapter'); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** + GridStoreAdapter + Stores files in Mongo using GridStore + Requires the database adapter to be based on mongoclient + + weak + */ + +var DefaultMongoURI = 'mongodb://localhost:27017/parse'; + +var GridStoreAdapter = exports.GridStoreAdapter = function (_FilesAdapter) { + _inherits(GridStoreAdapter, _FilesAdapter); + + function GridStoreAdapter() { + var mongoDatabaseURI = arguments.length <= 0 || arguments[0] === undefined ? DefaultMongoURI : arguments[0]; + + _classCallCheck(this, GridStoreAdapter); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(GridStoreAdapter).call(this)); + + _this._databaseURI = mongoDatabaseURI; + _this._connect(); + return _this; + } + + _createClass(GridStoreAdapter, [{ + key: '_connect', + value: function _connect() { + if (!this._connectionPromise) { + this._connectionPromise = _mongodb.MongoClient.connect(this._databaseURI); + } + return this._connectionPromise; + } + + // For a given config object, filename, and data, store a file + // Returns a promise + + }, { + key: 'createFile', + value: function createFile(filename, data, contentType) { + return this._connect().then(function (database) { + var gridStore = new _mongodb.GridStore(database, filename, 'w'); + return gridStore.open(); + }).then(function (gridStore) { + return gridStore.write(data); + }).then(function (gridStore) { + return gridStore.close(); + }); + } + }, { + key: 'deleteFile', + value: function deleteFile(filename) { + return this._connect().then(function (database) { + var gridStore = new _mongodb.GridStore(database, filename, 'r'); + return gridStore.open(); + }).then(function (gridStore) { + return gridStore.unlink(); + }).then(function (gridStore) { + return gridStore.close(); + }); + } + }, { + key: 'getFileData', + value: function getFileData(filename) { + return this._connect().then(function (database) { + return _mongodb.GridStore.exist(database, filename).then(function () { + var gridStore = new _mongodb.GridStore(database, filename, 'r'); + return gridStore.open(); + }); + }).then(function (gridStore) { + return gridStore.read(); + }); + } + }, { + key: 'getFileLocation', + value: function getFileLocation(config, filename) { + return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename); + } + }]); + + return GridStoreAdapter; +}(_FilesAdapter2.FilesAdapter); + +exports.default = GridStoreAdapter; \ No newline at end of file diff --git a/lib/Adapters/Logger/FileLoggerAdapter.js b/lib/Adapters/Logger/FileLoggerAdapter.js new file mode 100644 index 0000000000..a4d035e9cf --- /dev/null +++ b/lib/Adapters/Logger/FileLoggerAdapter.js @@ -0,0 +1,157 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FileLoggerAdapter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _LoggerAdapter2 = require('./LoggerAdapter'); + +var _winston = require('winston'); + +var _winston2 = _interopRequireDefault(_winston); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _node = require('parse/node'); + +var _logger = require('../../logger'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // Logger +// +// Wrapper around Winston logging library with custom query +// +// expected log entry to be in the shape of: +// {"level":"info","message":"Your Message","timestamp":"2016-02-04T05:59:27.412Z"} +// + + +var MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; +var CACHE_TIME = 1000 * 60; + +var LOGS_FOLDER = './logs/'; + +if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { + LOGS_FOLDER = './test_logs/'; +} + +var currentDate = new Date(); + +var simpleCache = { + timestamp: null, + from: null, + until: null, + order: null, + data: [], + level: 'info' +}; + +// returns Date object rounded to nearest day +var _getNearestDay = function _getNearestDay(date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); +}; + +// returns Date object of previous day +var _getPrevDay = function _getPrevDay(date) { + return new Date(date - MILLISECONDS_IN_A_DAY); +}; + +// returns the iso formatted file name +var _getFileName = function _getFileName() { + return _getNearestDay(currentDate).toISOString(); +}; + +// check for valid cache when both from and util match. +// cache valid for up to 1 minute +var _hasValidCache = function _hasValidCache(from, until, level) { + if (String(from) === String(simpleCache.from) && String(until) === String(simpleCache.until) && new Date() - simpleCache.timestamp < CACHE_TIME && level === simpleCache.level) { + return true; + } + return false; +}; + +// check that log entry has valid time stamp based on query +var _isValidLogEntry = function _isValidLogEntry(from, until, entry) { + var _entry = JSON.parse(entry), + timestamp = new Date(_entry.timestamp); + return timestamp >= from && timestamp <= until ? true : false; +}; + +var FileLoggerAdapter = exports.FileLoggerAdapter = function (_LoggerAdapter) { + _inherits(FileLoggerAdapter, _LoggerAdapter); + + function FileLoggerAdapter() { + _classCallCheck(this, FileLoggerAdapter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(FileLoggerAdapter).apply(this, arguments)); + } + + _createClass(FileLoggerAdapter, [{ + key: 'info', + value: function info() { + return _logger.logger.info.apply(undefined, arguments); + } + }, { + key: 'error', + value: function error() { + return _logger.logger.error.apply(undefined, arguments); + } + + // custom query as winston is currently limited + + }, { + key: 'query', + value: function query(options) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; + + if (!options) { + options = {}; + } + // defaults to 7 days prior + var from = options.from || new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY); + var until = options.until || new Date(); + var limit = options.size || 10; + var order = options.order || 'desc'; + var level = options.level || 'info'; + var roundedUntil = _getNearestDay(until); + var roundedFrom = _getNearestDay(from); + + var options = { + from: from, + until: until, + limit: limit, + order: order + }; + + return new Promise(function (resolve, reject) { + _logger.logger.query(options, function (err, res) { + if (err) { + callback(err); + return reject(err); + } + if (level == 'error') { + callback(res['parse-server-error']); + resolve(res['parse-server-error']); + } else { + callback(res['parse-server']); + resolve(res['parse-server']); + } + }); + }); + } + }]); + + return FileLoggerAdapter; +}(_LoggerAdapter2.LoggerAdapter); + +exports.default = FileLoggerAdapter; \ No newline at end of file diff --git a/lib/Adapters/Logger/LoggerAdapter.js b/lib/Adapters/Logger/LoggerAdapter.js new file mode 100644 index 0000000000..5def44f089 --- /dev/null +++ b/lib/Adapters/Logger/LoggerAdapter.js @@ -0,0 +1,40 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Logger Adapter +// +// Allows you to change the logger mechanism +// +// Adapter classes must implement the following functions: +// * info(obj1 [, obj2, .., objN]) +// * error(obj1 [, obj2, .., objN]) +// * query(options, callback) +// Default is FileLoggerAdapter.js + +var LoggerAdapter = exports.LoggerAdapter = function () { + function LoggerAdapter() { + _classCallCheck(this, LoggerAdapter); + } + + _createClass(LoggerAdapter, [{ + key: "info", + value: function info() {} + }, { + key: "error", + value: function error() {} + }, { + key: "query", + value: function query(options, callback) {} + }]); + + return LoggerAdapter; +}(); + +exports.default = LoggerAdapter; \ No newline at end of file diff --git a/lib/Adapters/Push/PushAdapter.js b/lib/Adapters/Push/PushAdapter.js new file mode 100644 index 0000000000..4ff7e54821 --- /dev/null +++ b/lib/Adapters/Push/PushAdapter.js @@ -0,0 +1,44 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Push Adapter +// +// Allows you to change the push notification mechanism. +// +// Adapter classes must implement the following functions: +// * getValidPushTypes() +// * send(devices, installations, pushStatus) +// +// Default is ParsePushAdapter, which uses GCM for +// android push and APNS for ios push. + +var PushAdapter = exports.PushAdapter = function () { + function PushAdapter() { + _classCallCheck(this, PushAdapter); + } + + _createClass(PushAdapter, [{ + key: "send", + value: function send(devices, installations, pushStatus) {} + + /** + * Get an array of valid push types. + * @returns {Array} An array of valid push types + */ + + }, { + key: "getValidPushTypes", + value: function getValidPushTypes() {} + }]); + + return PushAdapter; +}(); + +exports.default = PushAdapter; \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoCollection.js b/lib/Adapters/Storage/Mongo/MongoCollection.js new file mode 100644 index 0000000000..b2a71c553e --- /dev/null +++ b/lib/Adapters/Storage/Mongo/MongoCollection.js @@ -0,0 +1,141 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var mongodb = require('mongodb'); +var Collection = mongodb.Collection; + +var MongoCollection = function () { + function MongoCollection(mongoCollection) { + _classCallCheck(this, MongoCollection); + + this._mongoCollection = mongoCollection; + } + + // Does a find with "smart indexing". + // Currently this just means, if it needs a geoindex and there is + // none, then build the geoindex. + // This could be improved a lot but it's not clear if that's a good + // idea. Or even if this behavior is a good idea. + + + _createClass(MongoCollection, [{ + key: 'find', + value: function find(query) { + var _this = this; + + var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var skip = _ref.skip; + var limit = _ref.limit; + var sort = _ref.sort; + + return this._rawFind(query, { skip: skip, limit: limit, sort: sort }).catch(function (error) { + // Check for "no geoindex" error + if (error.code != 17007 && !error.message.match(/unable to find index for .geoNear/)) { + throw error; + } + // Figure out what key needs an index + var key = error.message.match(/field=([A-Za-z_0-9]+) /)[1]; + if (!key) { + throw error; + } + + var index = {}; + index[key] = '2d'; + return _this._mongoCollection.createIndex(index) + // Retry, but just once. + .then(function () { + return _this._rawFind(query, { skip: skip, limit: limit, sort: sort }); + }); + }); + } + }, { + key: '_rawFind', + value: function _rawFind(query) { + var _ref2 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var skip = _ref2.skip; + var limit = _ref2.limit; + var sort = _ref2.sort; + + return this._mongoCollection.find(query, { skip: skip, limit: limit, sort: sort }).toArray(); + } + }, { + key: 'count', + value: function count(query) { + var _ref3 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var skip = _ref3.skip; + var limit = _ref3.limit; + var sort = _ref3.sort; + + return this._mongoCollection.count(query, { skip: skip, limit: limit, sort: sort }); + } + }, { + key: 'insertOne', + value: function insertOne(object) { + return this._mongoCollection.insertOne(object); + } + + // Atomically updates data in the database for a single (first) object that matched the query + // If there is nothing that matches the query - does insert + // Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5. + + }, { + key: 'upsertOne', + value: function upsertOne(query, update) { + return this._mongoCollection.update(query, update, { upsert: true }); + } + }, { + key: 'updateOne', + value: function updateOne(query, update) { + return this._mongoCollection.updateOne(query, update); + } + }, { + key: 'updateMany', + value: function updateMany(query, update) { + return this._mongoCollection.updateMany(query, update); + } + }, { + key: 'deleteOne', + value: function deleteOne(query) { + return this._mongoCollection.deleteOne(query); + } + }, { + key: 'deleteMany', + value: function deleteMany(query) { + return this._mongoCollection.deleteMany(query); + } + }, { + key: '_ensureSparseUniqueIndexInBackground', + value: function _ensureSparseUniqueIndexInBackground(indexRequest) { + var _this2 = this; + + return new Promise(function (resolve, reject) { + _this2._mongoCollection.ensureIndex(indexRequest, { unique: true, background: true, sparse: true }, function (error, indexName) { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + } + }, { + key: 'drop', + value: function drop() { + return this._mongoCollection.drop(); + } + }]); + + return MongoCollection; +}(); + +exports.default = MongoCollection; \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoSchemaCollection.js b/lib/Adapters/Storage/Mongo/MongoSchemaCollection.js new file mode 100644 index 0000000000..48fb4192fe --- /dev/null +++ b/lib/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -0,0 +1,240 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _MongoCollection = require('./MongoCollection'); + +var _MongoCollection2 = _interopRequireDefault(_MongoCollection); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function mongoFieldToParseSchemaField(type) { + if (type[0] === '*') { + return { + type: 'Pointer', + targetClass: type.slice(1) + }; + } + if (type.startsWith('relation<')) { + return { + type: 'Relation', + targetClass: type.slice('relation<'.length, type.length - 1) + }; + } + switch (type) { + case 'number': + return { type: 'Number' }; + case 'string': + return { type: 'String' }; + case 'boolean': + return { type: 'Boolean' }; + case 'date': + return { type: 'Date' }; + case 'map': + case 'object': + return { type: 'Object' }; + case 'array': + return { type: 'Array' }; + case 'geopoint': + return { type: 'GeoPoint' }; + case 'file': + return { type: 'File' }; + case 'bytes': + return { type: 'Bytes' }; + } +} + +var nonFieldSchemaKeys = ['_id', '_metadata', '_client_permissions']; +function mongoSchemaFieldsToParseSchemaFields(schema) { + var fieldNames = Object.keys(schema).filter(function (key) { + return nonFieldSchemaKeys.indexOf(key) === -1; + }); + var response = fieldNames.reduce(function (obj, fieldName) { + obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]); + return obj; + }, {}); + response.ACL = { type: 'ACL' }; + response.createdAt = { type: 'Date' }; + response.updatedAt = { type: 'Date' }; + response.objectId = { type: 'String' }; + return response; +} + +var emptyCLPS = Object.freeze({ + find: {}, + get: {}, + create: {}, + update: {}, + delete: {}, + addField: {} +}); + +var defaultCLPS = Object.freeze({ + find: { '*': true }, + get: { '*': true }, + create: { '*': true }, + update: { '*': true }, + delete: { '*': true }, + addField: { '*': true } +}); + +function mongoSchemaToParseSchema(mongoSchema) { + var clps = defaultCLPS; + if (mongoSchema._metadata && mongoSchema._metadata.class_permissions) { + clps = _extends({}, emptyCLPS, mongoSchema._metadata.class_permissions); + } + return { + className: mongoSchema._id, + fields: mongoSchemaFieldsToParseSchemaFields(mongoSchema), + classLevelPermissions: clps + }; +} + +function _mongoSchemaQueryFromNameQuery(name, query) { + var object = { _id: name }; + if (query) { + Object.keys(query).forEach(function (key) { + object[key] = query[key]; + }); + } + return object; +} + +// Returns a type suitable for inserting into mongo _SCHEMA collection. +// Does no validation. That is expected to be done in Parse Server. +function parseFieldTypeToMongoFieldType(_ref) { + var type = _ref.type; + var targetClass = _ref.targetClass; + + switch (type) { + case 'Pointer': + return '*' + targetClass; + case 'Relation': + return 'relation<' + targetClass + '>'; + case 'Number': + return 'number'; + case 'String': + return 'string'; + case 'Boolean': + return 'boolean'; + case 'Date': + return 'date'; + case 'Object': + return 'object'; + case 'Array': + return 'array'; + case 'GeoPoint': + return 'geopoint'; + case 'File': + return 'file'; + } +} + +var MongoSchemaCollection = function () { + function MongoSchemaCollection(collection) { + _classCallCheck(this, MongoSchemaCollection); + + this._collection = collection; + } + + _createClass(MongoSchemaCollection, [{ + key: '_fetchAllSchemasFrom_SCHEMA', + value: function _fetchAllSchemasFrom_SCHEMA() { + return this._collection._rawFind({}).then(function (schemas) { + return schemas.map(mongoSchemaToParseSchema); + }); + } + }, { + key: '_fechOneSchemaFrom_SCHEMA', + value: function _fechOneSchemaFrom_SCHEMA(name) { + return this._collection._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 }).then(function (results) { + if (results.length === 1) { + return mongoSchemaToParseSchema(results[0]); + } else { + throw undefined; + } + }); + } + + // Atomically find and delete an object based on query. + + }, { + key: 'findAndDeleteSchema', + value: function findAndDeleteSchema(name) { + return this._collection._mongoCollection.findAndRemove(_mongoSchemaQueryFromNameQuery(name), []); + } + }, { + key: 'updateSchema', + value: function updateSchema(name, update) { + return this._collection.updateOne(_mongoSchemaQueryFromNameQuery(name), update); + } + }, { + key: 'upsertSchema', + value: function upsertSchema(name, query, update) { + return this._collection.upsertOne(_mongoSchemaQueryFromNameQuery(name, query), update); + } + + // Add a field to the schema. If database does not support the field + // type (e.g. mongo doesn't support more than one GeoPoint in a class) reject with an "Incorrect Type" + // Parse error with a desciptive message. If the field already exists, this function must + // not modify the schema, and must reject with DUPLICATE_VALUE error. + // If this is called for a class that doesn't exist, this function must create that class. + + // TODO: throw an error if an unsupported field type is passed. Deciding whether a type is supported + // should be the job of the adapter. Some adapters may not support GeoPoint at all. Others may + // Support additional types that Mongo doesn't, like Money, or something. + + // TODO: don't spend an extra query on finding the schema if the type we are trying to add isn't a GeoPoint. + + }, { + key: 'addFieldIfNotExists', + value: function addFieldIfNotExists(className, fieldName, type) { + var _this = this; + + return this._fechOneSchemaFrom_SCHEMA(className).then(function (schema) { + // The schema exists. Check for existing GeoPoints. + if (type.type === 'GeoPoint') { + // Make sure there are not other geopoint fields + if (Object.keys(schema.fields).some(function (existingField) { + return schema.fields[existingField].type === 'GeoPoint'; + })) { + throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.'); + } + } + return; + }, function (error) { + // If error is undefined, the schema doesn't exist, and we can create the schema with the field. + // If some other error, reject with it. + if (error === undefined) { + return; + } + throw error; + }).then(function () { + // We use $exists and $set to avoid overwriting the field type if it + // already exists. (it could have added inbetween the last query and the update) + return _this.upsertSchema(className, _defineProperty({}, fieldName, { '$exists': false }), { '$set': _defineProperty({}, fieldName, parseFieldTypeToMongoFieldType(type)) }); + }); + } + }]); + + return MongoSchemaCollection; +}(); + +// Exported for testing reasons and because we haven't moved all mongo schema format +// related logic into the database adapter yet. + + +MongoSchemaCollection._TESTmongoSchemaToParseSchema = mongoSchemaToParseSchema; +MongoSchemaCollection.parseFieldTypeToMongoFieldType = parseFieldTypeToMongoFieldType; + +exports.default = MongoSchemaCollection; \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoStorageAdapter.js b/lib/Adapters/Storage/Mongo/MongoStorageAdapter.js new file mode 100644 index 0000000000..6f79243ca0 --- /dev/null +++ b/lib/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -0,0 +1,492 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MongoStorageAdapter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _MongoCollection = require('./MongoCollection'); + +var _MongoCollection2 = _interopRequireDefault(_MongoCollection); + +var _MongoSchemaCollection = require('./MongoSchemaCollection'); + +var _MongoSchemaCollection2 = _interopRequireDefault(_MongoSchemaCollection); + +var _mongodbUrl = require('../../../vendor/mongodbUrl'); + +var _MongoTransform = require('./MongoTransform'); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } + +var mongodb = require('mongodb'); +var MongoClient = mongodb.MongoClient; + +var MongoSchemaCollectionName = '_SCHEMA'; +var DefaultMongoURI = 'mongodb://localhost:27017/parse'; + +var storageAdapterAllCollections = function storageAdapterAllCollections(mongoAdapter) { + return mongoAdapter.connect().then(function () { + return mongoAdapter.database.collections(); + }).then(function (collections) { + return collections.filter(function (collection) { + if (collection.namespace.match(/\.system\./)) { + return false; + } + // TODO: If you have one app with a collection prefix that happens to be a prefix of another + // apps prefix, this will go very very badly. We should fix that somehow. + return collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0; + }); + }); +}; + +var convertParseSchemaToMongoSchema = function convertParseSchemaToMongoSchema(_ref) { + var schema = _objectWithoutProperties(_ref, []); + + delete schema.fields._rperm; + delete schema.fields._wperm; + + if (schema.className === '_User') { + // Legacy mongo adapter knows about the difference between password and _hashed_password. + // Future database adapters will only know about _hashed_password. + // Note: Parse Server will bring back password with injectDefaultSchema, so we don't need + // to add _hashed_password back ever. + delete schema.fields._hashed_password; + } + + return schema; +}; + +// Returns { code, error } if invalid, or { result }, an object +// suitable for inserting into _SCHEMA collection, otherwise. +var mongoSchemaFromFieldsAndClassNameAndCLP = function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions) { + var mongoObject = { + _id: className, + objectId: 'string', + updatedAt: 'string', + createdAt: 'string' + }; + + for (var fieldName in fields) { + mongoObject[fieldName] = _MongoSchemaCollection2.default.parseFieldTypeToMongoFieldType(fields[fieldName]); + } + + if (typeof classLevelPermissions !== 'undefined') { + mongoObject._metadata = mongoObject._metadata || {}; + if (!classLevelPermissions) { + delete mongoObject._metadata.class_permissions; + } else { + mongoObject._metadata.class_permissions = classLevelPermissions; + } + } + + return mongoObject; +}; + +var MongoStorageAdapter = exports.MongoStorageAdapter = function () { + // Public + function MongoStorageAdapter(_ref2) { + var _ref2$uri = _ref2.uri; + var uri = _ref2$uri === undefined ? DefaultMongoURI : _ref2$uri; + var _ref2$collectionPrefi = _ref2.collectionPrefix; + var collectionPrefix = _ref2$collectionPrefi === undefined ? '' : _ref2$collectionPrefi; + var _ref2$mongoOptions = _ref2.mongoOptions; + var mongoOptions = _ref2$mongoOptions === undefined ? {} : _ref2$mongoOptions; + + _classCallCheck(this, MongoStorageAdapter); + + this._uri = uri; + this._collectionPrefix = collectionPrefix; + this._mongoOptions = mongoOptions; + } + // Private + + + _createClass(MongoStorageAdapter, [{ + key: 'connect', + value: function connect() { + var _this = this; + + if (this.connectionPromise) { + return this.connectionPromise; + } + + // parsing and re-formatting causes the auth value (if there) to get URI + // encoded + var encodedUri = (0, _mongodbUrl.format)((0, _mongodbUrl.parse)(this._uri)); + + this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(function (database) { + if (!database) { + delete _this.connectionPromise; + return; + } + database.on('error', function (error) { + delete _this.connectionPromise; + }); + database.on('close', function (error) { + delete _this.connectionPromise; + }); + _this.database = database; + }).catch(function (err) { + delete _this.connectionPromise; + return Promise.reject(err); + }); + + return this.connectionPromise; + } + }, { + key: '_adaptiveCollection', + value: function _adaptiveCollection(name) { + var _this2 = this; + + return this.connect().then(function () { + return _this2.database.collection(_this2._collectionPrefix + name); + }).then(function (rawCollection) { + return new _MongoCollection2.default(rawCollection); + }); + } + }, { + key: '_schemaCollection', + value: function _schemaCollection() { + var _this3 = this; + + return this.connect().then(function () { + return _this3._adaptiveCollection(MongoSchemaCollectionName); + }).then(function (collection) { + return new _MongoSchemaCollection2.default(collection); + }); + } + }, { + key: 'classExists', + value: function classExists(name) { + var _this4 = this; + + return this.connect().then(function () { + return _this4.database.listCollections({ name: _this4._collectionPrefix + name }).toArray(); + }).then(function (collections) { + return collections.length > 0; + }); + } + }, { + key: 'setClassLevelPermissions', + value: function setClassLevelPermissions(className, CLPs) { + return this._schemaCollection().then(function (schemaCollection) { + return schemaCollection.updateSchema(className, { + $set: { _metadata: { class_permissions: CLPs } } + }); + }); + } + }, { + key: 'createClass', + value: function createClass(className, schema) { + schema = convertParseSchemaToMongoSchema(schema); + var mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions); + mongoObject._id = className; + return this._schemaCollection().then(function (schemaCollection) { + return schemaCollection._collection.insertOne(mongoObject); + }).then(function (result) { + return _MongoSchemaCollection2.default._TESTmongoSchemaToParseSchema(result.ops[0]); + }).catch(function (error) { + if (error.code === 11000) { + //Mongo's duplicate key error + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.'); + } else { + throw error; + } + }); + } + }, { + key: 'addFieldIfNotExists', + value: function addFieldIfNotExists(className, fieldName, type) { + return this._schemaCollection().then(function (schemaCollection) { + return schemaCollection.addFieldIfNotExists(className, fieldName, type); + }); + } + + // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) + // and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible. + + }, { + key: 'deleteClass', + value: function deleteClass(className) { + var _this5 = this; + + return this._adaptiveCollection(className).then(function (collection) { + return collection.drop(); + }).catch(function (error) { + // 'ns not found' means collection was already gone. Ignore deletion attempt. + if (error.message == 'ns not found') { + return; + } + throw error; + }) + // We've dropped the collection, now remove the _SCHEMA document + .then(function () { + return _this5._schemaCollection(); + }).then(function (schemaCollection) { + return schemaCollection.findAndDeleteSchema(className); + }); + } + + // Delete all data known to this adatper. Used for testing. + + }, { + key: 'deleteAllClasses', + value: function deleteAllClasses() { + return storageAdapterAllCollections(this).then(function (collections) { + return Promise.all(collections.map(function (collection) { + return collection.drop(); + })); + }); + } + + // Remove the column and all the data. For Relations, the _Join collection is handled + // specially, this function does not delete _Join columns. It should, however, indicate + // that the relation fields does not exist anymore. In mongo, this means removing it from + // the _SCHEMA collection. There should be no actual data in the collection under the same name + // as the relation column, so it's fine to attempt to delete it. If the fields listed to be + // deleted do not exist, this function should return successfully anyways. Checking for + // attempts to delete non-existent fields is the responsibility of Parse Server. + + // Pointer field names are passed for legacy reasons: the original mongo + // format stored pointer field names differently in the database, and therefore + // needed to know the type of the field before it could delete it. Future database + // adatpers should ignore the pointerFieldNames argument. All the field names are in + // fieldNames, they show up additionally in the pointerFieldNames database for use + // by the mongo adapter, which deals with the legacy mongo format. + + // This function is not obligated to delete fields atomically. It is given the field + // names in a list so that databases that are capable of deleting fields atomically + // may do so. + + // Returns a Promise. + + }, { + key: 'deleteFields', + value: function deleteFields(className, schema, fieldNames) { + var _this6 = this; + + var mongoFormatNames = fieldNames.map(function (fieldName) { + if (schema.fields[fieldName].type === 'Pointer') { + return '_p_' + fieldName; + } else { + return fieldName; + } + }); + var collectionUpdate = { '$unset': {} }; + mongoFormatNames.forEach(function (name) { + collectionUpdate['$unset'][name] = null; + }); + + var schemaUpdate = { '$unset': {} }; + fieldNames.forEach(function (name) { + schemaUpdate['$unset'][name] = null; + }); + + return this._adaptiveCollection(className).then(function (collection) { + return collection.updateMany({}, collectionUpdate); + }).then(function () { + return _this6._schemaCollection(); + }).then(function (schemaCollection) { + return schemaCollection.updateSchema(className, schemaUpdate); + }); + } + + // Return a promise for all schemas known to this adapter, in Parse format. In case the + // schemas cannot be retrieved, returns a promise that rejects. Requirements for the + // rejection reason are TBD. + + }, { + key: 'getAllClasses', + value: function getAllClasses() { + return this._schemaCollection().then(function (schemasCollection) { + return schemasCollection._fetchAllSchemasFrom_SCHEMA(); + }); + } + + // Return a promise for the schema with the given name, in Parse format. If + // this adapter doesn't know about the schema, return a promise that rejects with + // undefined as the reason. + + }, { + key: 'getClass', + value: function getClass(className) { + return this._schemaCollection().then(function (schemasCollection) { + return schemasCollection._fechOneSchemaFrom_SCHEMA(className); + }); + } + + // TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema, + // and should infer from the type. Or maybe does need the schema for validations. Or maybe needs + // the schem only for the legacy mongo format. We'll figure that out later. + + }, { + key: 'createObject', + value: function createObject(className, schema, object) { + schema = convertParseSchemaToMongoSchema(schema); + var mongoObject = (0, _MongoTransform.parseObjectToMongoObjectForCreate)(className, object, schema); + return this._adaptiveCollection(className).then(function (collection) { + return collection.insertOne(mongoObject); + }).catch(function (error) { + if (error.code === 11000) { + // Duplicate value + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided'); + } + throw error; + }); + } + + // Remove all objects that match the given Parse Query. + // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. + // If there is some other error, reject with INTERNAL_SERVER_ERROR. + + }, { + key: 'deleteObjectsByQuery', + value: function deleteObjectsByQuery(className, schema, query) { + schema = convertParseSchemaToMongoSchema(schema); + return this._adaptiveCollection(className).then(function (collection) { + var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); + return collection.deleteMany(mongoWhere); + }).then(function (_ref3) { + var result = _ref3.result; + + if (result.n === 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } + return Promise.resolve(); + }, function (error) { + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error'); + }); + } + + // Apply the update to all objects that match the given Parse Query. + + }, { + key: 'updateObjectsByQuery', + value: function updateObjectsByQuery(className, schema, query, update) { + schema = convertParseSchemaToMongoSchema(schema); + var mongoUpdate = (0, _MongoTransform.transformUpdate)(className, update, schema); + var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); + return this._adaptiveCollection(className).then(function (collection) { + return collection.updateMany(mongoWhere, mongoUpdate); + }); + } + + // Atomically finds and updates an object based on query. + // Return value not currently well specified. + + }, { + key: 'findOneAndUpdate', + value: function findOneAndUpdate(className, schema, query, update) { + schema = convertParseSchemaToMongoSchema(schema); + var mongoUpdate = (0, _MongoTransform.transformUpdate)(className, update, schema); + var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); + return this._adaptiveCollection(className).then(function (collection) { + return collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }); + }).then(function (result) { + return result.value; + }); + } + + // Hopefully we can get rid of this. It's only used for config and hooks. + + }, { + key: 'upsertOneObject', + value: function upsertOneObject(className, schema, query, update) { + schema = convertParseSchemaToMongoSchema(schema); + var mongoUpdate = (0, _MongoTransform.transformUpdate)(className, update, schema); + var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); + return this._adaptiveCollection(className).then(function (collection) { + return collection.upsertOne(mongoWhere, mongoUpdate); + }); + } + + // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. + + }, { + key: 'find', + value: function find(className, schema, query, _ref4) { + var skip = _ref4.skip; + var limit = _ref4.limit; + var sort = _ref4.sort; + + schema = convertParseSchemaToMongoSchema(schema); + var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); + var mongoSort = _lodash2.default.mapKeys(sort, function (value, fieldName) { + return (0, _MongoTransform.transformKey)(className, fieldName, schema); + }); + return this._adaptiveCollection(className).then(function (collection) { + return collection.find(mongoWhere, { skip: skip, limit: limit, sort: mongoSort }); + }).then(function (objects) { + return objects.map(function (object) { + return (0, _MongoTransform.mongoObjectToParseObject)(className, object, schema); + }); + }); + } + + // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't + // currently know which fields are nullable and which aren't, we ignore that criteria. + // As such, we shouldn't expose this function to users of parse until we have an out-of-band + // Way of determining if a field is nullable. Undefined doesn't count against uniqueness, + // which is why we use sparse indexes. + + }, { + key: 'ensureUniqueness', + value: function ensureUniqueness(className, schema, fieldNames) { + schema = convertParseSchemaToMongoSchema(schema); + var indexCreationRequest = {}; + var mongoFieldNames = fieldNames.map(function (fieldName) { + return (0, _MongoTransform.transformKey)(className, fieldName, schema); + }); + mongoFieldNames.forEach(function (fieldName) { + indexCreationRequest[fieldName] = 1; + }); + return this._adaptiveCollection(className).then(function (collection) { + return collection._ensureSparseUniqueIndexInBackground(indexCreationRequest); + }).catch(function (error) { + if (error.code === 11000) { + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.'); + } else { + throw error; + } + }); + } + + // Used in tests + + }, { + key: '_rawFind', + value: function _rawFind(className, query) { + return this._adaptiveCollection(className).then(function (collection) { + return collection.find(query); + }); + } + + // Executs a count. + + }, { + key: 'count', + value: function count(className, schema, query) { + schema = convertParseSchemaToMongoSchema(schema); + return this._adaptiveCollection(className).then(function (collection) { + return collection.count((0, _MongoTransform.transformWhere)(className, query, schema)); + }); + } + }]); + + return MongoStorageAdapter; +}(); + +exports.default = MongoStorageAdapter; + +module.exports = MongoStorageAdapter; // Required for tests \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoTransform.js b/lib/Adapters/Storage/Mongo/MongoTransform.js new file mode 100644 index 0000000000..111bb49a8e --- /dev/null +++ b/lib/Adapters/Storage/Mongo/MongoTransform.js @@ -0,0 +1,945 @@ +'use strict'; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _logger = require('../../../logger'); + +var _logger2 = _interopRequireDefault(_logger); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var mongodb = require('mongodb'); +var Parse = require('parse/node').Parse; + +var transformKey = function transformKey(className, fieldName, schema) { + // Check if the schema is known since it's a built-in field. + switch (fieldName) { + case 'objectId': + return '_id'; + case 'createdAt': + return '_created_at'; + case 'updatedAt': + return '_updated_at'; + case 'sessionToken': + return '_session_token'; + } + + if (schema.fields[fieldName] && schema.fields[fieldName].__type == 'Pointer') { + fieldName = '_p_' + fieldName; + } + + return fieldName; +}; + +var transformKeyValueForUpdate = function transformKeyValueForUpdate(className, restKey, restValue, parseFormatSchema) { + // Check if the schema is known since it's a built-in field. + var key = restKey; + var timeField = false; + switch (key) { + case 'objectId': + case '_id': + key = '_id'; + break; + case 'createdAt': + case '_created_at': + key = '_created_at'; + timeField = true; + break; + case 'updatedAt': + case '_updated_at': + key = '_updated_at'; + timeField = true; + break; + case 'sessionToken': + case '_session_token': + key = '_session_token'; + break; + case 'expiresAt': + case '_expiresAt': + key = 'expiresAt'; + timeField = true; + break; + case '_email_verify_token_expires_at': + key = '_email_verify_token_expires_at'; + timeField = true; + break; + case '_rperm': + case '_wperm': + return { key: key, value: restValue }; + break; + } + + if (parseFormatSchema.fields[key] && parseFormatSchema.fields[key].type === 'Pointer' || !parseFormatSchema.fields[key] && restValue && restValue.__type == 'Pointer') { + key = '_p_' + key; + } + + // Handle atomic values + var value = transformTopLevelAtom(restValue); + if (value !== CannotTransform) { + if (timeField && typeof value === 'string') { + value = new Date(value); + } + return { key: key, value: value }; + } + + // Handle arrays + if (restValue instanceof Array) { + value = restValue.map(transformInteriorValue); + return { key: key, value: value }; + } + + // Handle update operators + if ((typeof restValue === 'undefined' ? 'undefined' : _typeof(restValue)) === 'object' && '__op' in restValue) { + return { key: key, value: transformUpdateOperator(restValue, false) }; + } + + // Handle normal objects by recursing + value = _lodash2.default.mapValues(restValue, transformInteriorValue); + return { key: key, value: value }; +}; + +var transformInteriorValue = function transformInteriorValue(restValue) { + if (restValue !== null && (typeof restValue === 'undefined' ? 'undefined' : _typeof(restValue)) === 'object' && Object.keys(restValue).some(function (key) { + return key.includes('$') || key.includes('.'); + })) { + throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters"); + } + // Handle atomic values + var value = transformInteriorAtom(restValue); + if (value !== CannotTransform) { + return value; + } + + // Handle arrays + if (restValue instanceof Array) { + return restValue.map(transformInteriorValue); + } + + // Handle update operators + if ((typeof restValue === 'undefined' ? 'undefined' : _typeof(restValue)) === 'object' && '__op' in restValue) { + return transformUpdateOperator(restValue, true); + } + + // Handle normal objects by recursing + return _lodash2.default.mapValues(restValue, transformInteriorValue); +}; + +var valueAsDate = function valueAsDate(value) { + if (typeof value === 'string') { + return new Date(value); + } else if (value instanceof Date) { + return value; + } + return false; +}; + +function transformQueryKeyValue(className, key, value, schema) { + switch (key) { + case 'createdAt': + if (valueAsDate(value)) { + return { key: '_created_at', value: valueAsDate(value) }; + } + key = '_created_at'; + break; + case 'updatedAt': + if (valueAsDate(value)) { + return { key: '_updated_at', value: valueAsDate(value) }; + } + key = '_updated_at'; + break; + case 'expiresAt': + if (valueAsDate(value)) { + return { key: 'expiresAt', value: valueAsDate(value) }; + } + break; + case '_email_verify_token_expires_at': + if (valueAsDate(value)) { + return { key: '_email_verify_token_expires_at', value: valueAsDate(value) }; + } + break; + case 'objectId': + return { key: '_id', value: value }; + case 'sessionToken': + return { key: '_session_token', value: value }; + case '_rperm': + case '_wperm': + case '_perishable_token': + case '_email_verify_token': + return { key: key, value: value }; + case '$or': + return { key: '$or', value: value.map(function (subQuery) { + return transformWhere(className, subQuery, schema); + }) }; + case '$and': + return { key: '$and', value: value.map(function (subQuery) { + return transformWhere(className, subQuery, schema); + }) }; + default: + // Other auth data + var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); + if (authDataMatch) { + var provider = authDataMatch[1]; + // Special-case auth data. + return { key: '_auth_data_' + provider + '.id', value: value }; + } + } + + var expectedTypeIsArray = schema && schema.fields[key] && schema.fields[key].type === 'Array'; + + var expectedTypeIsPointer = schema && schema.fields[key] && schema.fields[key].type === 'Pointer'; + + if (expectedTypeIsPointer || !schema && value && value.__type === 'Pointer') { + key = '_p_' + key; + } + + // Handle query constraints + if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) { + return { key: key, value: transformConstraint(value, expectedTypeIsArray) }; + } + + if (expectedTypeIsArray && !(value instanceof Array)) { + return { key: key, value: { '$all': [value] } }; + } + + // Handle atomic values + if (transformTopLevelAtom(value) !== CannotTransform) { + return { key: key, value: transformTopLevelAtom(value) }; + } else { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'You cannot use ' + value + ' as a query parameter.'); + } +} + +// Main exposed method to help run queries. +// restWhere is the "where" clause in REST API form. +// Returns the mongo form of the query. +function transformWhere(className, restWhere, schema) { + var mongoWhere = {}; + for (var restKey in restWhere) { + var out = transformQueryKeyValue(className, restKey, restWhere[restKey], schema); + mongoWhere[out.key] = out.value; + } + return mongoWhere; +} + +var parseObjectKeyValueToMongoObjectKeyValue = function parseObjectKeyValueToMongoObjectKeyValue(restKey, restValue, schema) { + // Check if the schema is known since it's a built-in field. + var transformedValue = void 0; + var coercedToDate = void 0; + switch (restKey) { + case 'objectId': + return { key: '_id', value: restValue }; + case 'expiresAt': + transformedValue = transformTopLevelAtom(restValue); + coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue; + return { key: 'expiresAt', value: coercedToDate }; + case '_email_verify_token_expires_at': + transformedValue = transformTopLevelAtom(restValue); + coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue; + return { key: '_email_verify_token_expires_at', value: coercedToDate }; + case '_rperm': + case '_wperm': + case '_email_verify_token': + case '_hashed_password': + case '_perishable_token': + return { key: restKey, value: restValue }; + case 'sessionToken': + return { key: '_session_token', value: restValue }; + default: + // Auth data should have been transformed already + if (restKey.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + restKey); + } + // Trust that the auth data has been transformed and save it directly + if (restKey.match(/^_auth_data_[a-zA-Z0-9_]+$/)) { + return { key: restKey, value: restValue }; + } + } + //skip straight to transformTopLevelAtom for Bytes, they don't show up in the schema for some reason + if (restValue && restValue.__type !== 'Bytes') { + //Note: We may not know the type of a field here, as the user could be saving (null) to a field + //That never existed before, meaning we can't infer the type. + if (schema.fields[restKey] && schema.fields[restKey].type == 'Pointer' || restValue.__type == 'Pointer') { + restKey = '_p_' + restKey; + } + } + + // Handle atomic values + var value = transformTopLevelAtom(restValue); + if (value !== CannotTransform) { + return { key: restKey, value: value }; + } + + // ACLs are handled before this method is called + // If an ACL key still exists here, something is wrong. + if (restKey === 'ACL') { + throw 'There was a problem transforming an ACL.'; + } + + // Handle arrays + if (restValue instanceof Array) { + value = restValue.map(transformInteriorValue); + return { key: restKey, value: value }; + } + + // Handle normal objects by recursing + if (Object.keys(restValue).some(function (key) { + return key.includes('$') || key.includes('.'); + })) { + throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters"); + } + value = _lodash2.default.mapValues(restValue, transformInteriorValue); + return { key: restKey, value: value }; +}; + +var parseObjectToMongoObjectForCreate = function parseObjectToMongoObjectForCreate(className, restCreate, schema) { + restCreate = addLegacyACL(restCreate); + var mongoCreate = {}; + for (var restKey in restCreate) { + var _parseObjectKeyValueT = parseObjectKeyValueToMongoObjectKeyValue(restKey, restCreate[restKey], schema); + + var key = _parseObjectKeyValueT.key; + var value = _parseObjectKeyValueT.value; + + if (value !== undefined) { + mongoCreate[key] = value; + } + } + + // Use the legacy mongo format for createdAt and updatedAt + if (mongoCreate.createdAt) { + mongoCreate._created_at = new Date(mongoCreate.createdAt.iso || mongoCreate.createdAt); + delete mongoCreate.createdAt; + } + if (mongoCreate.updatedAt) { + mongoCreate._updated_at = new Date(mongoCreate.updatedAt.iso || mongoCreate.updatedAt); + delete mongoCreate.updatedAt; + } + + return mongoCreate; +}; + +// Main exposed method to help update old objects. +var transformUpdate = function transformUpdate(className, restUpdate, parseFormatSchema) { + var mongoUpdate = {}; + var acl = addLegacyACL(restUpdate)._acl; + if (acl) { + mongoUpdate.$set = {}; + if (acl._rperm) { + mongoUpdate.$set._rperm = acl._rperm; + } + if (acl._wperm) { + mongoUpdate.$set._wperm = acl._wperm; + } + if (acl._acl) { + mongoUpdate.$set._acl = acl._acl; + } + } + for (var restKey in restUpdate) { + var out = transformKeyValueForUpdate(className, restKey, restUpdate[restKey], parseFormatSchema); + + // If the output value is an object with any $ keys, it's an + // operator that needs to be lifted onto the top level update + // object. + if (_typeof(out.value) === 'object' && out.value !== null && out.value.__op) { + mongoUpdate[out.value.__op] = mongoUpdate[out.value.__op] || {}; + mongoUpdate[out.value.__op][out.key] = out.value.arg; + } else { + mongoUpdate['$set'] = mongoUpdate['$set'] || {}; + mongoUpdate['$set'][out.key] = out.value; + } + } + + return mongoUpdate; +}; + +// Add the legacy _acl format. +var addLegacyACL = function addLegacyACL(restObject) { + var restObjectCopy = _extends({}, restObject); + var _acl = {}; + + if (restObject._wperm) { + restObject._wperm.forEach(function (entry) { + _acl[entry] = { w: true }; + }); + } + + if (restObject._rperm) { + restObject._rperm.forEach(function (entry) { + if (!(entry in _acl)) { + _acl[entry] = { r: true }; + } else { + _acl[entry].r = true; + } + }); + } + + if (Object.keys(_acl).length > 0) { + restObjectCopy._acl = _acl; + } + + return restObjectCopy; +}; + +// A sentinel value that helper transformations return when they +// cannot perform a transformation +function CannotTransform() {} + +var transformInteriorAtom = function transformInteriorAtom(atom) { + // TODO: check validity harder for the __type-defined types + if ((typeof atom === 'undefined' ? 'undefined' : _typeof(atom)) === 'object' && atom && !(atom instanceof Date) && atom.__type === 'Pointer') { + return { + __type: 'Pointer', + className: atom.className, + objectId: atom.objectId + }; + } else if (typeof atom === 'function' || (typeof atom === 'undefined' ? 'undefined' : _typeof(atom)) === 'symbol') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot transform value: ' + atom); + } else if (DateCoder.isValidJSON(atom)) { + return DateCoder.JSONToDatabase(atom); + } else if (BytesCoder.isValidJSON(atom)) { + return BytesCoder.JSONToDatabase(atom); + } else { + return atom; + } +}; + +// Helper function to transform an atom from REST format to Mongo format. +// An atom is anything that can't contain other expressions. So it +// includes things where objects are used to represent other +// datatypes, like pointers and dates, but it does not include objects +// or arrays with generic stuff inside. +// Raises an error if this cannot possibly be valid REST format. +// Returns CannotTransform if it's just not an atom +function transformTopLevelAtom(atom) { + switch (typeof atom === 'undefined' ? 'undefined' : _typeof(atom)) { + case 'string': + case 'number': + case 'boolean': + return atom; + case 'undefined': + return atom; + case 'symbol': + case 'function': + throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot transform value: ' + atom); + case 'object': + if (atom instanceof Date) { + // Technically dates are not rest format, but, it seems pretty + // clear what they should be transformed to, so let's just do it. + return atom; + } + + if (atom === null) { + return atom; + } + + // TODO: check validity harder for the __type-defined types + if (atom.__type == 'Pointer') { + return atom.className + '$' + atom.objectId; + } + if (DateCoder.isValidJSON(atom)) { + return DateCoder.JSONToDatabase(atom); + } + if (BytesCoder.isValidJSON(atom)) { + return BytesCoder.JSONToDatabase(atom); + } + if (GeoPointCoder.isValidJSON(atom)) { + return GeoPointCoder.JSONToDatabase(atom); + } + if (FileCoder.isValidJSON(atom)) { + return FileCoder.JSONToDatabase(atom); + } + return CannotTransform; + + default: + // I don't think typeof can ever let us get here + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'really did not expect value: ' + atom); + } +} + +// Transforms a query constraint from REST API format to Mongo format. +// A constraint is something with fields like $lt. +// If it is not a valid constraint but it could be a valid something +// else, return CannotTransform. +// inArray is whether this is an array field. +function transformConstraint(constraint, inArray) { + if ((typeof constraint === 'undefined' ? 'undefined' : _typeof(constraint)) !== 'object' || !constraint) { + return CannotTransform; + } + + // keys is the constraints in reverse alphabetical order. + // This is a hack so that: + // $regex is handled before $options + // $nearSphere is handled before $maxDistance + var keys = Object.keys(constraint).sort().reverse(); + var answer = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var key = _step.value; + + switch (key) { + case '$lt': + case '$lte': + case '$gt': + case '$gte': + case '$exists': + case '$ne': + case '$eq': + answer[key] = inArray ? transformInteriorAtom(constraint[key]) : transformTopLevelAtom(constraint[key]); + if (answer[key] === CannotTransform) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad atom: ' + atom); + } + break; + + case '$in': + case '$nin': + var arr = constraint[key]; + if (!(arr instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value'); + } + answer[key] = arr.map(function (value) { + var result = inArray ? transformInteriorAtom(value) : transformTopLevelAtom(value); + if (result === CannotTransform) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad atom: ' + atom); + } + return result; + }); + break; + + case '$all': + var arr = constraint[key]; + if (!(arr instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value'); + } + answer[key] = arr.map(transformInteriorAtom); + break; + + case '$regex': + var s = constraint[key]; + if (typeof s !== 'string') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad regex: ' + s); + } + answer[key] = s; + break; + + case '$options': + answer[key] = constraint[key]; + break; + + case '$nearSphere': + var point = constraint[key]; + answer[key] = [point.longitude, point.latitude]; + break; + + case '$maxDistance': + answer[key] = constraint[key]; + break; + + // The SDKs don't seem to use these but they are documented in the + // REST API docs. + case '$maxDistanceInRadians': + answer['$maxDistance'] = constraint[key]; + break; + case '$maxDistanceInMiles': + answer['$maxDistance'] = constraint[key] / 3959; + break; + case '$maxDistanceInKilometers': + answer['$maxDistance'] = constraint[key] / 6371; + break; + + case '$select': + case '$dontSelect': + throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, 'the ' + key + ' constraint is not supported yet'); + + case '$within': + var box = constraint[key]['$box']; + if (!box || box.length != 2) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'malformatted $within arg'); + } + answer[key] = { + '$box': [[box[0].longitude, box[0].latitude], [box[1].longitude, box[1].latitude]] + }; + break; + + default: + if (key.match(/^\$+/)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad constraint: ' + key); + } + return CannotTransform; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return answer; +} + +// Transforms an update operator from REST format to mongo format. +// To be transformed, the input should have an __op field. +// If flatten is true, this will flatten operators to their static +// data format. For example, an increment of 2 would simply become a +// 2. +// The output for a non-flattened operator is a hash with __op being +// the mongo op, and arg being the argument. +// The output for a flattened operator is just a value. +// Returns undefined if this should be a no-op. + +function transformUpdateOperator(_ref, flatten) { + var __op = _ref.__op; + var amount = _ref.amount; + var objects = _ref.objects; + + switch (__op) { + case 'Delete': + if (flatten) { + return undefined; + } else { + return { __op: '$unset', arg: '' }; + } + + case 'Increment': + if (typeof amount !== 'number') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'incrementing must provide a number'); + } + if (flatten) { + return amount; + } else { + return { __op: '$inc', arg: amount }; + } + + case 'Add': + case 'AddUnique': + if (!(objects instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + } + var toAdd = objects.map(transformInteriorAtom); + if (flatten) { + return toAdd; + } else { + var mongoOp = { + Add: '$push', + AddUnique: '$addToSet' + }[__op]; + return { __op: mongoOp, arg: { '$each': toAdd } }; + } + + case 'Remove': + if (!(objects instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to remove must be an array'); + } + var toRemove = objects.map(transformInteriorAtom); + if (flatten) { + return []; + } else { + return { __op: '$pullAll', arg: toRemove }; + } + + default: + throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, 'The ' + __op + ' operator is not supported yet.'); + } +} + +var nestedMongoObjectToNestedParseObject = function nestedMongoObjectToNestedParseObject(mongoObject) { + switch (typeof mongoObject === 'undefined' ? 'undefined' : _typeof(mongoObject)) { + case 'string': + case 'number': + case 'boolean': + return mongoObject; + case 'undefined': + case 'symbol': + case 'function': + throw 'bad value in mongoObjectToParseObject'; + case 'object': + if (mongoObject === null) { + return null; + } + if (mongoObject instanceof Array) { + return mongoObject.map(nestedMongoObjectToNestedParseObject); + } + + if (mongoObject instanceof Date) { + return Parse._encode(mongoObject); + } + + if (mongoObject instanceof mongodb.Long) { + return mongoObject.toNumber(); + } + + if (mongoObject instanceof mongodb.Double) { + return mongoObject.value; + } + + if (BytesCoder.isValidDatabaseObject(mongoObject)) { + return BytesCoder.databaseToJSON(mongoObject); + } + + return _lodash2.default.mapValues(mongoObject, nestedMongoObjectToNestedParseObject); + default: + throw 'unknown js type'; + } +}; + +// Converts from a mongo-format object to a REST-format object. +// Does not strip out anything based on a lack of authentication. +var mongoObjectToParseObject = function mongoObjectToParseObject(className, mongoObject, schema) { + var key; + var authDataMatch; + var provider; + var newKey; + var objData; + var value; + + var _ret = function () { + switch (typeof mongoObject === 'undefined' ? 'undefined' : _typeof(mongoObject)) { + case 'string': + case 'number': + case 'boolean': + return { + v: mongoObject + }; + case 'undefined': + case 'symbol': + case 'function': + throw 'bad value in mongoObjectToParseObject'; + case 'object': + if (mongoObject === null) { + return { + v: null + }; + } + if (mongoObject instanceof Array) { + return { + v: mongoObject.map(nestedMongoObjectToNestedParseObject) + }; + } + + if (mongoObject instanceof Date) { + return { + v: Parse._encode(mongoObject) + }; + } + + if (mongoObject instanceof mongodb.Long) { + return { + v: mongoObject.toNumber() + }; + } + + if (mongoObject instanceof mongodb.Double) { + return { + v: mongoObject.value + }; + } + + if (BytesCoder.isValidDatabaseObject(mongoObject)) { + return { + v: BytesCoder.databaseToJSON(mongoObject) + }; + } + + var restObject = {}; + if (mongoObject._rperm || mongoObject._wperm) { + restObject._rperm = mongoObject._rperm || []; + restObject._wperm = mongoObject._wperm || []; + delete mongoObject._rperm; + delete mongoObject._wperm; + } + + for (key in mongoObject) { + switch (key) { + case '_id': + restObject['objectId'] = '' + mongoObject[key]; + break; + case '_hashed_password': + restObject._hashed_password = mongoObject[key]; + break; + case '_acl': + case '_email_verify_token': + case '_perishable_token': + case '_tombstone': + case '_email_verify_token_expires_at': + break; + case '_session_token': + restObject['sessionToken'] = mongoObject[key]; + break; + case 'updatedAt': + case '_updated_at': + restObject['updatedAt'] = Parse._encode(new Date(mongoObject[key])).iso; + break; + case 'createdAt': + case '_created_at': + restObject['createdAt'] = Parse._encode(new Date(mongoObject[key])).iso; + break; + case 'expiresAt': + case '_expiresAt': + restObject['expiresAt'] = Parse._encode(new Date(mongoObject[key])); + break; + default: + // Check other auth data keys + authDataMatch = key.match(/^_auth_data_([a-zA-Z0-9_]+)$/); + + if (authDataMatch) { + provider = authDataMatch[1]; + + restObject['authData'] = restObject['authData'] || {}; + restObject['authData'][provider] = mongoObject[key]; + break; + } + + if (key.indexOf('_p_') == 0) { + newKey = key.substring(3); + + if (!schema.fields[newKey]) { + _logger2.default.info('transform.js', 'Found a pointer column not in the schema, dropping it.', className, newKey); + break; + } + if (schema.fields[newKey].type !== 'Pointer') { + _logger2.default.info('transform.js', 'Found a pointer in a non-pointer column, dropping it.', className, key); + break; + } + if (mongoObject[key] === null) { + break; + } + objData = mongoObject[key].split('$'); + + if (objData[0] !== schema.fields[newKey].targetClass) { + throw 'pointer to incorrect className'; + } + restObject[newKey] = { + __type: 'Pointer', + className: objData[0], + objectId: objData[1] + }; + break; + } else if (key[0] == '_' && key != '__type') { + throw 'bad key in untransform: ' + key; + } else { + value = mongoObject[key]; + + if (schema.fields[key] && schema.fields[key].type === 'File' && FileCoder.isValidDatabaseObject(value)) { + restObject[key] = FileCoder.databaseToJSON(value); + break; + } + if (schema.fields[key] && schema.fields[key].type === 'GeoPoint' && GeoPointCoder.isValidDatabaseObject(value)) { + restObject[key] = GeoPointCoder.databaseToJSON(value); + break; + } + } + restObject[key] = nestedMongoObjectToNestedParseObject(mongoObject[key]); + } + } + + var relationFieldNames = Object.keys(schema.fields).filter(function (fieldName) { + return schema.fields[fieldName].type === 'Relation'; + }); + var relationFields = {}; + relationFieldNames.forEach(function (relationFieldName) { + relationFields[relationFieldName] = { + __type: 'Relation', + className: schema.fields[relationFieldName].targetClass + }; + }); + + return { + v: _extends({}, restObject, relationFields) + }; + default: + throw 'unknown js type'; + } + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; +}; + +var DateCoder = { + JSONToDatabase: function JSONToDatabase(json) { + return new Date(json.iso); + }, + isValidJSON: function isValidJSON(value) { + return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'Date'; + } +}; + +var BytesCoder = { + databaseToJSON: function databaseToJSON(object) { + return { + __type: 'Bytes', + base64: object.buffer.toString('base64') + }; + }, + isValidDatabaseObject: function isValidDatabaseObject(object) { + return object instanceof mongodb.Binary; + }, + JSONToDatabase: function JSONToDatabase(json) { + return new mongodb.Binary(new Buffer(json.base64, 'base64')); + }, + isValidJSON: function isValidJSON(value) { + return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'Bytes'; + } +}; + +var GeoPointCoder = { + databaseToJSON: function databaseToJSON(object) { + return { + __type: 'GeoPoint', + latitude: object[1], + longitude: object[0] + }; + }, + isValidDatabaseObject: function isValidDatabaseObject(object) { + return object instanceof Array && object.length == 2; + }, + JSONToDatabase: function JSONToDatabase(json) { + return [json.longitude, json.latitude]; + }, + isValidJSON: function isValidJSON(value) { + return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'GeoPoint'; + } +}; + +var FileCoder = { + databaseToJSON: function databaseToJSON(object) { + return { + __type: 'File', + name: object + }; + }, + isValidDatabaseObject: function isValidDatabaseObject(object) { + return typeof object === 'string'; + }, + JSONToDatabase: function JSONToDatabase(json) { + return json.name; + }, + isValidJSON: function isValidJSON(value) { + return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'File'; + } +}; + +module.exports = { + transformKey: transformKey, + parseObjectToMongoObjectForCreate: parseObjectToMongoObjectForCreate, + transformUpdate: transformUpdate, + transformWhere: transformWhere, + mongoObjectToParseObject: mongoObjectToParseObject +}; \ No newline at end of file diff --git a/lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js new file mode 100644 index 0000000000..8c358621e5 --- /dev/null +++ b/lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -0,0 +1,546 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var pgp = require('pg-promise')(); + +var PostgresRelationDoesNotExistError = '42P01'; +var PostgresDuplicateRelationError = '42P07'; +var PostgresDuplicateColumnError = '42701'; +var PostgresUniqueIndexViolationError = '23505'; + +var parseTypeToPostgresType = function parseTypeToPostgresType(type) { + switch (type.type) { + case 'String': + return 'text'; + case 'Date': + return 'timestamp'; + case 'Object': + return 'jsonb'; + case 'Boolean': + return 'boolean'; + case 'Pointer': + return 'char(10)'; + case 'Number': + return 'double precision'; + case 'Array': + if (type.contents && type.contents.type === 'String') { + return 'text[]'; + } else { + return 'jsonb'; + } + default: + throw 'no type for ' + JSON.stringify(type) + ' yet'; + } +}; + +var buildWhereClause = function buildWhereClause(_ref) { + var schema = _ref.schema; + var query = _ref.query; + var index = _ref.index; + + var patterns = []; + var values = []; + for (var fieldName in query) { + var fieldValue = query[fieldName]; + if (typeof fieldValue === 'string') { + patterns.push('$' + index + ':name = $' + (index + 1)); + values.push(fieldName, fieldValue); + index += 2; + } else if (fieldValue.$ne) { + patterns.push('$' + index + ':name <> $' + (index + 1)); + values.push(fieldName, fieldValue.$ne); + index += 2; + } else if (fieldName === '$or') { + fieldValue.map(function (subQuery) { + return buildWhereClause({ schema: schema, query: subQuery, index: index }); + }).forEach(function (result) { + patterns.push(result.pattern); + values.push.apply(values, _toConsumableArray(result.values)); + }); + } else if (Array.isArray(fieldValue.$in) && schema.fields[fieldName].type === 'Array') { + (function () { + var inPatterns = []; + var allowNull = false; + values.push(fieldName); + fieldValue.$in.forEach(function (listElem, listIndex) { + if (listElem === null) { + allowNull = true; + } else { + values.push(listElem); + inPatterns.push('$' + (index + 1 + listIndex - (allowNull ? 1 : 0))); + } + }); + if (allowNull) { + patterns.push('($' + index + ':name IS NULL OR $' + index + ':name && ARRAY[' + inPatterns.join(',') + '])'); + } else { + patterns.push('$' + index + ':name && ARRAY[' + inPatterns.join(',') + ']'); + } + index = index + 1 + inPatterns.length; + })(); + } else if (Array.isArray(fieldValue.$in) && schema.fields[fieldName].type === 'String') { + (function () { + var inPatterns = []; + values.push(fieldName); + fieldValue.$in.forEach(function (listElem, listIndex) { + values.push(listElem); + inPatterns.push('$' + (index + 1 + listIndex)); + }); + patterns.push('$' + index + ':name IN (' + inPatterns.join(',') + ')'); + index = index + 1 + inPatterns.length; + })(); + } else if (fieldValue.__type === 'Pointer') { + patterns.push('$' + index + ':name = $' + (index + 1)); + values.push(fieldName, fieldValue.objectId); + index += 2; + } else { + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres doesn\'t support this query type yet'); + } + } + return { pattern: patterns.join(' AND '), values: values }; +}; + +var PostgresStorageAdapter = exports.PostgresStorageAdapter = function () { + // Private + function PostgresStorageAdapter(_ref2) { + var uri = _ref2.uri; + var _ref2$collectionPrefi = _ref2.collectionPrefix; + var collectionPrefix = _ref2$collectionPrefi === undefined ? '' : _ref2$collectionPrefi; + + _classCallCheck(this, PostgresStorageAdapter); + + this._collectionPrefix = collectionPrefix; + this._client = pgp(uri); + } + + _createClass(PostgresStorageAdapter, [{ + key: '_ensureSchemaCollectionExists', + value: function _ensureSchemaCollectionExists() { + return this._client.query('CREATE TABLE "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )').catch(function (error) { + if (error.code === PostgresDuplicateRelationError) { + // Table already exists, must have been created by a different request. Ignore error. + } else { + throw error; + } + }); + } + }, { + key: 'classExists', + value: function classExists(name) { + return notImplemented(); + } + }, { + key: 'setClassLevelPermissions', + value: function setClassLevelPermissions(className, CLPs) { + return notImplemented(); + } + }, { + key: 'createClass', + value: function createClass(className, schema) { + var _this = this; + + var valuesArray = []; + var patternsArray = []; + Object.keys(schema.fields).forEach(function (fieldName, index) { + valuesArray.push(fieldName); + var parseType = schema.fields[fieldName]; + if (['_rperm', '_wperm'].includes(fieldName)) { + parseType.contents = { type: 'String' }; + } + valuesArray.push(parseTypeToPostgresType(parseType)); + patternsArray.push('$' + (index * 2 + 2) + ':name $' + (index * 2 + 3) + ':raw'); + }); + return this._ensureSchemaCollectionExists().then(function () { + return _this._client.query('CREATE TABLE $1:name (' + patternsArray.join(',') + ')', [className].concat(valuesArray)); + }).catch(function (error) { + if (error.code === PostgresDuplicateRelationError) { + // Table already exists, must have been created by a different request. Ignore error. + } else { + throw error; + } + }).then(function () { + return _this._client.query('INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($, $, true)', { className: className, schema: schema }); + }).then(function () { + return schema; + }); + } + }, { + key: 'addFieldIfNotExists', + value: function addFieldIfNotExists(className, fieldName, type) { + var _this2 = this; + + // TODO: Must be revised for invalid logic... + return this._client.tx("addFieldIfNotExists", function (t) { + return t.query('ALTER TABLE $ ADD COLUMN $ $', { + className: className, + fieldName: fieldName, + postgresType: parseTypeToPostgresType(type) + }).catch(function (error) { + if (error.code === PostgresRelationDoesNotExistError) { + return _this2.createClass(className, { fields: _defineProperty({}, fieldName, type) }); + } else if (error.code === PostgresDuplicateColumnError) { + // Column already exists, created by other request. Carry on to + // See if it's the right type. + } else { + throw error; + } + }).then(function () { + return t.query('SELECT "schema" FROM "_SCHEMA" WHERE "className" = $', { className: className }); + }).then(function (result) { + if (fieldName in result[0].schema) { + throw "Attempted to add a field that already exists"; + } else { + result[0].schema.fields[fieldName] = type; + return t.query('UPDATE "_SCHEMA" SET "schema"=$ WHERE "className"=$', { schema: result[0].schema, className: className }); + } + }); + }); + } + + // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) + // and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible. + + }, { + key: 'deleteClass', + value: function deleteClass(className) { + return notImplemented(); + } + + // Delete all data known to this adapter. Used for testing. + + }, { + key: 'deleteAllClasses', + value: function deleteAllClasses() { + var _this3 = this; + + return this._client.query('SELECT "className" FROM "_SCHEMA"').then(function (results) { + var classes = ['_SCHEMA'].concat(_toConsumableArray(results.map(function (result) { + return result.className; + }))); + return _this3._client.tx(function (t) { + return t.batch(classes.map(function (className) { + return t.none('DROP TABLE $', { className: className }); + })); + }); + }, function (error) { + if (error.code === PostgresRelationDoesNotExistError) { + // No _SCHEMA collection. Don't delete anything. + return; + } else { + throw error; + } + }); + } + + // Remove the column and all the data. For Relations, the _Join collection is handled + // specially, this function does not delete _Join columns. It should, however, indicate + // that the relation fields does not exist anymore. In mongo, this means removing it from + // the _SCHEMA collection. There should be no actual data in the collection under the same name + // as the relation column, so it's fine to attempt to delete it. If the fields listed to be + // deleted do not exist, this function should return successfully anyways. Checking for + // attempts to delete non-existent fields is the responsibility of Parse Server. + + // This function is not obligated to delete fields atomically. It is given the field + // names in a list so that databases that are capable of deleting fields atomically + // may do so. + + // Returns a Promise. + + }, { + key: 'deleteFields', + value: function deleteFields(className, schema, fieldNames) { + return notImplemented(); + } + + // Return a promise for all schemas known to this adapter, in Parse format. In case the + // schemas cannot be retrieved, returns a promise that rejects. Requirements for the + // rejection reason are TBD. + + }, { + key: 'getAllClasses', + value: function getAllClasses() { + var _this4 = this; + + return this._ensureSchemaCollectionExists().then(function () { + return _this4._client.map('SELECT * FROM "_SCHEMA"', null, function (row) { + return _extends({ className: row.className }, row.schema); + }); + }); + } + + // Return a promise for the schema with the given name, in Parse format. If + // this adapter doesn't know about the schema, return a promise that rejects with + // undefined as the reason. + + }, { + key: 'getClass', + value: function getClass(className) { + return this._client.query('SELECT * FROM "_SCHEMA" WHERE "className"=$', { className: className }).then(function (result) { + if (result.length === 1) { + return result[0].schema; + } else { + throw undefined; + } + }); + } + + // TODO: remove the mongo format dependency in the return value + + }, { + key: 'createObject', + value: function createObject(className, schema, object) { + var columnsArray = []; + var valuesArray = []; + Object.keys(object).forEach(function (fieldName) { + columnsArray.push(fieldName); + switch (schema.fields[fieldName].type) { + case 'Date': + valuesArray.push(object[fieldName].iso); + break; + case 'Pointer': + valuesArray.push(object[fieldName].objectId); + break; + case 'Array': + if (['_rperm', '_wperm'].includes(fieldName)) { + valuesArray.push(object[fieldName]); + } else { + valuesArray.push(JSON.stringify(object[fieldName])); + } + break; + case 'Object': + valuesArray.push(object[fieldName]); + break; + case 'String': + valuesArray.push(object[fieldName]); + break; + case 'Number': + valuesArray.push(object[fieldName]); + break; + case 'Boolean': + valuesArray.push(object[fieldName]); + break; + default: + throw 'Type ' + schema.fields[fieldName].type + ' not supported yet'; + break; + } + }); + var columnsPattern = columnsArray.map(function (col, index) { + return '$' + (index + 2) + ':name'; + }).join(','); + var valuesPattern = valuesArray.map(function (val, index) { + return '$' + (index + 2 + columnsArray.length) + (['_rperm', '_wperm'].includes(columnsArray[index]) ? '::text[]' : ''); + }).join(','); + var qs = 'INSERT INTO $1:name (' + columnsPattern + ') VALUES (' + valuesPattern + ')'; + var values = [className].concat(columnsArray, valuesArray); + return this._client.query(qs, values).then(function () { + return { ops: [object] }; + }).catch(function (error) { + if (error.code === PostgresUniqueIndexViolationError) { + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided'); + } else { + throw error; + } + }); + } + + // Remove all objects that match the given Parse Query. + // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. + // If there is some other error, reject with INTERNAL_SERVER_ERROR. + + }, { + key: 'deleteObjectsByQuery', + value: function deleteObjectsByQuery(className, schema, query) { + return this._client.one('WITH deleted AS (DELETE FROM $ RETURNING *) SELECT count(*) FROM deleted', { className: className }, function (res) { + return parseInt(res.count); + }).then(function (count) { + if (count === 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } else { + return count; + } + }); + } + + // Apply the update to all objects that match the given Parse Query. + + }, { + key: 'updateObjectsByQuery', + value: function updateObjectsByQuery(className, schema, query, update) { + return notImplemented(); + } + + // Return value not currently well specified. + + }, { + key: 'findOneAndUpdate', + value: function findOneAndUpdate(className, schema, query, update) { + var conditionPatterns = []; + var updatePatterns = []; + var values = [className]; + var index = 2; + + for (var fieldName in update) { + var fieldValue = update[fieldName]; + if (fieldValue.__op === 'Increment') { + updatePatterns.push('$' + index + ':name = COALESCE($' + index + ':name, 0) + $' + (index + 1)); + values.push(fieldName, fieldValue.amount); + index += 2; + } else if (fieldValue.__op === 'Add') { + updatePatterns.push('$' + index + ':name = COALESCE($' + index + ':name, \'[]\'::jsonb) || $' + (index + 1)); + values.push(fieldName, fieldValue.objects); + index += 2; + } else if (fieldValue.__op === 'Remove') { + return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres does not support Remove operator.')); + } else if (fieldValue.__op === 'AddUnique') { + return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres does not support AddUnique operator')); + } else if (fieldName === 'updatedAt') { + //TODO: stop special casing this. It should check for __type === 'Date' and use .iso + updatePatterns.push('$' + index + ':name = $' + (index + 1)); + values.push(fieldName, new Date(fieldValue)); + index += 2; + } else if (typeof fieldValue === 'string') { + updatePatterns.push('$' + index + ':name = $' + (index + 1)); + values.push(fieldName, fieldValue); + index += 2; + } else if (fieldValue.__type === 'Pointer') { + updatePatterns.push('$' + index + ':name = $' + (index + 1)); + values.push(fieldName, fieldValue.objectId); + index += 2; + } else { + return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres doesn\'t support update ' + JSON.stringify(fieldValue) + ' yet')); + } + } + + var where = buildWhereClause({ schema: schema, index: index, query: query }); + values.push.apply(values, _toConsumableArray(where.values)); + + var qs = 'UPDATE $1:name SET ' + updatePatterns.join(',') + ' WHERE ' + where.pattern + ' RETURNING *'; + return this._client.query(qs, values).then(function (val) { + return val[0]; + }); // TODO: This is unsafe, verification is needed, or a different query method; + } + + // Hopefully, we can get rid of this. It's only used for config and hooks. + + }, { + key: 'upsertOneObject', + value: function upsertOneObject(className, schema, query, update) { + return notImplemented(); + } + }, { + key: 'find', + value: function find(className, schema, query, _ref3) { + var skip = _ref3.skip; + var limit = _ref3.limit; + var sort = _ref3.sort; + + var values = [className]; + var where = buildWhereClause({ schema: schema, query: query, index: 2 }); + values.push.apply(values, _toConsumableArray(where.values)); + + var wherePattern = where.pattern.length > 0 ? 'WHERE ' + where.pattern : ''; + var limitPattern = limit !== undefined ? 'LIMIT $' + (values.length + 1) : ''; + + var qs = 'SELECT * FROM $1:name ' + wherePattern + ' ' + limitPattern; + if (limit !== undefined) { + values.push(limit); + } + return this._client.query(qs, values).then(function (results) { + return results.map(function (object) { + Object.keys(schema.fields).filter(function (field) { + return schema.fields[field].type === 'Pointer'; + }).forEach(function (fieldName) { + object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass }; + }); + //TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field. + if (object.createdAt) { + object.createdAt = object.createdAt.toISOString(); + } + if (object.updatedAt) { + object.updatedAt = object.updatedAt.toISOString(); + } + if (object.expiresAt) { + object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() }; + } + if (object._email_verify_token_expires_at) { + object._email_verify_token_expires_at = { __type: 'Date', iso: object._email_verify_token_expires_at.toISOString() }; + } + + for (var fieldName in object) { + if (object[fieldName] === null) { + delete object[fieldName]; + } + if (object[fieldName] instanceof Date) { + object[fieldName] = { __type: 'Date', iso: object[fieldName].toISOString() }; + } + } + + return object; + }); + }); + } + + // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't + // currently know which fields are nullable and which aren't, we ignore that criteria. + // As such, we shouldn't expose this function to users of parse until we have an out-of-band + // Way of determining if a field is nullable. Undefined doesn't count against uniqueness, + // which is why we use sparse indexes. + + }, { + key: 'ensureUniqueness', + value: function ensureUniqueness(className, schema, fieldNames) { + // Use the same name for every ensureUniqueness attempt, because postgres + // Will happily create the same index with multiple names. + var constraintName = 'unique_' + fieldNames.sort().join('_'); + var constraintPatterns = fieldNames.map(function (fieldName, index) { + return '$' + (index + 3) + ':name'; + }); + var qs = 'ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (' + constraintPatterns.join(',') + ')'; + return this._client.query(qs, [className, constraintName].concat(_toConsumableArray(fieldNames))).catch(function (error) { + if (error.code === PostgresDuplicateRelationError && error.message.includes(constraintName)) { + // Index already exists. Ignore error. + } else { + throw error; + } + }); + } + + // Executes a count. + + }, { + key: 'count', + value: function count(className, schema, query) { + var values = [className]; + var where = buildWhereClause({ schema: schema, query: query, index: 2 }); + values.push.apply(values, _toConsumableArray(where.values)); + + var wherePattern = where.pattern.length > 0 ? 'WHERE ' + where.pattern : ''; + var qs = 'SELECT COUNT(*) FROM $1:name ' + wherePattern; + return this._client.query(qs, values).then(function (result) { + return parseInt(result[0].count); + }); + } + }]); + + return PostgresStorageAdapter; +}(); + +function notImplemented() { + return Promise.reject(new Error('Not implemented yet.')); +} + +exports.default = PostgresStorageAdapter; + +module.exports = PostgresStorageAdapter; // Required for tests \ No newline at end of file diff --git a/lib/Auth.js b/lib/Auth.js new file mode 100644 index 0000000000..1176948a3e --- /dev/null +++ b/lib/Auth.js @@ -0,0 +1,221 @@ +'use strict'; + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var deepcopy = require('deepcopy'); +var Parse = require('parse/node').Parse; +var RestQuery = require('./RestQuery'); + +// An Auth object tells you who is requesting something and whether +// the master key was used. +// userObject is a Parse.User and can be null if there's no user. +function Auth() { + var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var config = _ref.config; + var _ref$isMaster = _ref.isMaster; + var isMaster = _ref$isMaster === undefined ? false : _ref$isMaster; + var user = _ref.user; + var installationId = _ref.installationId; + + this.config = config; + this.installationId = installationId; + this.isMaster = isMaster; + this.user = user; + + // Assuming a users roles won't change during a single request, we'll + // only load them once. + this.userRoles = []; + this.fetchedRoles = false; + this.rolePromise = null; +} + +// Whether this auth could possibly modify the given user id. +// It still could be forbidden via ACLs even if this returns true. +Auth.prototype.couldUpdateUserId = function (userId) { + if (this.isMaster) { + return true; + } + if (this.user && this.user.id === userId) { + return true; + } + return false; +}; + +// A helper to get a master-level Auth object +function master(config) { + return new Auth({ config: config, isMaster: true }); +} + +// A helper to get a nobody-level Auth object +function nobody(config) { + return new Auth({ config: config, isMaster: false }); +} + +// Returns a promise that resolves to an Auth object +var getAuthForSessionToken = function getAuthForSessionToken() { + var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var config = _ref2.config; + var sessionToken = _ref2.sessionToken; + var installationId = _ref2.installationId; + + return config.cacheController.user.get(sessionToken).then(function (userJSON) { + if (userJSON) { + var cachedUser = Parse.Object.fromJSON(userJSON); + return Promise.resolve(new Auth({ config: config, isMaster: false, installationId: installationId, user: cachedUser })); + } + + var restOptions = { + limit: 1, + include: 'user' + }; + + var query = new RestQuery(config, master(config), '_Session', { sessionToken: sessionToken }, restOptions); + return query.execute().then(function (response) { + var results = response.results; + if (results.length !== 1 || !results[0]['user']) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); + } + + var now = new Date(), + expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined; + if (expiresAt < now) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.'); + } + var obj = results[0]['user']; + delete obj.password; + obj['className'] = '_User'; + obj['sessionToken'] = sessionToken; + config.cacheController.user.put(sessionToken, obj); + var userObject = Parse.Object.fromJSON(obj); + return new Auth({ config: config, isMaster: false, installationId: installationId, user: userObject }); + }); + }); +}; + +// Returns a promise that resolves to an array of role names +Auth.prototype.getUserRoles = function () { + if (this.isMaster || !this.user) { + return Promise.resolve([]); + } + if (this.fetchedRoles) { + return Promise.resolve(this.userRoles); + } + if (this.rolePromise) { + return this.rolePromise; + } + this.rolePromise = this._loadRoles(); + return this.rolePromise; +}; + +// Iterates through the role tree and compiles a users roles +Auth.prototype._loadRoles = function () { + var _this = this; + + var cacheAdapter = this.config.cacheController; + return cacheAdapter.role.get(this.user.id).then(function (cachedRoles) { + if (cachedRoles != null) { + _this.fetchedRoles = true; + _this.userRoles = cachedRoles; + return Promise.resolve(cachedRoles); + } + + var restWhere = { + 'users': { + __type: 'Pointer', + className: '_User', + objectId: _this.user.id + } + }; + // First get the role ids this user is directly a member of + var query = new RestQuery(_this.config, master(_this.config), '_Role', restWhere, {}); + return query.execute().then(function (response) { + var results = response.results; + if (!results.length) { + _this.userRoles = []; + _this.fetchedRoles = true; + _this.rolePromise = null; + + cacheAdapter.role.put(_this.user.id, _this.userRoles); + return Promise.resolve(_this.userRoles); + } + var rolesMap = results.reduce(function (m, r) { + m.names.push(r.name); + m.ids.push(r.objectId); + return m; + }, { ids: [], names: [] }); + + // run the recursive finding + return _this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names).then(function (roleNames) { + _this.userRoles = roleNames.map(function (r) { + return 'role:' + r; + }); + _this.fetchedRoles = true; + _this.rolePromise = null; + + cacheAdapter.role.put(_this.user.id, _this.userRoles); + return Promise.resolve(_this.userRoles); + }); + }); + }); +}; + +// Given a list of roleIds, find all the parent roles, returns a promise with all names +Auth.prototype._getAllRolesNamesForRoleIds = function (roleIDs) { + var _this2 = this; + + var names = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; + var queriedRoles = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + var ins = roleIDs.filter(function (roleID) { + return queriedRoles[roleID] !== true; + }).map(function (roleID) { + // mark as queried + queriedRoles[roleID] = true; + return { + __type: 'Pointer', + className: '_Role', + objectId: roleID + }; + }); + + // all roles are accounted for, return the names + if (ins.length == 0) { + return Promise.resolve([].concat(_toConsumableArray(new Set(names)))); + } + // Build an OR query across all parentRoles + var restWhere = void 0; + if (ins.length == 1) { + restWhere = { 'roles': ins[0] }; + } else { + restWhere = { 'roles': { '$in': ins } }; + } + var query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {}); + return query.execute().then(function (response) { + var results = response.results; + // Nothing found + if (!results.length) { + return Promise.resolve(names); + } + // Map the results with all Ids and names + var resultMap = results.reduce(function (memo, role) { + memo.names.push(role.name); + memo.ids.push(role.objectId); + return memo; + }, { ids: [], names: [] }); + // store the new found names + names = names.concat(resultMap.names); + // find the next ones, circular roles will be cut + return _this2._getAllRolesNamesForRoleIds(resultMap.ids, names, queriedRoles); + }).then(function (names) { + return Promise.resolve([].concat(_toConsumableArray(new Set(names)))); + }); +}; + +module.exports = { + Auth: Auth, + master: master, + nobody: nobody, + getAuthForSessionToken: getAuthForSessionToken +}; \ No newline at end of file diff --git a/lib/ClientSDK.js b/lib/ClientSDK.js new file mode 100644 index 0000000000..ea73b24ccf --- /dev/null +++ b/lib/ClientSDK.js @@ -0,0 +1,42 @@ +'use strict'; + +var semver = require('semver'); + +function compatible(compatibleSDK) { + return function (clientSDK) { + if (typeof clientSDK === 'string') { + clientSDK = fromString(clientSDK); + } + // REST API, or custom SDK + if (!clientSDK) { + return true; + } + var clientVersion = clientSDK.version; + var compatiblityVersion = compatibleSDK[clientSDK.sdk]; + return semver.satisfies(clientVersion, compatiblityVersion); + }; +} + +function supportsForwardDelete(clientSDK) { + return compatible({ + js: '>=1.9.0' + })(clientSDK); +} + +function fromString(version) { + var versionRE = /([-a-zA-Z]+)([0-9\.]+)/; + var match = version.toLowerCase().match(versionRE); + if (match && match.length === 3) { + return { + sdk: match[1], + version: match[2] + }; + } + return undefined; +} + +module.exports = { + compatible: compatible, + supportsForwardDelete: supportsForwardDelete, + fromString: fromString +}; \ No newline at end of file diff --git a/lib/Config.js b/lib/Config.js new file mode 100644 index 0000000000..5fb2fffa36 --- /dev/null +++ b/lib/Config.js @@ -0,0 +1,224 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Config = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // A Config object provides information about how a specific app is +// configured. +// mount is the URL for the root of the API; includes http, domain, etc. + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +var _SchemaCache = require('./Controllers/SchemaCache'); + +var _SchemaCache2 = _interopRequireDefault(_SchemaCache); + +var _DatabaseController = require('./Controllers/DatabaseController'); + +var _DatabaseController2 = _interopRequireDefault(_DatabaseController); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function removeTrailingSlash(str) { + if (!str) { + return str; + } + if (str.endsWith("/")) { + str = str.substr(0, str.length - 1); + } + return str; +} + +var Config = exports.Config = function () { + function Config(applicationId, mount) { + _classCallCheck(this, Config); + + var cacheInfo = _cache2.default.get(applicationId); + if (!cacheInfo) { + return; + } + + this.applicationId = applicationId; + this.jsonLogs = cacheInfo.jsonLogs; + this.masterKey = cacheInfo.masterKey; + this.clientKey = cacheInfo.clientKey; + this.javascriptKey = cacheInfo.javascriptKey; + this.dotNetKey = cacheInfo.dotNetKey; + this.restAPIKey = cacheInfo.restAPIKey; + this.webhookKey = cacheInfo.webhookKey; + this.fileKey = cacheInfo.fileKey; + this.facebookAppIds = cacheInfo.facebookAppIds; + this.allowClientClassCreation = cacheInfo.allowClientClassCreation; + + // Create a new DatabaseController per request + if (cacheInfo.databaseController) { + var schemaCache = new _SchemaCache2.default(cacheInfo.cacheController, cacheInfo.schemaCacheTTL); + this.database = new _DatabaseController2.default(cacheInfo.databaseController.adapter, schemaCache); + } + + this.schemaCacheTTL = cacheInfo.schemaCacheTTL; + + this.serverURL = cacheInfo.serverURL; + this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL); + this.verifyUserEmails = cacheInfo.verifyUserEmails; + this.preventLoginWithUnverifiedEmail = cacheInfo.preventLoginWithUnverifiedEmail; + this.emailVerifyTokenValidityDuration = cacheInfo.emailVerifyTokenValidityDuration; + this.appName = cacheInfo.appName; + + this.analyticsController = cacheInfo.analyticsController; + this.cacheController = cacheInfo.cacheController; + this.hooksController = cacheInfo.hooksController; + this.filesController = cacheInfo.filesController; + this.pushController = cacheInfo.pushController; + this.loggerController = cacheInfo.loggerController; + this.userController = cacheInfo.userController; + this.authDataManager = cacheInfo.authDataManager; + this.customPages = cacheInfo.customPages || {}; + this.mount = removeTrailingSlash(mount); + this.liveQueryController = cacheInfo.liveQueryController; + this.sessionLength = cacheInfo.sessionLength; + this.expireInactiveSessions = cacheInfo.expireInactiveSessions; + this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this); + this.generateEmailVerifyTokenExpiresAt = this.generateEmailVerifyTokenExpiresAt.bind(this); + this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset; + } + + _createClass(Config, [{ + key: 'generateEmailVerifyTokenExpiresAt', + value: function generateEmailVerifyTokenExpiresAt() { + if (!this.verifyUserEmails || !this.emailVerifyTokenValidityDuration) { + return undefined; + } + var now = new Date(); + return new Date(now.getTime() + this.emailVerifyTokenValidityDuration * 1000); + } + }, { + key: 'generateSessionExpiresAt', + value: function generateSessionExpiresAt() { + if (!this.expireInactiveSessions) { + return undefined; + } + var now = new Date(); + return new Date(now.getTime() + this.sessionLength * 1000); + } + }, { + key: 'mount', + get: function get() { + var mount = this._mount; + if (this.publicServerURL) { + mount = this.publicServerURL; + } + return mount; + }, + set: function set(newValue) { + this._mount = newValue; + } + }, { + key: 'invalidLinkURL', + get: function get() { + return this.customPages.invalidLink || this.publicServerURL + '/apps/invalid_link.html'; + } + }, { + key: 'verifyEmailSuccessURL', + get: function get() { + return this.customPages.verifyEmailSuccess || this.publicServerURL + '/apps/verify_email_success.html'; + } + }, { + key: 'choosePasswordURL', + get: function get() { + return this.customPages.choosePassword || this.publicServerURL + '/apps/choose_password'; + } + }, { + key: 'requestResetPasswordURL', + get: function get() { + return this.publicServerURL + '/apps/' + this.applicationId + '/request_password_reset'; + } + }, { + key: 'passwordResetSuccessURL', + get: function get() { + return this.customPages.passwordResetSuccess || this.publicServerURL + '/apps/password_reset_success.html'; + } + }, { + key: 'verifyEmailURL', + get: function get() { + return this.publicServerURL + '/apps/' + this.applicationId + '/verify_email'; + } + }], [{ + key: 'validate', + value: function validate(_ref) { + var verifyUserEmails = _ref.verifyUserEmails; + var userController = _ref.userController; + var appName = _ref.appName; + var publicServerURL = _ref.publicServerURL; + var revokeSessionOnPasswordReset = _ref.revokeSessionOnPasswordReset; + var expireInactiveSessions = _ref.expireInactiveSessions; + var sessionLength = _ref.sessionLength; + var emailVerifyTokenValidityDuration = _ref.emailVerifyTokenValidityDuration; + + var emailAdapter = userController.adapter; + if (verifyUserEmails) { + this.validateEmailConfiguration({ emailAdapter: emailAdapter, appName: appName, publicServerURL: publicServerURL, emailVerifyTokenValidityDuration: emailVerifyTokenValidityDuration }); + } + + if (typeof revokeSessionOnPasswordReset !== 'boolean') { + throw 'revokeSessionOnPasswordReset must be a boolean value'; + } + + if (publicServerURL) { + if (!publicServerURL.startsWith("http://") && !publicServerURL.startsWith("https://")) { + throw "publicServerURL should be a valid HTTPS URL starting with https://"; + } + } + + this.validateSessionConfiguration(sessionLength, expireInactiveSessions); + } + }, { + key: 'validateEmailConfiguration', + value: function validateEmailConfiguration(_ref2) { + var emailAdapter = _ref2.emailAdapter; + var appName = _ref2.appName; + var publicServerURL = _ref2.publicServerURL; + var emailVerifyTokenValidityDuration = _ref2.emailVerifyTokenValidityDuration; + + if (!emailAdapter) { + throw 'An emailAdapter is required for e-mail verification and password resets.'; + } + if (typeof appName !== 'string') { + throw 'An app name is required for e-mail verification and password resets.'; + } + if (typeof publicServerURL !== 'string') { + throw 'A public server url is required for e-mail verification and password resets.'; + } + if (emailVerifyTokenValidityDuration) { + if (isNaN(emailVerifyTokenValidityDuration)) { + throw 'Email verify token validity duration must be a valid number.'; + } else if (emailVerifyTokenValidityDuration <= 0) { + throw 'Email verify token validity duration must be a value greater than 0.'; + } + } + } + }, { + key: 'validateSessionConfiguration', + value: function validateSessionConfiguration(sessionLength, expireInactiveSessions) { + if (expireInactiveSessions) { + if (isNaN(sessionLength)) { + throw 'Session length must be a valid number.'; + } else if (sessionLength <= 0) { + throw 'Session length must be a value greater than 0.'; + } + } + } + }]); + + return Config; +}(); + +exports.default = Config; + +module.exports = Config; \ No newline at end of file diff --git a/lib/Controllers/AdaptableController.js b/lib/Controllers/AdaptableController.js new file mode 100644 index 0000000000..2cfa4f7212 --- /dev/null +++ b/lib/Controllers/AdaptableController.js @@ -0,0 +1,96 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.AdaptableController = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _Config = require("../Config"); + +var _Config2 = _interopRequireDefault(_Config); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/* +AdaptableController.js + +AdaptableController is the base class for all controllers +that support adapter, +The super class takes care of creating the right instance for the adapter +based on the parameters passed + + */ + +// _adapter is private, use Symbol +var _adapter = Symbol(); + +var AdaptableController = exports.AdaptableController = function () { + function AdaptableController(adapter, appId, options) { + _classCallCheck(this, AdaptableController); + + this.options = options; + this.appId = appId; + this.adapter = adapter; + } + + _createClass(AdaptableController, [{ + key: "expectedAdapterType", + value: function expectedAdapterType() { + throw new Error("Subclasses should implement expectedAdapterType()"); + } + }, { + key: "validateAdapter", + value: function validateAdapter(adapter) { + if (!adapter) { + throw new Error(this.constructor.name + " requires an adapter"); + } + + var Type = this.expectedAdapterType(); + // Allow skipping for testing + if (!Type) { + return; + } + + // Makes sure the prototype matches + var mismatches = Object.getOwnPropertyNames(Type.prototype).reduce(function (obj, key) { + var adapterType = _typeof(adapter[key]); + var expectedType = _typeof(Type.prototype[key]); + if (adapterType !== expectedType) { + obj[key] = { + expected: expectedType, + actual: adapterType + }; + } + return obj; + }, {}); + + if (Object.keys(mismatches).length > 0) { + throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches); + } + } + }, { + key: "adapter", + set: function set(adapter) { + this.validateAdapter(adapter); + this[_adapter] = adapter; + }, + get: function get() { + return this[_adapter]; + } + }, { + key: "config", + get: function get() { + return new _Config2.default(this.appId); + } + }]); + + return AdaptableController; +}(); + +exports.default = AdaptableController; \ No newline at end of file diff --git a/lib/Controllers/AnalyticsController.js b/lib/Controllers/AnalyticsController.js new file mode 100644 index 0000000000..304d132d2b --- /dev/null +++ b/lib/Controllers/AnalyticsController.js @@ -0,0 +1,61 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.AnalyticsController = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _AdaptableController2 = require('./AdaptableController'); + +var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); + +var _AnalyticsAdapter = require('../Adapters/Analytics/AnalyticsAdapter'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var AnalyticsController = exports.AnalyticsController = function (_AdaptableController) { + _inherits(AnalyticsController, _AdaptableController); + + function AnalyticsController() { + _classCallCheck(this, AnalyticsController); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(AnalyticsController).apply(this, arguments)); + } + + _createClass(AnalyticsController, [{ + key: 'appOpened', + value: function appOpened(req) { + return this.adapter.appOpened(req.body, req).then(function (response) { + return { response: response }; + }).catch(function (err) { + return { response: {} }; + }); + } + }, { + key: 'trackEvent', + value: function trackEvent(req) { + return this.adapter.trackEvent(req.params.eventName, req.body, req).then(function (response) { + return { response: response }; + }).catch(function (err) { + return { response: {} }; + }); + } + }, { + key: 'expectedAdapterType', + value: function expectedAdapterType() { + return _AnalyticsAdapter.AnalyticsAdapter; + } + }]); + + return AnalyticsController; +}(_AdaptableController3.default); + +exports.default = AnalyticsController; \ No newline at end of file diff --git a/lib/Controllers/CacheController.js b/lib/Controllers/CacheController.js new file mode 100644 index 0000000000..628a5cbeec --- /dev/null +++ b/lib/Controllers/CacheController.js @@ -0,0 +1,129 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.CacheController = exports.SubCache = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _AdaptableController2 = require('./AdaptableController'); + +var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); + +var _CacheAdapter = require('../Adapters/Cache/CacheAdapter'); + +var _CacheAdapter2 = _interopRequireDefault(_CacheAdapter); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var KEY_SEPARATOR_CHAR = ':'; + +function joinKeys() { + for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) { + keys[_key] = arguments[_key]; + } + + return keys.join(KEY_SEPARATOR_CHAR); +} + +/** + * Prefix all calls to the cache via a prefix string, useful when grouping Cache by object type. + * + * eg "Role" or "Session" + */ + +var SubCache = exports.SubCache = function () { + function SubCache(prefix, cacheController, ttl) { + _classCallCheck(this, SubCache); + + this.prefix = prefix; + this.cache = cacheController; + this.ttl = ttl; + } + + _createClass(SubCache, [{ + key: 'get', + value: function get(key) { + var cacheKey = joinKeys(this.prefix, key); + return this.cache.get(cacheKey); + } + }, { + key: 'put', + value: function put(key, value, ttl) { + var cacheKey = joinKeys(this.prefix, key); + return this.cache.put(cacheKey, value, ttl); + } + }, { + key: 'del', + value: function del(key) { + var cacheKey = joinKeys(this.prefix, key); + return this.cache.del(cacheKey); + } + }, { + key: 'clear', + value: function clear() { + return this.cache.clear(); + } + }]); + + return SubCache; +}(); + +var CacheController = exports.CacheController = function (_AdaptableController) { + _inherits(CacheController, _AdaptableController); + + function CacheController(adapter, appId) { + var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + _classCallCheck(this, CacheController); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(CacheController).call(this, adapter, appId, options)); + + _this.role = new SubCache('role', _this); + _this.user = new SubCache('user', _this); + return _this; + } + + _createClass(CacheController, [{ + key: 'get', + value: function get(key) { + var cacheKey = joinKeys(this.appId, key); + return this.adapter.get(cacheKey).then(null, function () { + return Promise.resolve(null); + }); + } + }, { + key: 'put', + value: function put(key, value, ttl) { + var cacheKey = joinKeys(this.appId, key); + return this.adapter.put(cacheKey, value, ttl); + } + }, { + key: 'del', + value: function del(key) { + var cacheKey = joinKeys(this.appId, key); + return this.adapter.del(cacheKey); + } + }, { + key: 'clear', + value: function clear() { + return this.adapter.clear(); + } + }, { + key: 'expectedAdapterType', + value: function expectedAdapterType() { + return _CacheAdapter2.default; + } + }]); + + return CacheController; +}(_AdaptableController3.default); + +exports.default = CacheController; \ No newline at end of file diff --git a/lib/Controllers/DatabaseController.js b/lib/Controllers/DatabaseController.js new file mode 100644 index 0000000000..e58197f475 --- /dev/null +++ b/lib/Controllers/DatabaseController.js @@ -0,0 +1,1061 @@ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _intersect = require('intersect'); + +var _intersect2 = _interopRequireDefault(_intersect); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // A database adapter that works with data exported from the hosted +// Parse database. + +var mongodb = require('mongodb'); +var Parse = require('parse/node').Parse; + +var SchemaController = require('./SchemaController'); + +var deepcopy = require('deepcopy'); + +function addWriteACL(query, acl) { + var newQuery = _lodash2.default.cloneDeep(query); + //Can't be any existing '_wperm' query, we don't allow client queries on that, no need to $and + newQuery._wperm = { "$in": [null].concat(_toConsumableArray(acl)) }; + return newQuery; +} + +function addReadACL(query, acl) { + var newQuery = _lodash2.default.cloneDeep(query); + //Can't be any existing '_rperm' query, we don't allow client queries on that, no need to $and + newQuery._rperm = { "$in": [null, "*"].concat(_toConsumableArray(acl)) }; + return newQuery; +} + +// Transforms a REST API formatted ACL object to our two-field mongo format. +var transformObjectACL = function transformObjectACL(_ref) { + var ACL = _ref.ACL; + + var result = _objectWithoutProperties(_ref, ['ACL']); + + if (!ACL) { + return result; + } + + result._wperm = []; + result._rperm = []; + + for (var entry in ACL) { + if (ACL[entry].read) { + result._rperm.push(entry); + } + if (ACL[entry].write) { + result._wperm.push(entry); + } + } + return result; +}; + +var specialQuerykeys = ['$and', '$or', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at']; +var validateQuery = function validateQuery(query) { + if (query.ACL) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); + } + + if (query.$or) { + if (query.$or instanceof Array) { + query.$or.forEach(validateQuery); + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.'); + } + } + + if (query.$and) { + if (query.$and instanceof Array) { + query.$and.forEach(validateQuery); + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $and format - use an array value.'); + } + } + + Object.keys(query).forEach(function (key) { + if (query && query[key] && query[key].$regex) { + if (typeof query[key].$options === 'string') { + if (!query[key].$options.match(/^[imxs]+$/)) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $options value for query: ' + query[key].$options); + } + } + } + if (!specialQuerykeys.includes(key) && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: ' + key); + } + }); +}; + +function DatabaseController(adapter, schemaCache) { + this.adapter = adapter; + this.schemaCache = schemaCache; + // We don't want a mutable this.schema, because then you could have + // one request that uses different schemas for different parts of + // it. Instead, use loadSchema to get a schema. + this.schemaPromise = null; +} + +DatabaseController.prototype.collectionExists = function (className) { + return this.adapter.classExists(className); +}; + +DatabaseController.prototype.purgeCollection = function (className) { + var _this = this; + + return this.loadSchema().then(function (schemaController) { + return schemaController.getOneSchema(className); + }).then(function (schema) { + return _this.adapter.deleteObjectsByQuery(className, schema, {}); + }); +}; + +DatabaseController.prototype.validateClassName = function (className) { + if (!SchemaController.classNameIsValid(className)) { + return Promise.reject(new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className)); + } + return Promise.resolve(); +}; + +// Returns a promise for a schemaController. +DatabaseController.prototype.loadSchema = function () { + var _this2 = this; + + var options = arguments.length <= 0 || arguments[0] === undefined ? { clearCache: false } : arguments[0]; + + if (!this.schemaPromise) { + this.schemaPromise = SchemaController.load(this.adapter, this.schemaCache, options); + this.schemaPromise.then(function () { + return delete _this2.schemaPromise; + }, function () { + return delete _this2.schemaPromise; + }); + } + return this.schemaPromise; +}; + +// Returns a promise for the classname that is related to the given +// classname through the key. +// TODO: make this not in the DatabaseController interface +DatabaseController.prototype.redirectClassNameForKey = function (className, key) { + return this.loadSchema().then(function (schema) { + var t = schema.getExpectedType(className, key); + if (t && t.type == 'Relation') { + return t.targetClass; + } else { + return className; + } + }); +}; + +// Uses the schema to validate the object (REST API format). +// Returns a promise that resolves to the new schema. +// This does not update this.schema, because in a situation like a +// batch request, that could confuse other users of the schema. +DatabaseController.prototype.validateObject = function (className, object, query, _ref2) { + var _this3 = this; + + var acl = _ref2.acl; + + var schema = void 0; + var isMaster = acl === undefined; + var aclGroup = acl || []; + return this.loadSchema().then(function (s) { + schema = s; + if (isMaster) { + return Promise.resolve(); + } + return _this3.canAddField(schema, className, object, aclGroup); + }).then(function () { + return schema.validateObject(className, object, query); + }); +}; + +// Filters out any data that shouldn't be on this REST-formatted object. +var filterSensitiveData = function filterSensitiveData(isMaster, aclGroup, className, object) { + if (className !== '_User') { + return object; + } + + object.password = object._hashed_password; + delete object._hashed_password; + + delete object.sessionToken; + + if (isMaster || aclGroup.indexOf(object.objectId) > -1) { + return object; + } + + delete object.authData; + + return object; +}; + +// Runs an update on the database. +// Returns a promise for an object with the new values for field +// modifications that don't know their results ahead of time, like +// 'increment'. +// Options: +// acl: a list of strings. If the object to be updated has an ACL, +// one of the provided strings must provide the caller with +// write permissions. +var specialKeysForUpdate = ['_hashed_password', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at']; +DatabaseController.prototype.update = function (className, query, update) { + var _this4 = this; + + var _ref3 = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; + + var acl = _ref3.acl; + var many = _ref3.many; + var upsert = _ref3.upsert; + var skipSanitization = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; + + var originalUpdate = update; + // Make a copy of the object, so we don't mutate the incoming data. + update = deepcopy(update); + + var isMaster = acl === undefined; + var aclGroup = acl || []; + var mongoUpdate; + return this.loadSchema().then(function (schemaController) { + return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update')).then(function () { + return _this4.handleRelationUpdates(className, query.objectId, update); + }).then(function () { + if (!isMaster) { + query = _this4.addPointerPermissions(schemaController, className, 'update', query, aclGroup); + } + if (!query) { + return Promise.resolve(); + } + if (acl) { + query = addWriteACL(query, acl); + } + validateQuery(query); + return schemaController.getOneSchema(className).catch(function (error) { + // If the schema doesn't exist, pretend it exists with no fields. This behaviour + // will likely need revisiting. + if (error === undefined) { + return { fields: {} }; + } + throw error; + }).then(function (schema) { + Object.keys(update).forEach(function (fieldName) { + if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name for update: ' + fieldName); + } + fieldName = fieldName.split('.')[0]; + if (!SchemaController.fieldNameIsValid(fieldName) && !specialKeysForUpdate.includes(fieldName)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name for update: ' + fieldName); + } + }); + for (var updateOperation in update) { + if (Object.keys(updateOperation).some(function (innerKey) { + return innerKey.includes('$') || innerKey.includes('.'); + })) { + throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters"); + } + } + update = transformObjectACL(update); + transformAuthData(className, update, schema); + if (many) { + return _this4.adapter.updateObjectsByQuery(className, schema, query, update); + } else if (upsert) { + return _this4.adapter.upsertOneObject(className, schema, query, update); + } else { + return _this4.adapter.findOneAndUpdate(className, schema, query, update); + } + }); + }).then(function (result) { + if (!result) { + return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); + } + if (skipSanitization) { + return Promise.resolve(result); + } + return sanitizeDatabaseResult(originalUpdate, result); + }); + }); +}; + +function sanitizeDatabaseResult(originalObject, result) { + var response = {}; + if (!result) { + return Promise.resolve(response); + } + Object.keys(originalObject).forEach(function (key) { + var keyUpdate = originalObject[key]; + // determine if that was an op + if (keyUpdate && (typeof keyUpdate === 'undefined' ? 'undefined' : _typeof(keyUpdate)) === 'object' && keyUpdate.__op && ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) { + // only valid ops that produce an actionable result + response[key] = result[key]; + } + }); + return Promise.resolve(response); +} + +// Processes relation-updating operations from a REST-format update. +// Returns a promise that resolves successfully when these are +// processed. +// This mutates update. +DatabaseController.prototype.handleRelationUpdates = function (className, objectId, update) { + var _this5 = this; + + var pending = []; + var deleteMe = []; + objectId = update.objectId || objectId; + + var process = function process(op, key) { + if (!op) { + return; + } + if (op.__op == 'AddRelation') { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = op.objects[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var object = _step.value; + + pending.push(_this5.addRelation(key, className, objectId, object.objectId)); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + deleteMe.push(key); + } + + if (op.__op == 'RemoveRelation') { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = op.objects[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var object = _step2.value; + + pending.push(_this5.removeRelation(key, className, objectId, object.objectId)); + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + deleteMe.push(key); + } + + if (op.__op == 'Batch') { + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = op.ops[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var x = _step3.value; + + process(x, key); + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + } + }; + + for (var key in update) { + process(update[key], key); + } + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = deleteMe[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var key = _step4.value; + + delete update[key]; + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + return Promise.all(pending); +}; + +// Adds a relation. +// Returns a promise that resolves successfully iff the add was successful. +var relationSchema = { fields: { relatedId: { type: 'String' }, owningId: { type: 'String' } } }; +DatabaseController.prototype.addRelation = function (key, fromClassName, fromId, toId) { + var doc = { + relatedId: toId, + owningId: fromId + }; + return this.adapter.upsertOneObject('_Join:' + key + ':' + fromClassName, relationSchema, doc, doc); +}; + +// Removes a relation. +// Returns a promise that resolves successfully iff the remove was +// successful. +DatabaseController.prototype.removeRelation = function (key, fromClassName, fromId, toId) { + var doc = { + relatedId: toId, + owningId: fromId + }; + return this.adapter.deleteObjectsByQuery('_Join:' + key + ':' + fromClassName, relationSchema, doc).catch(function (error) { + // We don't care if they try to delete a non-existent relation. + if (error.code == Parse.Error.OBJECT_NOT_FOUND) { + return; + } + throw error; + }); +}; + +// Removes objects matches this query from the database. +// Returns a promise that resolves successfully iff the object was +// deleted. +// Options: +// acl: a list of strings. If the object to be updated has an ACL, +// one of the provided strings must provide the caller with +// write permissions. +DatabaseController.prototype.destroy = function (className, query) { + var _this6 = this; + + var _ref4 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + var acl = _ref4.acl; + + var isMaster = acl === undefined; + var aclGroup = acl || []; + + return this.loadSchema().then(function (schemaController) { + return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete')).then(function () { + if (!isMaster) { + query = _this6.addPointerPermissions(schemaController, className, 'delete', query, aclGroup); + if (!query) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } + } + // delete by query + if (acl) { + query = addWriteACL(query, acl); + } + validateQuery(query); + return schemaController.getOneSchema(className).catch(function (error) { + // If the schema doesn't exist, pretend it exists with no fields. This behaviour + // will likely need revisiting. + if (error === undefined) { + return { fields: {} }; + } + throw error; + }).then(function (parseFormatSchema) { + return _this6.adapter.deleteObjectsByQuery(className, parseFormatSchema, query); + }).catch(function (error) { + // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions. + if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) { + return Promise.resolve({}); + } + throw error; + }); + }); + }); +}; + +var flattenUpdateOperatorsForCreate = function flattenUpdateOperatorsForCreate(object) { + for (var key in object) { + if (object[key] && object[key].__op) { + switch (object[key].__op) { + case 'Increment': + if (typeof object[key].amount !== 'number') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + } + object[key] = object[key].amount; + break; + case 'Add': + if (!(object[key].objects instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + } + object[key] = object[key].objects; + break; + case 'AddUnique': + if (!(object[key].objects instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + } + object[key] = object[key].objects; + break; + case 'Remove': + if (!(object[key].objects instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + } + object[key] = []; + break; + case 'Delete': + delete object[key]; + break; + default: + throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, 'The ' + object[key].__op + ' operator is not supported yet.'); + } + } + } +}; + +var transformAuthData = function transformAuthData(className, object, schema) { + if (object.authData && className === '_User') { + Object.keys(object.authData).forEach(function (provider) { + var providerData = object.authData[provider]; + var fieldName = '_auth_data_' + provider; + if (providerData == null) { + object[fieldName] = { + __op: 'Delete' + }; + } else { + object[fieldName] = providerData; + schema.fields[fieldName] = { type: 'Object' }; + } + }); + delete object.authData; + } +}; + +// Inserts an object into the database. +// Returns a promise that resolves successfully iff the object saved. +DatabaseController.prototype.create = function (className, object) { + var _this7 = this; + + var _ref5 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + var acl = _ref5.acl; + + // Make a copy of the object, so we don't mutate the incoming data. + var originalObject = object; + object = transformObjectACL(object); + + object.createdAt = { iso: object.createdAt, __type: 'Date' }; + object.updatedAt = { iso: object.updatedAt, __type: 'Date' }; + + var isMaster = acl === undefined; + var aclGroup = acl || []; + + return this.validateClassName(className).then(function () { + return _this7.loadSchema(); + }).then(function (schemaController) { + return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create')).then(function () { + return _this7.handleRelationUpdates(className, null, object); + }).then(function () { + return schemaController.enforceClassExists(className); + }).then(function () { + return schemaController.reloadData(); + }).then(function () { + return schemaController.getOneSchema(className, true); + }).then(function (schema) { + transformAuthData(className, object, schema); + flattenUpdateOperatorsForCreate(object); + return _this7.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object); + }).then(function (result) { + return sanitizeDatabaseResult(originalObject, result.ops[0]); + }); + }); +}; + +DatabaseController.prototype.canAddField = function (schema, className, object, aclGroup) { + var classSchema = schema.data[className]; + if (!classSchema) { + return Promise.resolve(); + } + var fields = Object.keys(object); + var schemaFields = Object.keys(classSchema); + var newKeys = fields.filter(function (field) { + return schemaFields.indexOf(field) < 0; + }); + if (newKeys.length > 0) { + return schema.validatePermission(className, aclGroup, 'addField'); + } + return Promise.resolve(); +}; + +// Won't delete collections in the system namespace +// Returns a promise. +DatabaseController.prototype.deleteEverything = function () { + this.schemaPromise = null; + return this.adapter.deleteAllClasses(); +}; + +// Finds the keys in a query. Returns a Set. REST format only +function keysForQuery(query) { + var sublist = query['$and'] || query['$or']; + if (sublist) { + var answer = sublist.reduce(function (memo, subquery) { + return memo.concat(keysForQuery(subquery)); + }, []); + + return new Set(answer); + } + + return new Set(Object.keys(query)); +} + +// Returns a promise for a list of related ids given an owning id. +// className here is the owning className. +DatabaseController.prototype.relatedIds = function (className, key, owningId) { + return this.adapter.find(joinTableName(className, key), relationSchema, { owningId: owningId }, {}).then(function (results) { + return results.map(function (result) { + return result.relatedId; + }); + }); +}; + +// Returns a promise for a list of owning ids given some related ids. +// className here is the owning className. +DatabaseController.prototype.owningIds = function (className, key, relatedIds) { + return this.adapter.find(joinTableName(className, key), relationSchema, { relatedId: { '$in': relatedIds } }, {}).then(function (results) { + return results.map(function (result) { + return result.owningId; + }); + }); +}; + +// Modifies query so that it no longer has $in on relation fields, or +// equal-to-pointer constraints on relation fields. +// Returns a promise that resolves when query is mutated +DatabaseController.prototype.reduceInRelation = function (className, query, schema) { + var _this8 = this; + + // Search for an in-relation or equal-to-relation + // Make it sequential for now, not sure of paralleization side effects + if (query['$or']) { + var ors = query['$or']; + return Promise.all(ors.map(function (aQuery, index) { + return _this8.reduceInRelation(className, aQuery, schema).then(function (aQuery) { + query['$or'][index] = aQuery; + }); + })).then(function () { + return Promise.resolve(query); + }); + } + + var promises = Object.keys(query).map(function (key) { + if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) { + var t = schema.getExpectedType(className, key); + if (!t || t.type !== 'Relation') { + return Promise.resolve(query); + } + var relatedClassName = t.targetClass; + // Build the list of queries + var queries = Object.keys(query[key]).map(function (constraintKey) { + var relatedIds = void 0; + var isNegation = false; + if (constraintKey === 'objectId') { + relatedIds = [query[key].objectId]; + } else if (constraintKey == '$in') { + relatedIds = query[key]['$in'].map(function (r) { + return r.objectId; + }); + } else if (constraintKey == '$nin') { + isNegation = true; + relatedIds = query[key]['$nin'].map(function (r) { + return r.objectId; + }); + } else if (constraintKey == '$ne') { + isNegation = true; + relatedIds = [query[key]['$ne'].objectId]; + } else { + return; + } + return { + isNegation: isNegation, + relatedIds: relatedIds + }; + }); + + // remove the current queryKey as we don,t need it anymore + delete query[key]; + // execute each query independnently to build the list of + // $in / $nin + var _promises = queries.map(function (q) { + if (!q) { + return Promise.resolve(); + } + return _this8.owningIds(className, key, q.relatedIds).then(function (ids) { + if (q.isNegation) { + _this8.addNotInObjectIdsIds(ids, query); + } else { + _this8.addInObjectIdsIds(ids, query); + } + return Promise.resolve(); + }); + }); + + return Promise.all(_promises).then(function () { + return Promise.resolve(); + }); + } + return Promise.resolve(); + }); + + return Promise.all(promises).then(function () { + return Promise.resolve(query); + }); +}; + +// Modifies query so that it no longer has $relatedTo +// Returns a promise that resolves when query is mutated +DatabaseController.prototype.reduceRelationKeys = function (className, query) { + var _this9 = this; + + if (query['$or']) { + return Promise.all(query['$or'].map(function (aQuery) { + return _this9.reduceRelationKeys(className, aQuery); + })); + } + + var relatedTo = query['$relatedTo']; + if (relatedTo) { + return this.relatedIds(relatedTo.object.className, relatedTo.key, relatedTo.object.objectId).then(function (ids) { + delete query['$relatedTo']; + _this9.addInObjectIdsIds(ids, query); + return _this9.reduceRelationKeys(className, query); + }); + } +}; + +DatabaseController.prototype.addInObjectIdsIds = function () { + var ids = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; + var query = arguments[1]; + + var idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null; + var idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null; + var idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null; + + var allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(function (list) { + return list !== null; + }); + var totalLength = allIds.reduce(function (memo, list) { + return memo + list.length; + }, 0); + + var idsIntersection = []; + if (totalLength > 125) { + idsIntersection = _intersect2.default.big(allIds); + } else { + idsIntersection = (0, _intersect2.default)(allIds); + } + + // Need to make sure we don't clobber existing $lt or other constraints on objectId. + // Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints + // is expected though. + if (!('objectId' in query) || typeof query.objectId === 'string') { + query.objectId = {}; + } + query.objectId['$in'] = idsIntersection; + + return query; +}; + +DatabaseController.prototype.addNotInObjectIdsIds = function () { + var ids = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; + var query = arguments[1]; + + var idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null; + var allIds = [idsFromNin, ids].filter(function (list) { + return list !== null; + }); + var totalLength = allIds.reduce(function (memo, list) { + return memo + list.length; + }, 0); + + var idsIntersection = []; + if (totalLength > 125) { + idsIntersection = _intersect2.default.big(allIds); + } else { + idsIntersection = (0, _intersect2.default)(allIds); + } + + // Need to make sure we don't clobber existing $lt or other constraints on objectId. + // Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints + // is expected though. + if (!('objectId' in query) || typeof query.objectId === 'string') { + query.objectId = {}; + } + query.objectId['$nin'] = idsIntersection; + + return query; +}; + +// Runs a query on the database. +// Returns a promise that resolves to a list of items. +// Options: +// skip number of results to skip. +// limit limit to this number of results. +// sort an object where keys are the fields to sort by. +// the value is +1 for ascending, -1 for descending. +// count run a count instead of returning results. +// acl restrict this operation with an ACL for the provided array +// of user objectIds and roles. acl: null means no user. +// when this field is not present, don't do anything regarding ACLs. +// TODO: make userIds not needed here. The db adapter shouldn't know +// anything about users, ideally. Then, improve the format of the ACL +// arg to work like the others. +DatabaseController.prototype.find = function (className, query) { + var _this10 = this; + + var _ref6 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + var skip = _ref6.skip; + var limit = _ref6.limit; + var acl = _ref6.acl; + var _ref6$sort = _ref6.sort; + var sort = _ref6$sort === undefined ? {} : _ref6$sort; + var count = _ref6.count; + + var isMaster = acl === undefined; + var aclGroup = acl || []; + var op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find'; + var classExists = true; + return this.loadSchema().then(function (schemaController) { + //Allow volatile classes if querying with Master (for _PushStatus) + //TODO: Move volatile classes concept into mongo adatper, postgres adapter shouldn't care + //that api.parse.com breaks when _PushStatus exists in mongo. + return schemaController.getOneSchema(className, isMaster).catch(function (error) { + // Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much. + // For now, pretend the class exists but has no objects, + if (error === undefined) { + classExists = false; + return { fields: {} }; + } + throw error; + }).then(function (schema) { + // Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt, + // so duplicate that behaviour here. If both are specified, the corrent behaviour to match Parse.com is to + // use the one that appears first in the sort list. + if (sort._created_at) { + sort.createdAt = sort._created_at; + delete sort._created_at; + } + if (sort._updated_at) { + sort.updatedAt = sort._updated_at; + delete sort._updated_at; + } + Object.keys(sort).forEach(function (fieldName) { + if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot sort by ' + fieldName); + } + if (!SchemaController.fieldNameIsValid(fieldName)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name: ' + fieldName + '.'); + } + }); + return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op)).then(function () { + return _this10.reduceRelationKeys(className, query); + }).then(function () { + return _this10.reduceInRelation(className, query, schemaController); + }).then(function () { + if (!isMaster) { + query = _this10.addPointerPermissions(schemaController, className, op, query, aclGroup); + } + if (!query) { + if (op == 'get') { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } else { + return []; + } + } + if (!isMaster) { + query = addReadACL(query, aclGroup); + } + validateQuery(query); + if (count) { + if (!classExists) { + return 0; + } else { + return _this10.adapter.count(className, schema, query); + } + } else { + if (!classExists) { + return []; + } else { + return _this10.adapter.find(className, schema, query, { skip: skip, limit: limit, sort: sort }).then(function (objects) { + return objects.map(function (object) { + object = untransformObjectACL(object); + return filterSensitiveData(isMaster, aclGroup, className, object); + }); + }); + } + } + }); + }); + }); +}; + +// Transforms a Database format ACL to a REST API format ACL +var untransformObjectACL = function untransformObjectACL(_ref7) { + var _rperm = _ref7._rperm; + var _wperm = _ref7._wperm; + + var output = _objectWithoutProperties(_ref7, ['_rperm', '_wperm']); + + if (_rperm || _wperm) { + output.ACL = {}; + + (_rperm || []).forEach(function (entry) { + if (!output.ACL[entry]) { + output.ACL[entry] = { read: true }; + } else { + output.ACL[entry]['read'] = true; + } + }); + + (_wperm || []).forEach(function (entry) { + if (!output.ACL[entry]) { + output.ACL[entry] = { write: true }; + } else { + output.ACL[entry]['write'] = true; + } + }); + } + return output; +}; + +DatabaseController.prototype.deleteSchema = function (className) { + var _this11 = this; + + return this.loadSchema(true).then(function (schemaController) { + return schemaController.getOneSchema(className, true); + }).catch(function (error) { + if (error === undefined) { + return { fields: {} }; + } else { + throw error; + } + }).then(function (schema) { + return _this11.collectionExists(className).then(function (exist) { + return _this11.adapter.count(className, { fields: {} }); + }).then(function (count) { + if (count > 0) { + throw new Parse.Error(255, 'Class ' + className + ' is not empty, contains ' + count + ' objects, cannot drop schema.'); + } + return _this11.adapter.deleteClass(className); + }).then(function (wasParseCollection) { + if (wasParseCollection) { + var relationFieldNames = Object.keys(schema.fields).filter(function (fieldName) { + return schema.fields[fieldName].type === 'Relation'; + }); + return Promise.all(relationFieldNames.map(function (name) { + return _this11.adapter.deleteClass(joinTableName(className, name)); + })); + } else { + return Promise.resolve(); + } + }); + }); +}; + +DatabaseController.prototype.addPointerPermissions = function (schema, className, operation, query) { + var aclGroup = arguments.length <= 4 || arguments[4] === undefined ? [] : arguments[4]; + + // Check if class has public permission for operation + // If the BaseCLP pass, let go through + if (schema.testBaseCLP(className, aclGroup, operation)) { + return query; + } + var perms = schema.perms[className]; + var field = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields'; + var userACL = aclGroup.filter(function (acl) { + return acl.indexOf('role:') != 0 && acl != '*'; + }); + // the ACL should have exactly 1 user + if (perms && perms[field] && perms[field].length > 0) { + var _ret = function () { + // No user set return undefined + if (userACL.length != 1) { + return { + v: void 0 + }; + } + var userId = userACL[0]; + var userPointer = { + "__type": "Pointer", + "className": "_User", + "objectId": userId + }; + + var constraints = {}; + var permFields = perms[field]; + var ors = permFields.map(function (key) { + var q = _defineProperty({}, key, userPointer); + return { '$and': [q, query] }; + }); + if (ors.length > 1) { + return { + v: { '$or': ors } + }; + } + return { + v: ors[0] + }; + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } else { + return query; + } +}; + +function joinTableName(className, key) { + return '_Join:' + key + ':' + className; +} + +module.exports = DatabaseController; \ No newline at end of file diff --git a/lib/Controllers/FilesController.js b/lib/Controllers/FilesController.js new file mode 100644 index 0000000000..af653b83e7 --- /dev/null +++ b/lib/Controllers/FilesController.js @@ -0,0 +1,139 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FilesController = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _node = require('parse/node'); + +var _cryptoUtils = require('../cryptoUtils'); + +var _AdaptableController2 = require('./AdaptableController'); + +var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); + +var _FilesAdapter = require('../Adapters/Files/FilesAdapter'); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _mime = require('mime'); + +var _mime2 = _interopRequireDefault(_mime); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // FilesController.js + + +var legacyFilesRegex = new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*"); + +var FilesController = exports.FilesController = function (_AdaptableController) { + _inherits(FilesController, _AdaptableController); + + function FilesController() { + _classCallCheck(this, FilesController); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(FilesController).apply(this, arguments)); + } + + _createClass(FilesController, [{ + key: 'getFileData', + value: function getFileData(config, filename) { + return this.adapter.getFileData(filename); + } + }, { + key: 'createFile', + value: function createFile(config, filename, data, contentType) { + + var extname = _path2.default.extname(filename); + + var hasExtension = extname.length > 0; + + if (!hasExtension && contentType && _mime2.default.extension(contentType)) { + filename = filename + '.' + _mime2.default.extension(contentType); + } else if (hasExtension && !contentType) { + contentType = _mime2.default.lookup(filename); + } + + filename = (0, _cryptoUtils.randomHexString)(32) + '_' + filename; + + var location = this.adapter.getFileLocation(config, filename); + return this.adapter.createFile(filename, data, contentType).then(function () { + return Promise.resolve({ + url: location, + name: filename + }); + }); + } + }, { + key: 'deleteFile', + value: function deleteFile(config, filename) { + return this.adapter.deleteFile(filename); + } + + /** + * Find file references in REST-format object and adds the url key + * with the current mount point and app id. + * Object may be a single object or list of REST-format objects. + */ + + }, { + key: 'expandFilesInObject', + value: function expandFilesInObject(config, object) { + var _this2 = this; + + if (object instanceof Array) { + object.map(function (obj) { + return _this2.expandFilesInObject(config, obj); + }); + return; + } + if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object') { + return; + } + for (var key in object) { + var fileObject = object[key]; + if (fileObject && fileObject['__type'] === 'File') { + if (fileObject['url']) { + continue; + } + var filename = fileObject['name']; + // all filenames starting with "tfss-" should be from files.parsetfss.com + // all filenames starting with a "-" seperated UUID should be from files.parse.com + // all other filenames have been migrated or created from Parse Server + if (config.fileKey === undefined) { + fileObject['url'] = this.adapter.getFileLocation(config, filename); + } else { + if (filename.indexOf('tfss-') === 0) { + fileObject['url'] = 'http://files.parsetfss.com/' + config.fileKey + '/' + encodeURIComponent(filename); + } else if (legacyFilesRegex.test(filename)) { + fileObject['url'] = 'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename); + } else { + fileObject['url'] = this.adapter.getFileLocation(config, filename); + } + } + } + } + } + }, { + key: 'expectedAdapterType', + value: function expectedAdapterType() { + return _FilesAdapter.FilesAdapter; + } + }]); + + return FilesController; +}(_AdaptableController3.default); + +exports.default = FilesController; \ No newline at end of file diff --git a/lib/Controllers/HooksController.js b/lib/Controllers/HooksController.js new file mode 100644 index 0000000000..64034d0ec3 --- /dev/null +++ b/lib/Controllers/HooksController.js @@ -0,0 +1,274 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.HooksController = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** weak */ + +var _DatabaseAdapter = require("../DatabaseAdapter"); + +var DatabaseAdapter = _interopRequireWildcard(_DatabaseAdapter); + +var _triggers = require("../triggers"); + +var triggers = _interopRequireWildcard(_triggers); + +var _node = require("parse/node"); + +var Parse = _interopRequireWildcard(_node); + +var _request = require("request"); + +var request = _interopRequireWildcard(_request); + +var _logger = require("../logger"); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var DefaultHooksCollectionName = "_Hooks"; + +var HooksController = exports.HooksController = function () { + function HooksController(applicationId, databaseController, webhookKey) { + _classCallCheck(this, HooksController); + + this._applicationId = applicationId; + this._webhookKey = webhookKey; + this.database = databaseController; + } + + _createClass(HooksController, [{ + key: "load", + value: function load() { + var _this = this; + + return this._getHooks().then(function (hooks) { + hooks = hooks || []; + hooks.forEach(function (hook) { + _this.addHookToTriggers(hook); + }); + }); + } + }, { + key: "getFunction", + value: function getFunction(functionName) { + return this._getHooks({ functionName: functionName }, 1).then(function (results) { + return results[0]; + }); + } + }, { + key: "getFunctions", + value: function getFunctions() { + return this._getHooks({ functionName: { $exists: true } }); + } + }, { + key: "getTrigger", + value: function getTrigger(className, triggerName) { + return this._getHooks({ className: className, triggerName: triggerName }, 1).then(function (results) { + return results[0]; + }); + } + }, { + key: "getTriggers", + value: function getTriggers() { + return this._getHooks({ className: { $exists: true }, triggerName: { $exists: true } }); + } + }, { + key: "deleteFunction", + value: function deleteFunction(functionName) { + triggers.removeFunction(functionName, this._applicationId); + return this._removeHooks({ functionName: functionName }); + } + }, { + key: "deleteTrigger", + value: function deleteTrigger(className, triggerName) { + triggers.removeTrigger(triggerName, className, this._applicationId); + return this._removeHooks({ className: className, triggerName: triggerName }); + } + }, { + key: "_getHooks", + value: function _getHooks() { + var query = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var limit = arguments[1]; + + var options = limit ? { limit: limit } : undefined; + return this.database.find(DefaultHooksCollectionName, query).then(function (results) { + return results.map(function (result) { + delete result.objectId; + return result; + }); + }); + } + }, { + key: "_removeHooks", + value: function _removeHooks(query) { + return this.database.destroy(DefaultHooksCollectionName, query).then(function () { + return Promise.resolve({}); + }); + } + }, { + key: "saveHook", + value: function saveHook(hook) { + var query; + if (hook.functionName && hook.url) { + query = { functionName: hook.functionName }; + } else if (hook.triggerName && hook.className && hook.url) { + query = { className: hook.className, triggerName: hook.triggerName }; + } else { + throw new Parse.Error(143, "invalid hook declaration"); + } + return this.database.update(DefaultHooksCollectionName, query, hook, { upsert: true }).then(function () { + return Promise.resolve(hook); + }); + } + }, { + key: "addHookToTriggers", + value: function addHookToTriggers(hook) { + var wrappedFunction = wrapToHTTPRequest(hook, this._webhookKey); + wrappedFunction.url = hook.url; + if (hook.className) { + triggers.addTrigger(hook.triggerName, hook.className, wrappedFunction, this._applicationId); + } else { + triggers.addFunction(hook.functionName, wrappedFunction, null, this._applicationId); + } + } + }, { + key: "addHook", + value: function addHook(hook) { + this.addHookToTriggers(hook); + return this.saveHook(hook); + } + }, { + key: "createOrUpdateHook", + value: function createOrUpdateHook(aHook) { + var hook; + if (aHook && aHook.functionName && aHook.url) { + hook = {}; + hook.functionName = aHook.functionName; + hook.url = aHook.url; + } else if (aHook && aHook.className && aHook.url && aHook.triggerName && triggers.Types[aHook.triggerName]) { + hook = {}; + hook.className = aHook.className; + hook.url = aHook.url; + hook.triggerName = aHook.triggerName; + } else { + throw new Parse.Error(143, "invalid hook declaration"); + } + + return this.addHook(hook); + } + }, { + key: "createHook", + value: function createHook(aHook) { + var _this2 = this; + + if (aHook.functionName) { + return this.getFunction(aHook.functionName).then(function (result) { + if (result) { + throw new Parse.Error(143, "function name: " + aHook.functionName + " already exits"); + } else { + return _this2.createOrUpdateHook(aHook); + } + }); + } else if (aHook.className && aHook.triggerName) { + return this.getTrigger(aHook.className, aHook.triggerName).then(function (result) { + if (result) { + throw new Parse.Error(143, "class " + aHook.className + " already has trigger " + aHook.triggerName); + } + return _this2.createOrUpdateHook(aHook); + }); + } + + throw new Parse.Error(143, "invalid hook declaration"); + } + }, { + key: "updateHook", + value: function updateHook(aHook) { + var _this3 = this; + + if (aHook.functionName) { + return this.getFunction(aHook.functionName).then(function (result) { + if (result) { + return _this3.createOrUpdateHook(aHook); + } + throw new Parse.Error(143, "no function named: " + aHook.functionName + " is defined"); + }); + } else if (aHook.className && aHook.triggerName) { + return this.getTrigger(aHook.className, aHook.triggerName).then(function (result) { + if (result) { + return _this3.createOrUpdateHook(aHook); + } + throw new Parse.Error(143, "class " + aHook.className + " does not exist"); + }); + } + throw new Parse.Error(143, "invalid hook declaration"); + } + }]); + + return HooksController; +}(); + +function wrapToHTTPRequest(hook, key) { + return function (req, res) { + var jsonBody = {}; + for (var i in req) { + jsonBody[i] = req[i]; + } + if (req.object) { + jsonBody.object = req.object.toJSON(); + jsonBody.object.className = req.object.className; + } + if (req.original) { + jsonBody.original = req.original.toJSON(); + jsonBody.original.className = req.original.className; + } + var jsonRequest = { + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(jsonBody) + }; + + if (key) { + jsonRequest.headers['X-Parse-Webhook-Key'] = key; + } else { + _logger.logger.warn('Making outgoing webhook request without webhookKey being set!'); + } + + request.post(hook.url, jsonRequest, function (err, httpResponse, body) { + var result; + if (body) { + if (typeof body === "string") { + try { + body = JSON.parse(body); + } catch (e) { + err = { error: "Malformed response", code: -1 }; + } + } + if (!err) { + result = body.success; + err = body.error; + } + } + + if (err) { + return res.error(err); + } else if (hook.triggerName === 'beforeSave') { + if ((typeof result === "undefined" ? "undefined" : _typeof(result)) === 'object') { + delete result.createdAt; + delete result.updatedAt; + } + return res.success({ object: result }); + } else { + return res.success(result); + } + }); + }; +} + +exports.default = HooksController; \ No newline at end of file diff --git a/lib/Controllers/LiveQueryController.js b/lib/Controllers/LiveQueryController.js new file mode 100644 index 0000000000..7ecb1c6113 --- /dev/null +++ b/lib/Controllers/LiveQueryController.js @@ -0,0 +1,69 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LiveQueryController = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _ParseCloudCodePublisher = require('../LiveQuery/ParseCloudCodePublisher'); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var LiveQueryController = exports.LiveQueryController = function () { + function LiveQueryController(config) { + _classCallCheck(this, LiveQueryController); + + var classNames = void 0; + // If config is empty, we just assume no classs needs to be registered as LiveQuery + if (!config || !config.classNames) { + this.classNames = new Set(); + } else if (config.classNames instanceof Array) { + this.classNames = new Set(config.classNames); + } else { + throw 'liveQuery.classes should be an array of string'; + } + this.liveQueryPublisher = new _ParseCloudCodePublisher.ParseCloudCodePublisher(config); + } + + _createClass(LiveQueryController, [{ + key: 'onAfterSave', + value: function onAfterSave(className, currentObject, originalObject) { + if (!this.hasLiveQuery(className)) { + return; + } + var req = this._makePublisherRequest(currentObject, originalObject); + this.liveQueryPublisher.onCloudCodeAfterSave(req); + } + }, { + key: 'onAfterDelete', + value: function onAfterDelete(className, currentObject, originalObject) { + if (!this.hasLiveQuery(className)) { + return; + } + var req = this._makePublisherRequest(currentObject, originalObject); + this.liveQueryPublisher.onCloudCodeAfterDelete(req); + } + }, { + key: 'hasLiveQuery', + value: function hasLiveQuery(className) { + return this.classNames.has(className); + } + }, { + key: '_makePublisherRequest', + value: function _makePublisherRequest(currentObject, originalObject) { + var req = { + object: currentObject + }; + if (currentObject) { + req.original = originalObject; + } + return req; + } + }]); + + return LiveQueryController; +}(); + +exports.default = LiveQueryController; \ No newline at end of file diff --git a/lib/Controllers/LoggerController.js b/lib/Controllers/LoggerController.js new file mode 100644 index 0000000000..37508e4d61 --- /dev/null +++ b/lib/Controllers/LoggerController.js @@ -0,0 +1,117 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LoggerController = exports.LogOrder = exports.LogLevel = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _node = require('parse/node'); + +var _PromiseRouter = require('../PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _AdaptableController2 = require('./AdaptableController'); + +var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); + +var _LoggerAdapter = require('../Adapters/Logger/LoggerAdapter'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; + +var LogLevel = exports.LogLevel = { + INFO: 'info', + ERROR: 'error' +}; + +var LogOrder = exports.LogOrder = { + DESCENDING: 'desc', + ASCENDING: 'asc' +}; + +var LoggerController = exports.LoggerController = function (_AdaptableController) { + _inherits(LoggerController, _AdaptableController); + + function LoggerController() { + _classCallCheck(this, LoggerController); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(LoggerController).apply(this, arguments)); + } + + _createClass(LoggerController, [{ + key: 'getLogs', + + + // Returns a promise for a {response} object. + // query params: + // level (optional) Level of logging you want to query for (info || error) + // from (optional) Start time for the search. Defaults to 1 week ago. + // until (optional) End time for the search. Defaults to current time. + // order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”. + // size (optional) Number of rows returned by search. Defaults to 10 + value: function getLogs() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + if (!this.adapter) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not availabe'); + } + options = LoggerController.parseOptions(options); + return this.adapter.query(options); + } + }, { + key: 'expectedAdapterType', + value: function expectedAdapterType() { + return _LoggerAdapter.LoggerAdapter; + } + }], [{ + key: 'validDateTime', + + + // check that date input is valid + value: function validDateTime(date) { + if (!date) { + return null; + } + date = new Date(date); + + if (!isNaN(date.getTime())) { + return date; + } + + return null; + } + }, { + key: 'parseOptions', + value: function parseOptions() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var from = LoggerController.validDateTime(options.from) || new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY); + var until = LoggerController.validDateTime(options.until) || new Date(); + var size = Number(options.size) || 10; + var order = options.order || LogOrder.DESCENDING; + var level = options.level || LogLevel.INFO; + + return { + from: from, + until: until, + size: size, + order: order, + level: level + }; + } + }]); + + return LoggerController; +}(_AdaptableController3.default); + +exports.default = LoggerController; \ No newline at end of file diff --git a/lib/Controllers/PushController.js b/lib/Controllers/PushController.js new file mode 100644 index 0000000000..a84c0cc60f --- /dev/null +++ b/lib/Controllers/PushController.js @@ -0,0 +1,258 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PushController = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _node = require('parse/node'); + +var _PromiseRouter = require('../PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +var _AdaptableController2 = require('./AdaptableController'); + +var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); + +var _PushAdapter = require('../Adapters/Push/PushAdapter'); + +var _deepcopy = require('deepcopy'); + +var _deepcopy2 = _interopRequireDefault(_deepcopy); + +var _RestQuery = require('../RestQuery'); + +var _RestQuery2 = _interopRequireDefault(_RestQuery); + +var _RestWrite = require('../RestWrite'); + +var _RestWrite2 = _interopRequireDefault(_RestWrite); + +var _Auth = require('../Auth'); + +var _pushStatusHandler = require('../pushStatusHandler'); + +var _pushStatusHandler2 = _interopRequireDefault(_pushStatusHandler); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var FEATURE_NAME = 'push'; +var UNSUPPORTED_BADGE_KEY = "unsupported"; + +var PushController = exports.PushController = function (_AdaptableController) { + _inherits(PushController, _AdaptableController); + + function PushController() { + _classCallCheck(this, PushController); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(PushController).apply(this, arguments)); + } + + _createClass(PushController, [{ + key: 'sendPush', + value: function sendPush() { + var body = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var where = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var config = arguments[2]; + + var _this2 = this; + + var auth = arguments[3]; + var onPushStatusSaved = arguments.length <= 4 || arguments[4] === undefined ? function () {} : arguments[4]; + + var pushAdapter = this.adapter; + if (!this.pushIsAvailable) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Push adapter is not available'); + } + if (!this.options) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Missing push configuration'); + } + PushController.validatePushType(where, pushAdapter.getValidPushTypes()); + // Replace the expiration_time with a valid Unix epoch milliseconds time + body['expiration_time'] = PushController.getExpirationTime(body); + // TODO: If the req can pass the checking, we return immediately instead of waiting + // pushes to be sent. We probably change this behaviour in the future. + var badgeUpdate = function badgeUpdate() { + return Promise.resolve(); + }; + if (body.data && body.data.badge) { + (function () { + var badge = body.data.badge; + var restUpdate = {}; + if (typeof badge == 'string' && badge.toLowerCase() === 'increment') { + restUpdate = { badge: { __op: 'Increment', amount: 1 } }; + } else if (Number(badge)) { + restUpdate = { badge: badge }; + } else { + throw "Invalid value for badge, expected number or 'Increment'"; + } + var updateWhere = (0, _deepcopy2.default)(where); + + badgeUpdate = function badgeUpdate() { + if (updateWhere.arn) { + return; + } + updateWhere.deviceType = 'ios'; + // Build a real RestQuery so we can use it in RestWrite + var restQuery = new _RestQuery2.default(config, (0, _Auth.master)(config), '_Installation', updateWhere); + return restQuery.buildRestWhere().then(function () { + var write = new _RestWrite2.default(config, (0, _Auth.master)(config), '_Installation', restQuery.restWhere, restUpdate); + write.runOptions.many = true; + return write.execute(); + }); + }; + })(); + } + var pushStatus = (0, _pushStatusHandler2.default)(config); + return Promise.resolve().then(function () { + return pushStatus.setInitial(body, where); + }).then(function () { + onPushStatusSaved(pushStatus.objectId); + return badgeUpdate(); + }).then(function () { + if (_this2.adapter.snsConfig && where.arn) { + body.data.badge = null; // skip badge increment by bulk push send + return { + results: where + }; + } + return _rest2.default.find(config, auth, '_Installation', where); + }).then(function (response) { + if (!response.results) { + return Promise.reject({ error: 'PushController: no results in query' }); + } + pushStatus.setRunning(response.results); + return _this2.sendToAdapter(body, response.results, pushStatus, config); + }).then(function (results) { + return pushStatus.complete(results); + }).catch(function (err) { + pushStatus.fail(err); + return Promise.reject(err); + }); + } + }, { + key: 'sendToAdapter', + value: function sendToAdapter(body, installations, pushStatus, config) { + var _this3 = this; + + if (body.data && body.data.badge && typeof body.data.badge == 'string' && body.data.badge.toLowerCase() == "increment") { + var _ret2 = function () { + // Collect the badges to reduce the # of calls + var badgeInstallationsMap = installations.reduce(function (map, installation) { + var badge = installation.badge; + if (installation.deviceType != "ios") { + badge = UNSUPPORTED_BADGE_KEY; + } + map[badge + ''] = map[badge + ''] || []; + map[badge + ''].push(installation); + return map; + }, {}); + + // Map the on the badges count and return the send result + var promises = Object.keys(badgeInstallationsMap).map(function (badge) { + var payload = (0, _deepcopy2.default)(body); + if (badge == UNSUPPORTED_BADGE_KEY) { + delete payload.data.badge; + } else { + payload.data.badge = parseInt(badge); + } + return _this3.adapter.send(payload, badgeInstallationsMap[badge], pushStatus.objectId); + }); + return { + v: Promise.all(promises) + }; + }(); + + if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; + } + return this.adapter.send(body, installations, pushStatus.objectId); + } + + /** + * Get expiration time from the request body. + * @param {Object} request A request object + * @returns {Number|undefined} The expiration time if it exists in the request + */ + + }, { + key: 'expectedAdapterType', + value: function expectedAdapterType() { + return _PushAdapter.PushAdapter; + } + }, { + key: 'pushIsAvailable', + get: function get() { + return !!this.adapter; + } + }], [{ + key: 'validatePushType', + + + /** + * Check whether the deviceType parameter in qury condition is valid or not. + * @param {Object} where A query condition + * @param {Array} validPushTypes An array of valid push types(string) + */ + value: function validatePushType() { + var where = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var validPushTypes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; + + var deviceTypeField = where.deviceType || {}; + var deviceTypes = []; + if (typeof deviceTypeField === 'string') { + deviceTypes.push(deviceTypeField); + } else if (typeof deviceTypeField['$in'] === 'array') { + deviceTypes.concat(deviceTypeField['$in']); + } + for (var i = 0; i < deviceTypes.length; i++) { + var deviceType = deviceTypes[i]; + if (validPushTypes.indexOf(deviceType) < 0) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, deviceType + ' is not supported push type.'); + } + } + } + }, { + key: 'getExpirationTime', + value: function getExpirationTime() { + var body = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var hasExpirationTime = !!body['expiration_time']; + if (!hasExpirationTime) { + return; + } + var expirationTimeParam = body['expiration_time']; + var expirationTime; + if (typeof expirationTimeParam === 'number') { + expirationTime = new Date(expirationTimeParam * 1000); + } else if (typeof expirationTimeParam === 'string') { + expirationTime = new Date(expirationTimeParam); + } else { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, body['expiration_time'] + ' is not valid time.'); + } + // Check expirationTime is valid or not, if it is not valid, expirationTime is NaN + if (!isFinite(expirationTime)) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, body['expiration_time'] + ' is not valid time.'); + } + return expirationTime.valueOf(); + } + }]); + + return PushController; +}(_AdaptableController3.default); + +exports.default = PushController; \ No newline at end of file diff --git a/lib/Controllers/SchemaCache.js b/lib/Controllers/SchemaCache.js new file mode 100644 index 0000000000..6607f3d258 --- /dev/null +++ b/lib/Controllers/SchemaCache.js @@ -0,0 +1,96 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _cryptoUtils = require("../cryptoUtils"); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var MAIN_SCHEMA = "__MAIN_SCHEMA"; +var SCHEMA_CACHE_PREFIX = "__SCHEMA"; +var ALL_KEYS = "__ALL_KEYS"; + +var SchemaCache = function () { + function SchemaCache(cacheController) { + var ttl = arguments.length <= 1 || arguments[1] === undefined ? 30 : arguments[1]; + + _classCallCheck(this, SchemaCache); + + this.ttl = ttl; + if (typeof ttl == 'string') { + this.ttl = parseInt(ttl); + } + this.cache = cacheController; + this.prefix = SCHEMA_CACHE_PREFIX + (0, _cryptoUtils.randomString)(20); + } + + _createClass(SchemaCache, [{ + key: "put", + value: function put(key, value) { + var _this = this; + + return this.cache.get(this.prefix + ALL_KEYS).then(function (allKeys) { + allKeys = allKeys || {}; + allKeys[key] = true; + return Promise.all([_this.cache.put(_this.prefix + ALL_KEYS, allKeys, _this.ttl), _this.cache.put(key, value, _this.ttl)]); + }); + } + }, { + key: "getAllClasses", + value: function getAllClasses() { + if (!this.ttl) { + return Promise.resolve(null); + } + return this.cache.get(this.prefix + MAIN_SCHEMA); + } + }, { + key: "setAllClasses", + value: function setAllClasses(schema) { + if (!this.ttl) { + return Promise.resolve(null); + } + return this.put(this.prefix + MAIN_SCHEMA, schema); + } + }, { + key: "setOneSchema", + value: function setOneSchema(className, schema) { + if (!this.ttl) { + return Promise.resolve(null); + } + return this.put(this.prefix + className, schema); + } + }, { + key: "getOneSchema", + value: function getOneSchema(className) { + if (!this.ttl) { + return Promise.resolve(null); + } + return this.cache.get(this.prefix + className); + } + }, { + key: "clear", + value: function clear() { + var _this2 = this; + + // That clears all caches... + var promise = Promise.resolve(); + return this.cache.get(this.prefix + ALL_KEYS).then(function (allKeys) { + if (!allKeys) { + return; + } + var promises = Object.keys(allKeys).map(function (key) { + return _this2.cache.del(key); + }); + return Promise.all(promises); + }); + } + }]); + + return SchemaCache; +}(); + +exports.default = SchemaCache; \ No newline at end of file diff --git a/lib/Controllers/SchemaController.js b/lib/Controllers/SchemaController.js new file mode 100644 index 0000000000..529daef191 --- /dev/null +++ b/lib/Controllers/SchemaController.js @@ -0,0 +1,1011 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.convertSchemaToAdapterSchema = exports.defaultColumns = exports.systemClasses = exports.buildMergedSchemaObject = exports.invalidClassNameMessage = exports.fieldNameIsValid = exports.classNameIsValid = exports.load = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } + +// This class handles schema validation, persistence, and modification. +// +// Each individual Schema object should be immutable. The helpers to +// do things with the Schema just return a new schema when the schema +// is changed. +// +// The canonical place to store this Schema is in the database itself, +// in a _SCHEMA collection. This is not the right way to do it for an +// open source framework, but it's backward compatible, so we're +// keeping it this way for now. +// +// In API-handling code, you should only use the Schema class via the +// DatabaseController. This will let us replace the schema logic for +// different databases. +// TODO: hide all schema logic inside the database adapter. + +var Parse = require('parse/node').Parse; + + +var defaultColumns = Object.freeze({ + // Contain the default columns for every parse object type (except _Join collection) + _Default: { + "objectId": { type: 'String' }, + "createdAt": { type: 'Date' }, + "updatedAt": { type: 'Date' }, + "ACL": { type: 'ACL' } + }, + // The additional default columns for the _User collection (in addition to DefaultCols) + _User: { + "username": { type: 'String' }, + "password": { type: 'String' }, + "email": { type: 'String' }, + "emailVerified": { type: 'Boolean' } + }, + // The additional default columns for the _Installation collection (in addition to DefaultCols) + _Installation: { + "installationId": { type: 'String' }, + "deviceToken": { type: 'String' }, + "channels": { type: 'Array' }, + "deviceType": { type: 'String' }, + "pushType": { type: 'String' }, + "GCMSenderId": { type: 'String' }, + "timeZone": { type: 'String' }, + "localeIdentifier": { type: 'String' }, + "badge": { type: 'Number' }, + "appVersion": { type: 'String' }, + "appName": { type: 'String' }, + "appIdentifier": { type: 'String' }, + "parseVersion": { type: 'String' } + }, + // The additional default columns for the _Role collection (in addition to DefaultCols) + _Role: { + "name": { type: 'String' }, + "users": { type: 'Relation', targetClass: '_User' }, + "roles": { type: 'Relation', targetClass: '_Role' } + }, + // The additional default columns for the _Session collection (in addition to DefaultCols) + _Session: { + "restricted": { type: 'Boolean' }, + "user": { type: 'Pointer', targetClass: '_User' }, + "installationId": { type: 'String' }, + "sessionToken": { type: 'String' }, + "expiresAt": { type: 'Date' }, + "createdWith": { type: 'Object' } + }, + _Product: { + "productIdentifier": { type: 'String' }, + "download": { type: 'File' }, + "downloadName": { type: 'String' }, + "icon": { type: 'File' }, + "order": { type: 'Number' }, + "title": { type: 'String' }, + "subtitle": { type: 'String' } + }, + _PushStatus: { + "pushTime": { type: 'String' }, + "source": { type: 'String' }, // rest or webui + "query": { type: 'String' }, // the stringified JSON query + "payload": { type: 'Object' }, // the JSON payload, + "title": { type: 'String' }, + "expiry": { type: 'Number' }, + "status": { type: 'String' }, + "numSent": { type: 'Number' }, + "numFailed": { type: 'Number' }, + "pushHash": { type: 'String' }, + "errorMessage": { type: 'Object' }, + "sentPerType": { type: 'Object' }, + "failedPerType": { type: 'Object' } + } +}); + +var requiredColumns = Object.freeze({ + _Product: ["productIdentifier", "icon", "order", "title", "subtitle"], + _Role: ["name", "ACL"] +}); + +var systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus']); + +var volatileClasses = Object.freeze(['_PushStatus', '_Hooks', '_GlobalConfig']); + +// 10 alpha numberic chars + uppercase +var userIdRegex = /^[a-zA-Z0-9]{10}$/; +// Anything that start with role +var roleRegex = /^role:.*/; +// * permission +var publicRegex = /^\*$/; + +var permissionKeyRegex = Object.freeze([userIdRegex, roleRegex, publicRegex]); + +function verifyPermissionKey(key) { + var result = permissionKeyRegex.reduce(function (isGood, regEx) { + isGood = isGood || key.match(regEx) != null; + return isGood; + }, false); + if (!result) { + throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + key + '\' is not a valid key for class level permissions'); + } +} + +var CLPValidKeys = Object.freeze(['find', 'get', 'create', 'update', 'delete', 'addField', 'readUserFields', 'writeUserFields']); +function validateCLP(perms, fields) { + if (!perms) { + return; + } + Object.keys(perms).forEach(function (operation) { + if (CLPValidKeys.indexOf(operation) == -1) { + throw new Parse.Error(Parse.Error.INVALID_JSON, operation + ' is not a valid operation for class level permissions'); + } + + if (operation === 'readUserFields' || operation === 'writeUserFields') { + if (!Array.isArray(perms[operation])) { + throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + perms[operation] + '\' is not a valid value for class level permissions ' + operation); + } else { + perms[operation].forEach(function (key) { + if (!fields[key] || fields[key].type != 'Pointer' || fields[key].targetClass != '_User') { + throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + key + '\' is not a valid column for class level pointer permissions ' + operation); + } + }); + } + return; + } + + Object.keys(perms[operation]).forEach(function (key) { + verifyPermissionKey(key); + var perm = perms[operation][key]; + if (perm !== true) { + throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + perm + '\' is not a valid value for class level permissions ' + operation + ':' + key + ':' + perm); + } + }); + }); +} +var joinClassRegex = /^_Join:[A-Za-z0-9_]+:[A-Za-z0-9_]+/; +var classAndFieldRegex = /^[A-Za-z][A-Za-z0-9_]*$/; +function classNameIsValid(className) { + // Valid classes must: + return ( + // Be one of _User, _Installation, _Role, _Session OR + systemClasses.indexOf(className) > -1 || + // Be a join table OR + joinClassRegex.test(className) || + // Include only alpha-numeric and underscores, and not start with an underscore or number + fieldNameIsValid(className) + ); +} + +// Valid fields must be alpha-numeric, and not start with an underscore or number +function fieldNameIsValid(fieldName) { + return classAndFieldRegex.test(fieldName); +} + +// Checks that it's not trying to clobber one of the default fields of the class. +function fieldNameIsValidForClass(fieldName, className) { + if (!fieldNameIsValid(fieldName)) { + return false; + } + if (defaultColumns._Default[fieldName]) { + return false; + } + if (defaultColumns[className] && defaultColumns[className][fieldName]) { + return false; + } + return true; +} + +function invalidClassNameMessage(className) { + return 'Invalid classname: ' + className + ', classnames can only have alphanumeric characters and _, and must start with an alpha character '; +} + +var invalidJsonError = new Parse.Error(Parse.Error.INVALID_JSON, "invalid JSON"); +var validNonRelationOrPointerTypes = ['Number', 'String', 'Boolean', 'Date', 'Object', 'Array', 'GeoPoint', 'File']; +// Returns an error suitable for throwing if the type is invalid +var fieldTypeIsInvalid = function fieldTypeIsInvalid(_ref) { + var type = _ref.type; + var targetClass = _ref.targetClass; + + if (['Pointer', 'Relation'].includes(type)) { + if (!targetClass) { + return new Parse.Error(135, 'type ' + type + ' needs a class name'); + } else if (typeof targetClass !== 'string') { + return invalidJsonError; + } else if (!classNameIsValid(targetClass)) { + return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass)); + } else { + return undefined; + } + } + if (typeof type !== 'string') { + return invalidJsonError; + } + if (!validNonRelationOrPointerTypes.includes(type)) { + return new Parse.Error(Parse.Error.INCORRECT_TYPE, 'invalid field type: ' + type); + } + return undefined; +}; + +var convertSchemaToAdapterSchema = function convertSchemaToAdapterSchema(schema) { + schema = injectDefaultSchema(schema); + delete schema.fields.ACL; + schema.fields._rperm = { type: 'Array' }; + schema.fields._wperm = { type: 'Array' }; + + if (schema.className === '_User') { + delete schema.fields.password; + schema.fields._hashed_password = { type: 'String' }; + } + + return schema; +}; + +var convertAdapterSchemaToParseSchema = function convertAdapterSchemaToParseSchema(_ref2) { + var schema = _objectWithoutProperties(_ref2, []); + + delete schema.fields._rperm; + delete schema.fields._wperm; + + schema.fields.ACL = { type: 'ACL' }; + + if (schema.className === '_User') { + delete schema.fields.authData; //Auth data is implicit + delete schema.fields._hashed_password; + schema.fields.password = { type: 'String' }; + } + + return schema; +}; + +var injectDefaultSchema = function injectDefaultSchema(_ref3) { + var className = _ref3.className; + var fields = _ref3.fields; + var classLevelPermissions = _ref3.classLevelPermissions; + return { + className: className, + fields: _extends({}, defaultColumns._Default, defaultColumns[className] || {}, fields), + classLevelPermissions: classLevelPermissions + }; +}; + +var dbTypeMatchesObjectType = function dbTypeMatchesObjectType(dbType, objectType) { + if (dbType.type !== objectType.type) return false; + if (dbType.targetClass !== objectType.targetClass) return false; + if (dbType === objectType.type) return true; + if (dbType.type === objectType.type) return true; + return false; +}; + +// Stores the entire schema of the app in a weird hybrid format somewhere between +// the mongo format and the Parse format. Soon, this will all be Parse format. + +var SchemaController = function () { + function SchemaController(databaseAdapter, schemaCache) { + _classCallCheck(this, SchemaController); + + this._dbAdapter = databaseAdapter; + this._cache = schemaCache; + // this.data[className][fieldName] tells you the type of that field, in mongo format + this.data = {}; + // this.perms[className][operation] tells you the acl-style permissions + this.perms = {}; + } + + _createClass(SchemaController, [{ + key: 'reloadData', + value: function reloadData() { + var _this = this; + + var options = arguments.length <= 0 || arguments[0] === undefined ? { clearCache: false } : arguments[0]; + + if (options.clearCache) { + this._cache.clear(); + } + if (this.reloadDataPromise && !options.clearCache) { + return this.reloadDataPromise; + } + this.data = {}; + this.perms = {}; + this.reloadDataPromise = this.getAllClasses(options).then(function (allSchemas) { + allSchemas.forEach(function (schema) { + _this.data[schema.className] = injectDefaultSchema(schema).fields; + _this.perms[schema.className] = schema.classLevelPermissions; + }); + + // Inject the in-memory classes + volatileClasses.forEach(function (className) { + _this.data[className] = injectDefaultSchema({ + className: className, + fields: {}, + classLevelPermissions: {} + }); + }); + delete _this.reloadDataPromise; + }, function (err) { + delete _this.reloadDataPromise; + throw err; + }); + return this.reloadDataPromise; + } + }, { + key: 'getAllClasses', + value: function getAllClasses() { + var _this2 = this; + + var options = arguments.length <= 0 || arguments[0] === undefined ? { clearCache: false } : arguments[0]; + + if (options.clearCache) { + this._cache.clear(); + } + return this._cache.getAllClasses().then(function (allClasses) { + if (allClasses && allClasses.length && !options.clearCache) { + return Promise.resolve(allClasses); + } + return _this2._dbAdapter.getAllClasses().then(function (allSchemas) { + return allSchemas.map(injectDefaultSchema); + }).then(function (allSchemas) { + return _this2._cache.setAllClasses(allSchemas).then(function () { + return allSchemas; + }); + }); + }); + } + }, { + key: 'getOneSchema', + value: function getOneSchema(className) { + var _this3 = this; + + var allowVolatileClasses = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; + var options = arguments.length <= 2 || arguments[2] === undefined ? { clearCache: false } : arguments[2]; + + if (options.clearCache) { + this._cache.clear(); + } + if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) { + return Promise.resolve(this.data[className]); + } + return this._cache.getOneSchema(className).then(function (cached) { + if (cached && !options.clearCache) { + return Promise.resolve(cached); + } + return _this3._dbAdapter.getClass(className).then(injectDefaultSchema).then(function (result) { + return _this3._cache.setOneSchema(className, result).then(function () { + return result; + }); + }); + }); + } + + // Create a new class that includes the three default fields. + // ACL is an implicit column that does not get an entry in the + // _SCHEMAS database. Returns a promise that resolves with the + // created schema, in mongo format. + // on success, and rejects with an error on fail. Ensure you + // have authorization (master key, or client class creation + // enabled) before calling this function. + + }, { + key: 'addClassIfNotExists', + value: function addClassIfNotExists(className) { + var _this4 = this; + + var fields = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var classLevelPermissions = arguments[2]; + + var validationError = this.validateNewClass(className, fields, classLevelPermissions); + if (validationError) { + return Promise.reject(validationError); + } + + return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields: fields, classLevelPermissions: classLevelPermissions, className: className })).then(convertAdapterSchemaToParseSchema).then(function (res) { + _this4._cache.clear(); + return res; + }).catch(function (error) { + if (error && error.code === Parse.Error.DUPLICATE_VALUE) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' already exists.'); + } else { + throw error; + } + }); + } + }, { + key: 'updateClass', + value: function updateClass(className, submittedFields, classLevelPermissions, database) { + var _this5 = this; + + return this.getOneSchema(className).then(function (schema) { + var existingFields = schema.fields; + Object.keys(submittedFields).forEach(function (name) { + var field = submittedFields[name]; + if (existingFields[name] && field.__op !== 'Delete') { + throw new Parse.Error(255, 'Field ' + name + ' exists, cannot update.'); + } + if (!existingFields[name] && field.__op === 'Delete') { + throw new Parse.Error(255, 'Field ' + name + ' does not exist, cannot delete.'); + } + }); + + delete existingFields._rperm; + delete existingFields._wperm; + var newSchema = buildMergedSchemaObject(existingFields, submittedFields); + var validationError = _this5.validateSchemaData(className, newSchema, classLevelPermissions, Object.keys(existingFields)); + if (validationError) { + throw new Parse.Error(validationError.code, validationError.error); + } + + // Finally we have checked to make sure the request is valid and we can start deleting fields. + // Do all deletions first, then a single save to _SCHEMA collection to handle all additions. + var deletePromises = []; + var insertedFields = []; + Object.keys(submittedFields).forEach(function (fieldName) { + if (submittedFields[fieldName].__op === 'Delete') { + var promise = _this5.deleteField(fieldName, className, database); + deletePromises.push(promise); + } else { + insertedFields.push(fieldName); + } + }); + + return Promise.all(deletePromises) // Delete Everything + .then(function () { + return _this5.reloadData({ clearCache: true }); + }) // Reload our Schema, so we have all the new values + .then(function () { + var promises = insertedFields.map(function (fieldName) { + var type = submittedFields[fieldName]; + return _this5.enforceFieldExists(className, fieldName, type); + }); + return Promise.all(promises); + }).then(function () { + return _this5.setPermissions(className, classLevelPermissions, newSchema); + }) + //TODO: Move this logic into the database adapter + .then(function () { + return { + className: className, + fields: _this5.data[className], + classLevelPermissions: _this5.perms[className] + }; + }); + }).catch(function (error) { + if (error === undefined) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' does not exist.'); + } else { + throw error; + } + }); + } + + // Returns a promise that resolves successfully to the new schema + // object or fails with a reason. + + }, { + key: 'enforceClassExists', + value: function enforceClassExists(className) { + var _this6 = this; + + if (this.data[className]) { + return Promise.resolve(this); + } + // We don't have this class. Update the schema + return this.addClassIfNotExists(className) + // The schema update succeeded. Reload the schema + .then(function () { + return _this6.reloadData({ clearCache: true }); + }).catch(function (error) { + // The schema update failed. This can be okay - it might + // have failed because there's a race condition and a different + // client is making the exact same schema update that we want. + // So just reload the schema. + return _this6.reloadData({ clearCache: true }); + }).then(function () { + // Ensure that the schema now validates + if (_this6.data[className]) { + return _this6; + } else { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'Failed to add ' + className); + } + }).catch(function (error) { + // The schema still doesn't validate. Give up + throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate'); + }); + } + }, { + key: 'validateNewClass', + value: function validateNewClass(className) { + var fields = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var classLevelPermissions = arguments[2]; + + if (this.data[className]) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' already exists.'); + } + if (!classNameIsValid(className)) { + return { + code: Parse.Error.INVALID_CLASS_NAME, + error: invalidClassNameMessage(className) + }; + } + return this.validateSchemaData(className, fields, classLevelPermissions, []); + } + }, { + key: 'validateSchemaData', + value: function validateSchemaData(className, fields, classLevelPermissions, existingFieldNames) { + for (var fieldName in fields) { + if (!existingFieldNames.includes(fieldName)) { + if (!fieldNameIsValid(fieldName)) { + return { + code: Parse.Error.INVALID_KEY_NAME, + error: 'invalid field name: ' + fieldName + }; + } + if (!fieldNameIsValidForClass(fieldName, className)) { + return { + code: 136, + error: 'field ' + fieldName + ' cannot be added' + }; + } + var error = fieldTypeIsInvalid(fields[fieldName]); + if (error) return { code: error.code, error: error.message }; + } + } + + for (var _fieldName in defaultColumns[className]) { + fields[_fieldName] = defaultColumns[className][_fieldName]; + } + + var geoPoints = Object.keys(fields).filter(function (key) { + return fields[key] && fields[key].type === 'GeoPoint'; + }); + if (geoPoints.length > 1) { + return { + code: Parse.Error.INCORRECT_TYPE, + error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.' + }; + } + validateCLP(classLevelPermissions, fields); + } + + // Sets the Class-level permissions for a given className, which must exist. + + }, { + key: 'setPermissions', + value: function setPermissions(className, perms, newSchema) { + var _this7 = this; + + if (typeof perms === 'undefined') { + return Promise.resolve(); + } + validateCLP(perms, newSchema); + return this._dbAdapter.setClassLevelPermissions(className, perms).then(function () { + return _this7.reloadData({ clearCache: true }); + }); + } + + // Returns a promise that resolves successfully to the new schema + // object if the provided className-fieldName-type tuple is valid. + // The className must already be validated. + // If 'freeze' is true, refuse to update the schema for this field. + + }, { + key: 'enforceFieldExists', + value: function enforceFieldExists(className, fieldName, type, freeze) { + var _this8 = this; + + if (fieldName.indexOf(".") > 0) { + // subdocument key (x.y) => ok if x is of type 'object' + fieldName = fieldName.split(".")[0]; + type = 'Object'; + } + if (!fieldNameIsValid(fieldName)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name: ' + fieldName + '.'); + } + + // If someone tries to create a new field with null/undefined as the value, return; + if (!type) { + return Promise.resolve(this); + } + + return this.reloadData().then(function () { + var expectedType = _this8.getExpectedType(className, fieldName); + if (typeof type === 'string') { + type = { type: type }; + } + + if (expectedType) { + if (!dbTypeMatchesObjectType(expectedType, type)) { + throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'schema mismatch for ' + className + '.' + fieldName + '; expected ' + (expectedType.type || expectedType) + ' but got ' + type.type); + } + return _this8; + } + + return _this8._dbAdapter.addFieldIfNotExists(className, fieldName, type).then(function () { + // The update succeeded. Reload the schema + return _this8.reloadData({ clearCache: true }); + }, function (error) { + //TODO: introspect the error and only reload if the error is one for which is makes sense to reload + + // The update failed. This can be okay - it might have been a race + // condition where another client updated the schema in the same + // way that we wanted to. So, just reload the schema + return _this8.reloadData({ clearCache: true }); + }).then(function (error) { + // Ensure that the schema now validates + if (!dbTypeMatchesObjectType(_this8.getExpectedType(className, fieldName), type)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'Could not add field ' + fieldName); + } + // Remove the cached schema + _this8._cache.clear(); + return _this8; + }); + }); + } + + // Delete a field, and remove that data from all objects. This is intended + // to remove unused fields, if other writers are writing objects that include + // this field, the field may reappear. Returns a Promise that resolves with + // no object on success, or rejects with { code, error } on failure. + // Passing the database and prefix is necessary in order to drop relation collections + // and remove fields from objects. Ideally the database would belong to + // a database adapter and this function would close over it or access it via member. + + }, { + key: 'deleteField', + value: function deleteField(fieldName, className, database) { + var _this9 = this; + + if (!classNameIsValid(className)) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(className)); + } + if (!fieldNameIsValid(fieldName)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid field name: ' + fieldName); + } + //Don't allow deleting the default fields. + if (!fieldNameIsValidForClass(fieldName, className)) { + throw new Parse.Error(136, 'field ' + fieldName + ' cannot be changed'); + } + + return this.getOneSchema(className, false, { clearCache: true }).catch(function (error) { + if (error === undefined) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' does not exist.'); + } else { + throw error; + } + }).then(function (schema) { + if (!schema.fields[fieldName]) { + throw new Parse.Error(255, 'Field ' + fieldName + ' does not exist, cannot delete.'); + } + if (schema.fields[fieldName].type == 'Relation') { + //For relations, drop the _Join table + return database.adapter.deleteFields(className, schema, [fieldName]).then(function () { + return database.adapter.deleteClass('_Join:' + fieldName + ':' + className); + }); + } + return database.adapter.deleteFields(className, schema, [fieldName]); + }).then(function () { + _this9._cache.clear(); + }); + } + + // Validates an object provided in REST format. + // Returns a promise that resolves to the new schema if this object is + // valid. + + }, { + key: 'validateObject', + value: function validateObject(className, object, query) { + var geocount = 0; + var promise = this.enforceClassExists(className); + + var _loop = function _loop(fieldName) { + if (object[fieldName] === undefined) { + return 'continue'; + } + var expected = getType(object[fieldName]); + if (expected === 'GeoPoint') { + geocount++; + } + if (geocount > 1) { + // Make sure all field validation operations run before we return. + // If not - we are continuing to run logic, but already provided response from the server. + return { + v: promise.then(function () { + return Promise.reject(new Parse.Error(Parse.Error.INCORRECT_TYPE, 'there can only be one geopoint field in a class')); + }) + }; + } + if (!expected) { + return 'continue'; + } + if (fieldName === 'ACL') { + // Every object has ACL implicitly. + return 'continue'; + } + + promise = promise.then(function (schema) { + return schema.enforceFieldExists(className, fieldName, expected); + }); + }; + + for (var fieldName in object) { + var _ret = _loop(fieldName); + + switch (_ret) { + case 'continue': + continue; + + default: + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + } + promise = thenValidateRequiredColumns(promise, className, object, query); + return promise; + } + + // Validates that all the properties are set for the object + + }, { + key: 'validateRequiredColumns', + value: function validateRequiredColumns(className, object, query) { + var columns = requiredColumns[className]; + if (!columns || columns.length == 0) { + return Promise.resolve(this); + } + + var missingColumns = columns.filter(function (column) { + if (query && query.objectId) { + if (object[column] && _typeof(object[column]) === "object") { + // Trying to delete a required column + return object[column].__op == 'Delete'; + } + // Not trying to do anything there + return false; + } + return !object[column]; + }); + + if (missingColumns.length > 0) { + throw new Parse.Error(Parse.Error.INCORRECT_TYPE, missingColumns[0] + ' is required.'); + } + return Promise.resolve(this); + } + + // Validates the base CLP for an operation + + }, { + key: 'testBaseCLP', + value: function testBaseCLP(className, aclGroup, operation) { + if (!this.perms[className] || !this.perms[className][operation]) { + return true; + } + var classPerms = this.perms[className]; + var perms = classPerms[operation]; + // Handle the public scenario quickly + if (perms['*']) { + return true; + } + // Check permissions against the aclGroup provided (array of userId/roles) + if (aclGroup.some(function (acl) { + return perms[acl] === true; + })) { + return true; + } + return false; + } + + // Validates an operation passes class-level-permissions set in the schema + + }, { + key: 'validatePermission', + value: function validatePermission(className, aclGroup, operation) { + if (this.testBaseCLP(className, aclGroup, operation)) { + return Promise.resolve(); + } + + if (!this.perms[className] || !this.perms[className][operation]) { + return true; + } + var classPerms = this.perms[className]; + var perms = classPerms[operation]; + // No matching CLP, let's check the Pointer permissions + // And handle those later + var permissionField = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields'; + + // Reject create when write lockdown + if (permissionField == 'writeUserFields' && operation == 'create') { + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied for action ' + operation + ' on class ' + className + '.'); + } + + // Process the readUserFields later + if (Array.isArray(classPerms[permissionField]) && classPerms[permissionField].length > 0) { + return Promise.resolve(); + } + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied for action ' + operation + ' on class ' + className + '.'); + } + }, { + key: 'getExpectedType', + + + // Returns the expected type for a className+key combination + // or undefined if the schema is not set + value: function getExpectedType(className, fieldName) { + if (this.data && this.data[className]) { + var expectedType = this.data[className][fieldName]; + return expectedType === 'map' ? 'Object' : expectedType; + } + return undefined; + } + }, { + key: 'hasClass', + + + // Checks if a given class is in the schema. + value: function hasClass(className) { + var _this10 = this; + + return this.reloadData().then(function () { + return !!_this10.data[className]; + }); + } + }]); + + return SchemaController; +}(); + +// Returns a promise for a new Schema. + + +var load = function load(dbAdapter, schemaCache, options) { + var schema = new SchemaController(dbAdapter, schemaCache); + return schema.reloadData(options).then(function () { + return schema; + }); +}; + +// Builds a new schema (in schema API response format) out of an +// existing mongo schema + a schemas API put request. This response +// does not include the default fields, as it is intended to be passed +// to mongoSchemaFromFieldsAndClassName. No validation is done here, it +// is done in mongoSchemaFromFieldsAndClassName. +function buildMergedSchemaObject(existingFields, putRequest) { + var newSchema = {}; + var sysSchemaField = Object.keys(defaultColumns).indexOf(existingFields._id) === -1 ? [] : Object.keys(defaultColumns[existingFields._id]); + for (var oldField in existingFields) { + if (oldField !== '_id' && oldField !== 'ACL' && oldField !== 'updatedAt' && oldField !== 'createdAt' && oldField !== 'objectId') { + if (sysSchemaField.length > 0 && sysSchemaField.indexOf(oldField) !== -1) { + continue; + } + var fieldIsDeleted = putRequest[oldField] && putRequest[oldField].__op === 'Delete'; + if (!fieldIsDeleted) { + newSchema[oldField] = existingFields[oldField]; + } + } + } + for (var newField in putRequest) { + if (newField !== 'objectId' && putRequest[newField].__op !== 'Delete') { + if (sysSchemaField.length > 0 && sysSchemaField.indexOf(newField) !== -1) { + continue; + } + newSchema[newField] = putRequest[newField]; + } + } + return newSchema; +} + +// Given a schema promise, construct another schema promise that +// validates this field once the schema loads. +function thenValidateRequiredColumns(schemaPromise, className, object, query) { + return schemaPromise.then(function (schema) { + return schema.validateRequiredColumns(className, object, query); + }); +} + +// Gets the type from a REST API formatted object, where 'type' is +// extended past javascript types to include the rest of the Parse +// type system. +// The output should be a valid schema value. +// TODO: ensure that this is compatible with the format used in Open DB +function getType(obj) { + var type = typeof obj === 'undefined' ? 'undefined' : _typeof(obj); + switch (type) { + case 'boolean': + return 'Boolean'; + case 'string': + return 'String'; + case 'number': + return 'Number'; + case 'map': + case 'object': + if (!obj) { + return undefined; + } + return getObjectType(obj); + case 'function': + case 'symbol': + case 'undefined': + default: + throw 'bad obj: ' + obj; + } +} + +// This gets the type for non-JSON types like pointers and files, but +// also gets the appropriate type for $ operators. +// Returns null if the type is unknown. +function getObjectType(obj) { + if (obj instanceof Array) { + return 'Array'; + } + if (obj.__type) { + switch (obj.__type) { + case 'Pointer': + if (obj.className) { + return { + type: 'Pointer', + targetClass: obj.className + }; + } + case 'File': + if (obj.name) { + return 'File'; + } + case 'Date': + if (obj.iso) { + return 'Date'; + } + case 'GeoPoint': + if (obj.latitude != null && obj.longitude != null) { + return 'GeoPoint'; + } + case 'Bytes': + if (obj.base64) { + return; + } + default: + throw new Parse.Error(Parse.Error.INCORRECT_TYPE, "This is not a valid " + obj.__type); + } + } + if (obj['$ne']) { + return getObjectType(obj['$ne']); + } + if (obj.__op) { + switch (obj.__op) { + case 'Increment': + return 'Number'; + case 'Delete': + return null; + case 'Add': + case 'AddUnique': + case 'Remove': + return 'Array'; + case 'AddRelation': + case 'RemoveRelation': + return { + type: 'Relation', + targetClass: obj.objects[0].className + }; + case 'Batch': + return getObjectType(obj.ops[0]); + default: + throw 'unexpected op: ' + obj.__op; + } + } + return 'Object'; +} + +exports.load = load; +exports.classNameIsValid = classNameIsValid; +exports.fieldNameIsValid = fieldNameIsValid; +exports.invalidClassNameMessage = invalidClassNameMessage; +exports.buildMergedSchemaObject = buildMergedSchemaObject; +exports.systemClasses = systemClasses; +exports.defaultColumns = defaultColumns; +exports.convertSchemaToAdapterSchema = convertSchemaToAdapterSchema; \ No newline at end of file diff --git a/lib/Controllers/UserController.js b/lib/Controllers/UserController.js new file mode 100644 index 0000000000..859dd66fc6 --- /dev/null +++ b/lib/Controllers/UserController.js @@ -0,0 +1,261 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.UserController = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _cryptoUtils = require('../cryptoUtils'); + +var _triggers = require('../triggers'); + +var _AdaptableController2 = require('./AdaptableController'); + +var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); + +var _MailAdapter = require('../Adapters/Email/MailAdapter'); + +var _MailAdapter2 = _interopRequireDefault(_MailAdapter); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var DatabaseAdapter = require('../DatabaseAdapter'); +var RestWrite = require('../RestWrite'); +var RestQuery = require('../RestQuery'); +var hash = require('../password').hash; +var Auth = require('../Auth'); + +var UserController = exports.UserController = function (_AdaptableController) { + _inherits(UserController, _AdaptableController); + + function UserController(adapter, appId) { + var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + _classCallCheck(this, UserController); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(UserController).call(this, adapter, appId, options)); + } + + _createClass(UserController, [{ + key: 'validateAdapter', + value: function validateAdapter(adapter) { + // Allow no adapter + if (!adapter && !this.shouldVerifyEmails) { + return; + } + _get(Object.getPrototypeOf(UserController.prototype), 'validateAdapter', this).call(this, adapter); + } + }, { + key: 'expectedAdapterType', + value: function expectedAdapterType() { + return _MailAdapter2.default; + } + }, { + key: 'setEmailVerifyToken', + value: function setEmailVerifyToken(user) { + if (this.shouldVerifyEmails) { + user._email_verify_token = (0, _cryptoUtils.randomString)(25); + user.emailVerified = false; + + if (this.config.emailVerifyTokenValidityDuration) { + user._email_verify_token_expires_at = Parse._encode(this.config.generateEmailVerifyTokenExpiresAt()); + } + } + } + }, { + key: 'verifyEmail', + value: function verifyEmail(username, token) { + if (!this.shouldVerifyEmails) { + // Trying to verify email when not enabled + // TODO: Better error here. + throw undefined; + } + + var query = { username: username, _email_verify_token: token }; + var updateFields = { emailVerified: true, _email_verify_token: { __op: 'Delete' } }; + + // if the email verify token needs to be validated then + // add additional query params and additional fields that need to be updated + if (this.config.emailVerifyTokenValidityDuration) { + query.emailVerified = false; + query._email_verify_token_expires_at = { $gt: Parse._encode(new Date()) }; + + updateFields._email_verify_token_expires_at = { __op: 'Delete' }; + } + + return this.config.database.update('_User', query, updateFields).then(function (document) { + if (!document) { + throw undefined; + } + return Promise.resolve(document); + }); + } + }, { + key: 'checkResetTokenValidity', + value: function checkResetTokenValidity(username, token) { + return this.config.database.find('_User', { + username: username, + _perishable_token: token + }, { limit: 1 }).then(function (results) { + if (results.length != 1) { + throw undefined; + } + return results[0]; + }); + } + }, { + key: 'getUserIfNeeded', + value: function getUserIfNeeded(user) { + if (user.username && user.email) { + return Promise.resolve(user); + } + var where = {}; + if (user.username) { + where.username = user.username; + } + if (user.email) { + where.email = user.email; + } + + var query = new RestQuery(this.config, Auth.master(this.config), '_User', where); + return query.execute().then(function (result) { + if (result.results.length != 1) { + throw undefined; + } + return result.results[0]; + }); + } + }, { + key: 'sendVerificationEmail', + value: function sendVerificationEmail(user) { + var _this2 = this; + + if (!this.shouldVerifyEmails) { + return; + } + var token = encodeURIComponent(user._email_verify_token); + // We may need to fetch the user in case of update email + this.getUserIfNeeded(user).then(function (user) { + var username = encodeURIComponent(user.username); + var link = _this2.config.verifyEmailURL + '?token=' + token + '&username=' + username; + var options = { + appName: _this2.config.appName, + link: link, + user: (0, _triggers.inflate)('_User', user) + }; + if (_this2.adapter.sendVerificationEmail) { + _this2.adapter.sendVerificationEmail(options); + } else { + _this2.adapter.sendMail(_this2.defaultVerificationEmail(options)); + } + }); + } + }, { + key: 'setPasswordResetToken', + value: function setPasswordResetToken(email) { + return this.config.database.update('_User', { email: email }, { _perishable_token: (0, _cryptoUtils.randomString)(25) }, {}, true); + } + }, { + key: 'sendPasswordResetEmail', + value: function sendPasswordResetEmail(email) { + var _this3 = this; + + if (!this.adapter) { + throw "Trying to send a reset password but no adapter is set"; + // TODO: No adapter? + return; + } + + return this.setPasswordResetToken(email).then(function (user) { + var token = encodeURIComponent(user._perishable_token); + var username = encodeURIComponent(user.username); + var link = _this3.config.requestResetPasswordURL + '?token=' + token + '&username=' + username; + + var options = { + appName: _this3.config.appName, + link: link, + user: (0, _triggers.inflate)('_User', user) + }; + + if (_this3.adapter.sendPasswordResetEmail) { + _this3.adapter.sendPasswordResetEmail(options); + } else { + _this3.adapter.sendMail(_this3.defaultResetPasswordEmail(options)); + } + + return Promise.resolve(user); + }); + } + }, { + key: 'updatePassword', + value: function updatePassword(username, token, password, config) { + var _this4 = this; + + return this.checkResetTokenValidity(username, token).then(function (user) { + return updateUserPassword(user.objectId, password, _this4.config); + }) + // clear reset password token + .then(function () { + return _this4.config.database.update('_User', { username: username }, { + _perishable_token: { __op: 'Delete' } + }); + }); + } + }, { + key: 'defaultVerificationEmail', + value: function defaultVerificationEmail(_ref) { + var link = _ref.link; + var user = _ref.user; + var appName = _ref.appName; + + var text = "Hi,\n\n" + "You are being asked to confirm the e-mail address " + user.get("email") + " with " + appName + "\n\n" + "" + "Click here to confirm it:\n" + link; + var to = user.get("email"); + var subject = 'Please verify your e-mail for ' + appName; + return { text: text, to: to, subject: subject }; + } + }, { + key: 'defaultResetPasswordEmail', + value: function defaultResetPasswordEmail(_ref2) { + var link = _ref2.link; + var user = _ref2.user; + var appName = _ref2.appName; + + var text = "Hi,\n\n" + "You requested to reset your password for " + appName + ".\n\n" + "" + "Click here to reset it:\n" + link; + var to = user.get("email"); + var subject = 'Password Reset for ' + appName; + return { text: text, to: to, subject: subject }; + } + }, { + key: 'shouldVerifyEmails', + get: function get() { + return this.options.verifyUserEmails; + } + }]); + + return UserController; +}(_AdaptableController3.default); + +// Mark this private + + +function updateUserPassword(userId, password, config) { + return _rest2.default.update(config, Auth.master(config), '_User', userId, { + password: password + }); +} + +exports.default = UserController; \ No newline at end of file diff --git a/lib/DatabaseAdapter.js b/lib/DatabaseAdapter.js new file mode 100644 index 0000000000..54fc6e6a0f --- /dev/null +++ b/lib/DatabaseAdapter.js @@ -0,0 +1,27 @@ +'use strict'; + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +//Used by tests +function destroyAllDataPermanently() { + if (process.env.TESTING) { + // This is super janky, but destroyAllDataPermanently is + // a janky interface, so we need to have some jankyness + // to support it + return Promise.all(Object.keys(_cache2.default.cache).map(function (appId) { + var app = _cache2.default.get(appId); + if (app.databaseController) { + return app.databaseController.deleteEverything(); + } else { + return Promise.resolve(); + } + })); + } + throw 'Only supported in test environment'; +} + +module.exports = { destroyAllDataPermanently: destroyAllDataPermanently }; \ No newline at end of file diff --git a/lib/LiveQuery/Client.js b/lib/LiveQuery/Client.js new file mode 100644 index 0000000000..8b1289124a --- /dev/null +++ b/lib/LiveQuery/Client.js @@ -0,0 +1,162 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Client = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PLog = require('./PLog'); + +var _PLog2 = _interopRequireDefault(_PLog); + +var _node = require('parse/node'); + +var _node2 = _interopRequireDefault(_node); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var dafaultFields = ['className', 'objectId', 'updatedAt', 'createdAt', 'ACL']; + +var Client = function () { + function Client(id, parseWebSocket) { + _classCallCheck(this, Client); + + this.id = id; + this.parseWebSocket = parseWebSocket; + this.roles = []; + this.subscriptionInfos = new Map(); + this.pushConnect = this._pushEvent('connected'); + this.pushSubscribe = this._pushEvent('subscribed'); + this.pushUnsubscribe = this._pushEvent('unsubscribed'); + this.pushCreate = this._pushEvent('create'); + this.pushEnter = this._pushEvent('enter'); + this.pushUpdate = this._pushEvent('update'); + this.pushDelete = this._pushEvent('delete'); + this.pushLeave = this._pushEvent('leave'); + } + + _createClass(Client, [{ + key: 'addSubscriptionInfo', + value: function addSubscriptionInfo(requestId, subscriptionInfo) { + this.subscriptionInfos.set(requestId, subscriptionInfo); + } + }, { + key: 'getSubscriptionInfo', + value: function getSubscriptionInfo(requestId) { + return this.subscriptionInfos.get(requestId); + } + }, { + key: 'deleteSubscriptionInfo', + value: function deleteSubscriptionInfo(requestId) { + return this.subscriptionInfos.delete(requestId); + } + }, { + key: '_pushEvent', + value: function _pushEvent(type) { + return function (subscriptionId, parseObjectJSON) { + var response = { + 'op': type, + 'clientId': this.id + }; + if (typeof subscriptionId !== 'undefined') { + response['requestId'] = subscriptionId; + } + if (typeof parseObjectJSON !== 'undefined') { + var fields = void 0; + if (this.subscriptionInfos.has(subscriptionId)) { + fields = this.subscriptionInfos.get(subscriptionId).fields; + } + response['object'] = this._toJSONWithFields(parseObjectJSON, fields); + } + Client.pushResponse(this.parseWebSocket, JSON.stringify(response)); + }; + } + }, { + key: '_toJSONWithFields', + value: function _toJSONWithFields(parseObjectJSON, fields) { + if (!fields) { + return parseObjectJSON; + } + var limitedParseObject = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = dafaultFields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var field = _step.value; + + limitedParseObject[field] = parseObjectJSON[field]; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = fields[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var _field = _step2.value; + + if (_field in parseObjectJSON) { + limitedParseObject[_field] = parseObjectJSON[_field]; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + return limitedParseObject; + } + }], [{ + key: 'pushResponse', + value: function pushResponse(parseWebSocket, message) { + _PLog2.default.verbose('Push Response : %j', message); + parseWebSocket.send(message); + } + }, { + key: 'pushError', + value: function pushError(parseWebSocket, code, error) { + var reconnect = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3]; + + Client.pushResponse(parseWebSocket, JSON.stringify({ + 'op': 'error', + 'error': error, + 'code': code, + 'reconnect': reconnect + })); + } + }]); + + return Client; +}(); + +exports.Client = Client; \ No newline at end of file diff --git a/lib/LiveQuery/EventEmitterPubSub.js b/lib/LiveQuery/EventEmitterPubSub.js new file mode 100644 index 0000000000..90159a156d --- /dev/null +++ b/lib/LiveQuery/EventEmitterPubSub.js @@ -0,0 +1,92 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.EventEmitterPubSub = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _events = require('events'); + +var _events2 = _interopRequireDefault(_events); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var emitter = new _events2.default.EventEmitter(); + +var Publisher = function () { + function Publisher(emitter) { + _classCallCheck(this, Publisher); + + this.emitter = emitter; + } + + _createClass(Publisher, [{ + key: 'publish', + value: function publish(channel, message) { + this.emitter.emit(channel, message); + } + }]); + + return Publisher; +}(); + +var Subscriber = function (_events$EventEmitter) { + _inherits(Subscriber, _events$EventEmitter); + + function Subscriber(emitter) { + _classCallCheck(this, Subscriber); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Subscriber).call(this)); + + _this.emitter = emitter; + _this.subscriptions = new Map(); + return _this; + } + + _createClass(Subscriber, [{ + key: 'subscribe', + value: function subscribe(channel) { + var _this2 = this; + + var handler = function handler(message) { + _this2.emit('message', channel, message); + }; + this.subscriptions.set(channel, handler); + this.emitter.on(channel, handler); + } + }, { + key: 'unsubscribe', + value: function unsubscribe(channel) { + if (!this.subscriptions.has(channel)) { + return; + } + this.emitter.removeListener(channel, this.subscriptions.get(channel)); + this.subscriptions.delete(channel); + } + }]); + + return Subscriber; +}(_events2.default.EventEmitter); + +function createPublisher() { + return new Publisher(emitter); +} + +function createSubscriber() { + return new Subscriber(emitter); +} + +var EventEmitterPubSub = { + createPublisher: createPublisher, + createSubscriber: createSubscriber +}; + +exports.EventEmitterPubSub = EventEmitterPubSub; \ No newline at end of file diff --git a/lib/LiveQuery/Id.js b/lib/LiveQuery/Id.js new file mode 100644 index 0000000000..048cc21084 --- /dev/null +++ b/lib/LiveQuery/Id.js @@ -0,0 +1,34 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Id = function () { + function Id(className, objectId) { + _classCallCheck(this, Id); + + this.className = className; + this.objectId = objectId; + } + + _createClass(Id, [{ + key: 'toString', + value: function toString() { + return this.className + ':' + this.objectId; + } + }], [{ + key: 'fromString', + value: function fromString(str) { + var split = str.split(':'); + if (split.length !== 2) { + throw new TypeError('Cannot create Id object from this string'); + } + return new Id(split[0], split[1]); + } + }]); + + return Id; +}(); + +module.exports = Id; \ No newline at end of file diff --git a/lib/LiveQuery/PLog.js b/lib/LiveQuery/PLog.js new file mode 100644 index 0000000000..12149d8433 --- /dev/null +++ b/lib/LiveQuery/PLog.js @@ -0,0 +1,7 @@ +'use strict'; + +var _logger = require('../logger'); + +var PLog = (0, _logger.addGroup)('parse-live-query-server'); + +module.exports = PLog; \ No newline at end of file diff --git a/lib/LiveQuery/ParseCloudCodePublisher.js b/lib/LiveQuery/ParseCloudCodePublisher.js new file mode 100644 index 0000000000..e89761acee --- /dev/null +++ b/lib/LiveQuery/ParseCloudCodePublisher.js @@ -0,0 +1,63 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ParseCloudCodePublisher = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _ParsePubSub = require('./ParsePubSub'); + +var _PLog = require('./PLog'); + +var _PLog2 = _interopRequireDefault(_PLog); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var ParseCloudCodePublisher = function () { + + // config object of the publisher, right now it only contains the redisURL, + // but we may extend it later. + function ParseCloudCodePublisher() { + var config = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, ParseCloudCodePublisher); + + this.parsePublisher = _ParsePubSub.ParsePubSub.createPublisher(config); + } + + _createClass(ParseCloudCodePublisher, [{ + key: 'onCloudCodeAfterSave', + value: function onCloudCodeAfterSave(request) { + this._onCloudCodeMessage('afterSave', request); + } + }, { + key: 'onCloudCodeAfterDelete', + value: function onCloudCodeAfterDelete(request) { + this._onCloudCodeMessage('afterDelete', request); + } + + // Request is the request object from cloud code functions. request.object is a ParseObject. + + }, { + key: '_onCloudCodeMessage', + value: function _onCloudCodeMessage(type, request) { + _PLog2.default.verbose('Raw request from cloud code current : %j | original : %j', request.object, request.original); + // We need the full JSON which includes className + var message = { + currentParseObject: request.object._toFullJSON() + }; + if (request.original) { + message.originalParseObject = request.original._toFullJSON(); + } + this.parsePublisher.publish(type, JSON.stringify(message)); + } + }]); + + return ParseCloudCodePublisher; +}(); + +exports.ParseCloudCodePublisher = ParseCloudCodePublisher; \ No newline at end of file diff --git a/lib/LiveQuery/ParseLiveQueryServer.js b/lib/LiveQuery/ParseLiveQueryServer.js new file mode 100644 index 0000000000..0b7b03ee72 --- /dev/null +++ b/lib/LiveQuery/ParseLiveQueryServer.js @@ -0,0 +1,727 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ParseLiveQueryServer = undefined; + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _tv = require('tv4'); + +var _tv2 = _interopRequireDefault(_tv); + +var _node = require('parse/node'); + +var _node2 = _interopRequireDefault(_node); + +var _Subscription = require('./Subscription'); + +var _Client = require('./Client'); + +var _ParseWebSocketServer = require('./ParseWebSocketServer'); + +var _PLog = require('./PLog'); + +var _PLog2 = _interopRequireDefault(_PLog); + +var _RequestSchema = require('./RequestSchema'); + +var _RequestSchema2 = _interopRequireDefault(_RequestSchema); + +var _QueryTools = require('./QueryTools'); + +var _ParsePubSub = require('./ParsePubSub'); + +var _SessionTokenCache = require('./SessionTokenCache'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var ParseLiveQueryServer = function () { + // className -> (queryHash -> subscription) + function ParseLiveQueryServer(server, config) { + var _this = this; + + _classCallCheck(this, ParseLiveQueryServer); + + this.clientId = 0; + this.clients = new Map(); + this.subscriptions = new Map(); + + config = config || {}; + // Set LogLevel + _PLog2.default.level = config.logLevel || 'INFO'; + // Store keys, convert obj to map + var keyPairs = config.keyPairs || {}; + this.keyPairs = new Map(); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = Object.keys(keyPairs)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var key = _step.value; + + this.keyPairs.set(key, keyPairs[key]); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + _PLog2.default.verbose('Support key pairs', this.keyPairs); + + // Initialize Parse + _node2.default.Object.disableSingleInstance(); + _node2.default.User.enableUnsafeCurrentUser(); + + var serverURL = config.serverURL || _node2.default.serverURL; + _node2.default.serverURL = serverURL; + var appId = config.appId || _node2.default.applicationId; + var javascriptKey = _node2.default.javaScriptKey; + var masterKey = config.masterKey || _node2.default.masterKey; + _node2.default.initialize(appId, javascriptKey, masterKey); + + // Initialize websocket server + this.parseWebSocketServer = new _ParseWebSocketServer.ParseWebSocketServer(server, function (parseWebsocket) { + return _this._onConnect(parseWebsocket); + }, config.websocketTimeout); + + // Initialize subscriber + this.subscriber = _ParsePubSub.ParsePubSub.createSubscriber({ + redisURL: config.redisURL + }); + this.subscriber.subscribe('afterSave'); + this.subscriber.subscribe('afterDelete'); + // Register message handler for subscriber. When publisher get messages, it will publish message + // to the subscribers and the handler will be called. + this.subscriber.on('message', function (channel, messageStr) { + _PLog2.default.verbose('Subscribe messsage %j', messageStr); + var message = JSON.parse(messageStr); + _this._inflateParseObject(message); + if (channel === 'afterSave') { + _this._onAfterSave(message); + } else if (channel === 'afterDelete') { + _this._onAfterDelete(message); + } else { + _PLog2.default.error('Get message %s from unknown channel %j', message, channel); + } + }); + + // Initialize sessionToken cache + this.sessionTokenCache = new _SessionTokenCache.SessionTokenCache(config.cacheTimeout); + } + + // Message is the JSON object from publisher. Message.currentParseObject is the ParseObject JSON after changes. + // Message.originalParseObject is the original ParseObject JSON. + + // The subscriber we use to get object update from publisher + + + _createClass(ParseLiveQueryServer, [{ + key: '_inflateParseObject', + value: function _inflateParseObject(message) { + // Inflate merged object + var currentParseObject = message.currentParseObject; + var className = currentParseObject.className; + var parseObject = new _node2.default.Object(className); + parseObject._finishFetch(currentParseObject); + message.currentParseObject = parseObject; + // Inflate original object + var originalParseObject = message.originalParseObject; + if (originalParseObject) { + className = originalParseObject.className; + parseObject = new _node2.default.Object(className); + parseObject._finishFetch(originalParseObject); + message.originalParseObject = parseObject; + } + } + + // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. + // Message.originalParseObject is the original ParseObject. + + }, { + key: '_onAfterDelete', + value: function _onAfterDelete(message) { + var _this2 = this; + + _PLog2.default.verbose('afterDelete is triggered'); + + var deletedParseObject = message.currentParseObject.toJSON(); + var className = deletedParseObject.className; + _PLog2.default.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id); + _PLog2.default.verbose('Current client number : %d', this.clients.size); + + var classSubscriptions = this.subscriptions.get(className); + if (typeof classSubscriptions === 'undefined') { + _PLog2.default.error('Can not find subscriptions under this class ' + className); + return; + } + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = classSubscriptions.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var subscription = _step2.value; + + var isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription); + if (!isSubscriptionMatched) { + continue; + } + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + var _loop = function _loop() { + var _step3$value = _slicedToArray(_step3.value, 2); + + var clientId = _step3$value[0]; + var requestIds = _step3$value[1]; + + var client = _this2.clients.get(clientId); + if (typeof client === 'undefined') { + return 'continue'; + } + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + var _loop2 = function _loop2() { + var requestId = _step4.value; + + var acl = message.currentParseObject.getACL(); + // Check ACL + _this2._matchesACL(acl, client, requestId).then(function (isMatched) { + if (!isMatched) { + return null; + } + client.pushDelete(requestId, deletedParseObject); + }, function (error) { + _PLog2.default.error('Matching ACL error : ', error); + }); + }; + + for (var _iterator4 = requestIds[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + _loop2(); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + }; + + for (var _iterator3 = subscription.clientRequestIds.entries()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var _ret = _loop(); + + if (_ret === 'continue') continue; + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + } + + // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. + // Message.originalParseObject is the original ParseObject. + + }, { + key: '_onAfterSave', + value: function _onAfterSave(message) { + var _this3 = this; + + _PLog2.default.verbose('afterSave is triggered'); + + var originalParseObject = null; + if (message.originalParseObject) { + originalParseObject = message.originalParseObject.toJSON(); + } + var currentParseObject = message.currentParseObject.toJSON(); + var className = currentParseObject.className; + _PLog2.default.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id); + _PLog2.default.verbose('Current client number : %d', this.clients.size); + + var classSubscriptions = this.subscriptions.get(className); + if (typeof classSubscriptions === 'undefined') { + _PLog2.default.error('Can not find subscriptions under this class ' + className); + return; + } + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + var _loop3 = function _loop3() { + var subscription = _step5.value; + + var isOriginalSubscriptionMatched = _this3._matchesSubscription(originalParseObject, subscription); + var isCurrentSubscriptionMatched = _this3._matchesSubscription(currentParseObject, subscription); + var _iteratorNormalCompletion6 = true; + var _didIteratorError6 = false; + var _iteratorError6 = undefined; + + try { + var _loop4 = function _loop4() { + var _step6$value = _slicedToArray(_step6.value, 2); + + var clientId = _step6$value[0]; + var requestIds = _step6$value[1]; + + var client = _this3.clients.get(clientId); + if (typeof client === 'undefined') { + return 'continue'; + } + var _iteratorNormalCompletion7 = true; + var _didIteratorError7 = false; + var _iteratorError7 = undefined; + + try { + var _loop5 = function _loop5() { + var requestId = _step7.value; + + // Set orignal ParseObject ACL checking promise, if the object does not match + // subscription, we do not need to check ACL + var originalACLCheckingPromise = void 0; + if (!isOriginalSubscriptionMatched) { + originalACLCheckingPromise = _node2.default.Promise.as(false); + } else { + var originalACL = void 0; + if (message.originalParseObject) { + originalACL = message.originalParseObject.getACL(); + } + originalACLCheckingPromise = _this3._matchesACL(originalACL, client, requestId); + } + // Set current ParseObject ACL checking promise, if the object does not match + // subscription, we do not need to check ACL + var currentACLCheckingPromise = void 0; + if (!isCurrentSubscriptionMatched) { + currentACLCheckingPromise = _node2.default.Promise.as(false); + } else { + var currentACL = message.currentParseObject.getACL(); + currentACLCheckingPromise = _this3._matchesACL(currentACL, client, requestId); + } + + _node2.default.Promise.when(originalACLCheckingPromise, currentACLCheckingPromise).then(function (isOriginalMatched, isCurrentMatched) { + _PLog2.default.verbose('Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', originalParseObject, currentParseObject, isOriginalSubscriptionMatched, isCurrentSubscriptionMatched, isOriginalMatched, isCurrentMatched, subscription.hash); + + // Decide event type + var type = void 0; + if (isOriginalMatched && isCurrentMatched) { + type = 'Update'; + } else if (isOriginalMatched && !isCurrentMatched) { + type = 'Leave'; + } else if (!isOriginalMatched && isCurrentMatched) { + if (originalParseObject) { + type = 'Enter'; + } else { + type = 'Create'; + } + } else { + return null; + } + var functionName = 'push' + type; + client[functionName](requestId, currentParseObject); + }, function (error) { + _PLog2.default.error('Matching ACL error : ', error); + }); + }; + + for (var _iterator7 = requestIds[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { + _loop5(); + } + } catch (err) { + _didIteratorError7 = true; + _iteratorError7 = err; + } finally { + try { + if (!_iteratorNormalCompletion7 && _iterator7.return) { + _iterator7.return(); + } + } finally { + if (_didIteratorError7) { + throw _iteratorError7; + } + } + } + }; + + for (var _iterator6 = subscription.clientRequestIds.entries()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { + var _ret4 = _loop4(); + + if (_ret4 === 'continue') continue; + } + } catch (err) { + _didIteratorError6 = true; + _iteratorError6 = err; + } finally { + try { + if (!_iteratorNormalCompletion6 && _iterator6.return) { + _iterator6.return(); + } + } finally { + if (_didIteratorError6) { + throw _iteratorError6; + } + } + } + }; + + for (var _iterator5 = classSubscriptions.values()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + _loop3(); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + } + }, { + key: '_onConnect', + value: function _onConnect(parseWebsocket) { + var _this4 = this; + + parseWebsocket.on('message', function (request) { + if (typeof request === 'string') { + request = JSON.parse(request); + } + _PLog2.default.verbose('Request: %j', request); + + // Check whether this request is a valid request, return error directly if not + if (!_tv2.default.validate(request, _RequestSchema2.default['general']) || !_tv2.default.validate(request, _RequestSchema2.default[request.op])) { + _Client.Client.pushError(parseWebsocket, 1, _tv2.default.error.message); + _PLog2.default.error('Connect message error %s', _tv2.default.error.message); + return; + } + + switch (request.op) { + case 'connect': + _this4._handleConnect(parseWebsocket, request); + break; + case 'subscribe': + _this4._handleSubscribe(parseWebsocket, request); + break; + case 'unsubscribe': + _this4._handleUnsubscribe(parseWebsocket, request); + break; + default: + _Client.Client.pushError(parseWebsocket, 3, 'Get unknown operation'); + _PLog2.default.error('Get unknown operation', request.op); + } + }); + + parseWebsocket.on('disconnect', function () { + _PLog2.default.log('Client disconnect: %d', parseWebsocket.clientId); + var clientId = parseWebsocket.clientId; + if (!_this4.clients.has(clientId)) { + _PLog2.default.error('Can not find client %d on disconnect', clientId); + return; + } + + // Delete client + var client = _this4.clients.get(clientId); + _this4.clients.delete(clientId); + + // Delete client from subscriptions + var _iteratorNormalCompletion8 = true; + var _didIteratorError8 = false; + var _iteratorError8 = undefined; + + try { + for (var _iterator8 = client.subscriptionInfos.entries()[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { + var _step8$value = _slicedToArray(_step8.value, 2); + + var _requestId = _step8$value[0]; + var subscriptionInfo = _step8$value[1]; + + var _subscription = subscriptionInfo.subscription; + _subscription.deleteClientSubscription(clientId, _requestId); + + // If there is no client which is subscribing this subscription, remove it from subscriptions + var classSubscriptions = _this4.subscriptions.get(_subscription.className); + if (!_subscription.hasSubscribingClient()) { + classSubscriptions.delete(_subscription.hash); + } + // If there is no subscriptions under this class, remove it from subscriptions + if (classSubscriptions.size === 0) { + _this4.subscriptions.delete(_subscription.className); + } + } + } catch (err) { + _didIteratorError8 = true; + _iteratorError8 = err; + } finally { + try { + if (!_iteratorNormalCompletion8 && _iterator8.return) { + _iterator8.return(); + } + } finally { + if (_didIteratorError8) { + throw _iteratorError8; + } + } + } + + _PLog2.default.verbose('Current clients %d', _this4.clients.size); + _PLog2.default.verbose('Current subscriptions %d', _this4.subscriptions.size); + }); + } + }, { + key: '_matchesSubscription', + value: function _matchesSubscription(parseObject, subscription) { + // Object is undefined or null, not match + if (!parseObject) { + return false; + } + return (0, _QueryTools.matchesQuery)(parseObject, subscription.query); + } + }, { + key: '_matchesACL', + value: function _matchesACL(acl, client, requestId) { + var _this5 = this; + + // If ACL is undefined or null, or ACL has public read access, return true directly + if (!acl || acl.getPublicReadAccess()) { + return _node2.default.Promise.as(true); + } + // Check subscription sessionToken matches ACL first + var subscriptionInfo = client.getSubscriptionInfo(requestId); + if (typeof subscriptionInfo === 'undefined') { + return _node2.default.Promise.as(false); + } + + var subscriptionSessionToken = subscriptionInfo.sessionToken; + return this.sessionTokenCache.getUserId(subscriptionSessionToken).then(function (userId) { + return acl.getReadAccess(userId); + }).then(function (isSubscriptionSessionTokenMatched) { + if (isSubscriptionSessionTokenMatched) { + return _node2.default.Promise.as(true); + } + // Check client sessionToken matches ACL + var clientSessionToken = client.sessionToken; + return _this5.sessionTokenCache.getUserId(clientSessionToken).then(function (userId) { + return acl.getReadAccess(userId); + }); + }).then(function (isMatched) { + return _node2.default.Promise.as(isMatched); + }, function (error) { + return _node2.default.Promise.as(false); + }); + } + }, { + key: '_handleConnect', + value: function _handleConnect(parseWebsocket, request) { + if (!this._validateKeys(request, this.keyPairs)) { + _Client.Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); + _PLog2.default.error('Key in request is not valid'); + return; + } + var client = new _Client.Client(this.clientId, parseWebsocket); + parseWebsocket.clientId = this.clientId; + this.clientId += 1; + this.clients.set(parseWebsocket.clientId, client); + _PLog2.default.log('Create new client: %d', parseWebsocket.clientId); + client.pushConnect(); + } + }, { + key: '_validateKeys', + value: function _validateKeys(request, validKeyPairs) { + if (!validKeyPairs || validKeyPairs.size == 0) { + return true; + } + var isValid = false; + var _iteratorNormalCompletion9 = true; + var _didIteratorError9 = false; + var _iteratorError9 = undefined; + + try { + for (var _iterator9 = validKeyPairs[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { + var _step9$value = _slicedToArray(_step9.value, 2); + + var key = _step9$value[0]; + var secret = _step9$value[1]; + + if (!request[key] || request[key] !== secret) { + continue; + } + isValid = true; + break; + } + } catch (err) { + _didIteratorError9 = true; + _iteratorError9 = err; + } finally { + try { + if (!_iteratorNormalCompletion9 && _iterator9.return) { + _iterator9.return(); + } + } finally { + if (_didIteratorError9) { + throw _iteratorError9; + } + } + } + + return isValid; + } + }, { + key: '_handleSubscribe', + value: function _handleSubscribe(parseWebsocket, request) { + // If we can not find this client, return error to client + if (!parseWebsocket.hasOwnProperty('clientId')) { + _Client.Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before subscribing'); + _PLog2.default.error('Can not find this client, make sure you connect to server before subscribing'); + return; + } + var client = this.clients.get(parseWebsocket.clientId); + + // Get subscription from subscriptions, create one if necessary + var subscriptionHash = (0, _QueryTools.queryHash)(request.query); + // Add className to subscriptions if necessary + var className = request.query.className; + if (!this.subscriptions.has(className)) { + this.subscriptions.set(className, new Map()); + } + var classSubscriptions = this.subscriptions.get(className); + var subscription = void 0; + if (classSubscriptions.has(subscriptionHash)) { + subscription = classSubscriptions.get(subscriptionHash); + } else { + subscription = new _Subscription.Subscription(className, request.query.where, subscriptionHash); + classSubscriptions.set(subscriptionHash, subscription); + } + + // Add subscriptionInfo to client + var subscriptionInfo = { + subscription: subscription + }; + // Add selected fields and sessionToken for this subscription if necessary + if (request.query.fields) { + subscriptionInfo.fields = request.query.fields; + } + if (request.sessionToken) { + subscriptionInfo.sessionToken = request.sessionToken; + } + client.addSubscriptionInfo(request.requestId, subscriptionInfo); + + // Add clientId to subscription + subscription.addClientSubscription(parseWebsocket.clientId, request.requestId); + + client.pushSubscribe(request.requestId); + + _PLog2.default.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId); + _PLog2.default.verbose('Current client number: %d', this.clients.size); + } + }, { + key: '_handleUnsubscribe', + value: function _handleUnsubscribe(parseWebsocket, request) { + // If we can not find this client, return error to client + if (!parseWebsocket.hasOwnProperty('clientId')) { + _Client.Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before unsubscribing'); + _PLog2.default.error('Can not find this client, make sure you connect to server before unsubscribing'); + return; + } + var requestId = request.requestId; + var client = this.clients.get(parseWebsocket.clientId); + if (typeof client === 'undefined') { + _Client.Client.pushError(parseWebsocket, 2, 'Cannot find client with clientId ' + parseWebsocket.clientId + '. Make sure you connect to live query server before unsubscribing.'); + _PLog2.default.error('Can not find this client ' + parseWebsocket.clientId); + return; + } + + var subscriptionInfo = client.getSubscriptionInfo(requestId); + if (typeof subscriptionInfo === 'undefined') { + _Client.Client.pushError(parseWebsocket, 2, 'Cannot find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId + '. Make sure you subscribe to live query server before unsubscribing.'); + _PLog2.default.error('Can not find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId); + return; + } + + // Remove subscription from client + client.deleteSubscriptionInfo(requestId); + // Remove client from subscription + var subscription = subscriptionInfo.subscription; + var className = subscription.className; + subscription.deleteClientSubscription(parseWebsocket.clientId, requestId); + // If there is no client which is subscribing this subscription, remove it from subscriptions + var classSubscriptions = this.subscriptions.get(className); + if (!subscription.hasSubscribingClient()) { + classSubscriptions.delete(subscription.hash); + } + // If there is no subscriptions under this class, remove it from subscriptions + if (classSubscriptions.size === 0) { + this.subscriptions.delete(className); + } + + client.pushUnsubscribe(request.requestId); + + _PLog2.default.verbose('Delete client: %d | subscription: %d', parseWebsocket.clientId, request.requestId); + } + }]); + + return ParseLiveQueryServer; +}(); + +ParseLiveQueryServer.setLogLevel = function (logLevel) { + _PLog2.default.logLevel = logLevel; +}; + +exports.ParseLiveQueryServer = ParseLiveQueryServer; \ No newline at end of file diff --git a/lib/LiveQuery/ParsePubSub.js b/lib/LiveQuery/ParsePubSub.js new file mode 100644 index 0000000000..d3ba5fcb6f --- /dev/null +++ b/lib/LiveQuery/ParsePubSub.js @@ -0,0 +1,35 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ParsePubSub = undefined; + +var _RedisPubSub = require('./RedisPubSub'); + +var _EventEmitterPubSub = require('./EventEmitterPubSub'); + +var ParsePubSub = {}; + +function useRedis(config) { + var redisURL = config.redisURL; + return typeof redisURL !== 'undefined' && redisURL !== ''; +} + +ParsePubSub.createPublisher = function (config) { + if (useRedis(config)) { + return _RedisPubSub.RedisPubSub.createPublisher(config.redisURL); + } else { + return _EventEmitterPubSub.EventEmitterPubSub.createPublisher(); + } +}; + +ParsePubSub.createSubscriber = function (config) { + if (useRedis(config)) { + return _RedisPubSub.RedisPubSub.createSubscriber(config.redisURL); + } else { + return _EventEmitterPubSub.EventEmitterPubSub.createSubscriber(); + } +}; + +exports.ParsePubSub = ParsePubSub; \ No newline at end of file diff --git a/lib/LiveQuery/ParseWebSocketServer.js b/lib/LiveQuery/ParseWebSocketServer.js new file mode 100644 index 0000000000..f72aeaf59e --- /dev/null +++ b/lib/LiveQuery/ParseWebSocketServer.js @@ -0,0 +1,65 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ParseWebSocket = exports.ParseWebSocketServer = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PLog = require('./PLog'); + +var _PLog2 = _interopRequireDefault(_PLog); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var typeMap = new Map([['disconnect', 'close']]); + +var ParseWebSocketServer = exports.ParseWebSocketServer = function ParseWebSocketServer(server, onConnect) { + var websocketTimeout = arguments.length <= 2 || arguments[2] === undefined ? 10 * 1000 : arguments[2]; + + _classCallCheck(this, ParseWebSocketServer); + + var WebSocketServer = require('ws').Server; + var wss = new WebSocketServer({ server: server }); + wss.on('listening', function () { + _PLog2.default.log('Parse LiveQuery Server starts running'); + }); + wss.on('connection', function (ws) { + onConnect(new ParseWebSocket(ws)); + // Send ping to client periodically + var pingIntervalId = setInterval(function () { + if (ws.readyState == ws.OPEN) { + ws.ping(); + } else { + clearInterval(pingIntervalId); + } + }, websocketTimeout); + }); + this.server = wss; +}; + +var ParseWebSocket = exports.ParseWebSocket = function () { + function ParseWebSocket(ws) { + _classCallCheck(this, ParseWebSocket); + + this.ws = ws; + } + + _createClass(ParseWebSocket, [{ + key: 'on', + value: function on(type, callback) { + var wsType = typeMap.has(type) ? typeMap.get(type) : type; + this.ws.on(wsType, callback); + } + }, { + key: 'send', + value: function send(message, channel) { + this.ws.send(message); + } + }]); + + return ParseWebSocket; +}(); \ No newline at end of file diff --git a/lib/LiveQuery/QueryTools.js b/lib/LiveQuery/QueryTools.js new file mode 100644 index 0000000000..7622c44f24 --- /dev/null +++ b/lib/LiveQuery/QueryTools.js @@ -0,0 +1,282 @@ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var equalObjects = require('./equalObjects'); +var Id = require('./Id'); +var Parse = require('parse/node'); + +/** + * Query Hashes are deterministic hashes for Parse Queries. + * Any two queries that have the same set of constraints will produce the same + * hash. This lets us reliably group components by the queries they depend upon, + * and quickly determine if a query has changed. + */ + +/** + * Convert $or queries into an array of where conditions + */ +function flattenOrQueries(where) { + if (!where.hasOwnProperty('$or')) { + return where; + } + var accum = []; + for (var i = 0; i < where.$or.length; i++) { + accum = accum.concat(where.$or[i]); + } + return accum; +} + +/** + * Deterministically turns an object into a string. Disregards ordering + */ +function stringify(object) { + if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object' || object === null) { + if (typeof object === 'string') { + return '"' + object.replace(/\|/g, '%|') + '"'; + } + return object + ''; + } + if (Array.isArray(object)) { + var copy = object.map(stringify); + copy.sort(); + return '[' + copy.join(',') + ']'; + } + var sections = []; + var keys = Object.keys(object); + keys.sort(); + for (var k = 0; k < keys.length; k++) { + sections.push(stringify(keys[k]) + ':' + stringify(object[keys[k]])); + } + return '{' + sections.join(',') + '}'; +} + +/** + * Generate a hash from a query, with unique fields for columns, values, order, + * skip, and limit. + */ +function queryHash(query) { + if (query instanceof Parse.Query) { + query = { + className: query.className, + where: query._where + }; + } + var where = flattenOrQueries(query.where || {}); + var columns = []; + var values = []; + var i; + if (Array.isArray(where)) { + var uniqueColumns = {}; + for (i = 0; i < where.length; i++) { + var subValues = {}; + var keys = Object.keys(where[i]); + keys.sort(); + for (var j = 0; j < keys.length; j++) { + subValues[keys[j]] = where[i][keys[j]]; + uniqueColumns[keys[j]] = true; + } + values.push(subValues); + } + columns = Object.keys(uniqueColumns); + columns.sort(); + } else { + columns = Object.keys(where); + columns.sort(); + for (i = 0; i < columns.length; i++) { + values.push(where[columns[i]]); + } + } + + var sections = [columns.join(','), stringify(values)]; + + return query.className + ':' + sections.join('|'); +} + +/** + * matchesQuery -- Determines if an object would be returned by a Parse Query + * It's a lightweight, where-clause only implementation of a full query engine. + * Since we find queries that match objects, rather than objects that match + * queries, we can avoid building a full-blown query tool. + */ +function matchesQuery(object, query) { + if (query instanceof Parse.Query) { + var className = object.id instanceof Id ? object.id.className : object.className; + if (className !== query.className) { + return false; + } + return matchesQuery(object, query._where); + } + for (var field in query) { + if (!matchesKeyConstraints(object, field, query[field])) { + return false; + } + } + return true; +} + +/** + * Determines whether an object matches a single key's constraints + */ +function matchesKeyConstraints(object, key, constraints) { + if (constraints === null) { + return false; + } + var i; + if (key === '$or') { + for (i = 0; i < constraints.length; i++) { + if (matchesQuery(object, constraints[i])) { + return true; + } + } + return false; + } + if (key === '$relatedTo') { + // Bail! We can't handle relational queries locally + return false; + } + // Equality (or Array contains) cases + if ((typeof constraints === 'undefined' ? 'undefined' : _typeof(constraints)) !== 'object') { + if (Array.isArray(object[key])) { + return object[key].indexOf(constraints) > -1; + } + return object[key] === constraints; + } + var compareTo; + if (constraints.__type) { + if (constraints.__type === 'Pointer') { + return typeof object[key] !== 'undefined' && constraints.className === object[key].className && constraints.objectId === object[key].objectId; + } + compareTo = Parse._decode(key, constraints); + if (Array.isArray(object[key])) { + for (i = 0; i < object[key].length; i++) { + if (equalObjects(object[key][i], compareTo)) { + return true; + } + } + return false; + } + return equalObjects(object[key], compareTo); + } + // More complex cases + for (var condition in constraints) { + compareTo = constraints[condition]; + if (compareTo.__type) { + compareTo = Parse._decode(key, compareTo); + } + switch (condition) { + case '$lt': + if (object[key] >= compareTo) { + return false; + } + break; + case '$lte': + if (object[key] > compareTo) { + return false; + } + break; + case '$gt': + if (object[key] <= compareTo) { + return false; + } + break; + case '$gte': + if (object[key] < compareTo) { + return false; + } + break; + case '$ne': + if (equalObjects(object[key], compareTo)) { + return false; + } + break; + case '$in': + if (compareTo.indexOf(object[key]) < 0) { + return false; + } + break; + case '$nin': + if (compareTo.indexOf(object[key]) > -1) { + return false; + } + break; + case '$all': + for (i = 0; i < compareTo.length; i++) { + if (object[key].indexOf(compareTo[i]) < 0) { + return false; + } + } + break; + case '$exists': + var propertyExists = typeof object[key] !== 'undefined'; + var existenceIsRequired = constraints['$exists']; + if (typeof constraints['$exists'] !== 'boolean') { + // The SDK will never submit a non-boolean for $exists, but if someone + // tries to submit a non-boolean for $exits outside the SDKs, just ignore it. + break; + } + if (!propertyExists && existenceIsRequired || propertyExists && !existenceIsRequired) { + return false; + } + break; + case '$regex': + if ((typeof compareTo === 'undefined' ? 'undefined' : _typeof(compareTo)) === 'object') { + return compareTo.test(object[key]); + } + // JS doesn't support perl-style escaping + var expString = ''; + var escapeEnd = -2; + var escapeStart = compareTo.indexOf('\\Q'); + while (escapeStart > -1) { + // Add the unescaped portion + expString += compareTo.substring(escapeEnd + 2, escapeStart); + escapeEnd = compareTo.indexOf('\\E', escapeStart); + if (escapeEnd > -1) { + expString += compareTo.substring(escapeStart + 2, escapeEnd).replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&'); + } + + escapeStart = compareTo.indexOf('\\Q', escapeEnd); + } + expString += compareTo.substring(Math.max(escapeStart, escapeEnd + 2)); + var exp = new RegExp(expString, constraints.$options || ''); + if (!exp.test(object[key])) { + return false; + } + break; + case '$nearSphere': + var distance = compareTo.radiansTo(object[key]); + var max = constraints.$maxDistance || Infinity; + return distance <= max; + case '$within': + var southWest = compareTo.$box[0]; + var northEast = compareTo.$box[1]; + if (southWest.latitude > northEast.latitude || southWest.longitude > northEast.longitude) { + // Invalid box, crosses the date line + return false; + } + return object[key].latitude > southWest.latitude && object[key].latitude < northEast.latitude && object[key].longitude > southWest.longitude && object[key].longitude < northEast.longitude; + case '$options': + // Not a query type, but a way to add options to $regex. Ignore and + // avoid the default + break; + case '$maxDistance': + // Not a query type, but a way to add a cap to $nearSphere. Ignore and + // avoid the default + break; + case '$select': + return false; + case '$dontSelect': + return false; + default: + return false; + } + } + return true; +} + +var QueryTools = { + queryHash: queryHash, + matchesQuery: matchesQuery +}; + +module.exports = QueryTools; \ No newline at end of file diff --git a/lib/LiveQuery/RedisPubSub.js b/lib/LiveQuery/RedisPubSub.js new file mode 100644 index 0000000000..7def28dbe7 --- /dev/null +++ b/lib/LiveQuery/RedisPubSub.js @@ -0,0 +1,27 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.RedisPubSub = undefined; + +var _redis = require('redis'); + +var _redis2 = _interopRequireDefault(_redis); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function createPublisher(redisURL) { + return _redis2.default.createClient(redisURL, { no_ready_check: true }); +} + +function createSubscriber(redisURL) { + return _redis2.default.createClient(redisURL, { no_ready_check: true }); +} + +var RedisPubSub = { + createPublisher: createPublisher, + createSubscriber: createSubscriber +}; + +exports.RedisPubSub = RedisPubSub; \ No newline at end of file diff --git a/lib/LiveQuery/RequestSchema.js b/lib/LiveQuery/RequestSchema.js new file mode 100644 index 0000000000..8ae11cafb4 --- /dev/null +++ b/lib/LiveQuery/RequestSchema.js @@ -0,0 +1,106 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var general = { + 'title': 'General request schema', + 'type': 'object', + 'properties': { + 'op': { + 'type': 'string', + 'enum': ['connect', 'subscribe', 'unsubscribe'] + } + } +}; + +var connect = { + 'title': 'Connect operation schema', + 'type': 'object', + 'properties': { + 'op': 'connect', + 'applicationId': { + 'type': 'string' + }, + 'javascriptKey': { + type: 'string' + }, + 'masterKey': { + type: 'string' + }, + 'clientKey': { + type: 'string' + }, + 'windowsKey': { + type: 'string' + }, + 'restAPIKey': { + 'type': 'string' + }, + 'sessionToken': { + 'type': 'string' + } + }, + 'required': ['op', 'applicationId'], + "additionalProperties": false +}; + +var subscribe = { + 'title': 'Subscribe operation schema', + 'type': 'object', + 'properties': { + 'op': 'subscribe', + 'requestId': { + 'type': 'number' + }, + 'query': { + 'title': 'Query field schema', + 'type': 'object', + 'properties': { + 'className': { + 'type': 'string' + }, + 'where': { + 'type': 'object' + }, + 'fields': { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + 'required': ['where', 'className'], + 'additionalProperties': false + }, + 'sessionToken': { + 'type': 'string' + } + }, + 'required': ['op', 'requestId', 'query'], + 'additionalProperties': false +}; + +var unsubscribe = { + 'title': 'Unsubscribe operation schema', + 'type': 'object', + 'properties': { + 'op': 'unsubscribe', + 'requestId': { + 'type': 'number' + } + }, + 'required': ['op', 'requestId'], + "additionalProperties": false +}; + +var RequestSchema = { + 'general': general, + 'connect': connect, + 'subscribe': subscribe, + 'unsubscribe': unsubscribe +}; + +exports.default = RequestSchema; \ No newline at end of file diff --git a/lib/LiveQuery/SessionTokenCache.js b/lib/LiveQuery/SessionTokenCache.js new file mode 100644 index 0000000000..2045744b0f --- /dev/null +++ b/lib/LiveQuery/SessionTokenCache.js @@ -0,0 +1,67 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SessionTokenCache = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _node = require('parse/node'); + +var _node2 = _interopRequireDefault(_node); + +var _lruCache = require('lru-cache'); + +var _lruCache2 = _interopRequireDefault(_lruCache); + +var _PLog = require('./PLog'); + +var _PLog2 = _interopRequireDefault(_PLog); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var SessionTokenCache = function () { + function SessionTokenCache() { + var timeout = arguments.length <= 0 || arguments[0] === undefined ? 30 * 24 * 60 * 60 * 1000 : arguments[0]; + var maxSize = arguments.length <= 1 || arguments[1] === undefined ? 10000 : arguments[1]; + + _classCallCheck(this, SessionTokenCache); + + this.cache = new _lruCache2.default({ + max: maxSize, + maxAge: timeout + }); + } + + _createClass(SessionTokenCache, [{ + key: 'getUserId', + value: function getUserId(sessionToken) { + var _this = this; + + if (!sessionToken) { + return _node2.default.Promise.error('Empty sessionToken'); + } + var userId = this.cache.get(sessionToken); + if (userId) { + _PLog2.default.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken); + return _node2.default.Promise.as(userId); + } + return _node2.default.User.become(sessionToken).then(function (user) { + _PLog2.default.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken); + var userId = user.id; + _this.cache.set(sessionToken, userId); + return _node2.default.Promise.as(userId); + }, function (error) { + _PLog2.default.error('Can not fetch userId for sessionToken %j, error %j', sessionToken, error); + return _node2.default.Promise.error(error); + }); + } + }]); + + return SessionTokenCache; +}(); + +exports.SessionTokenCache = SessionTokenCache; \ No newline at end of file diff --git a/lib/LiveQuery/Subscription.js b/lib/LiveQuery/Subscription.js new file mode 100644 index 0000000000..d9d9453563 --- /dev/null +++ b/lib/LiveQuery/Subscription.js @@ -0,0 +1,70 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Subscription = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _QueryTools = require('./QueryTools'); + +var _PLog = require('./PLog'); + +var _PLog2 = _interopRequireDefault(_PLog); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Subscription = function () { + // It is query condition eg query.where + function Subscription(className, query, queryHash) { + _classCallCheck(this, Subscription); + + this.className = className; + this.query = query; + this.hash = queryHash; + this.clientRequestIds = new Map(); + } + + _createClass(Subscription, [{ + key: 'addClientSubscription', + value: function addClientSubscription(clientId, requestId) { + if (!this.clientRequestIds.has(clientId)) { + this.clientRequestIds.set(clientId, []); + } + var requestIds = this.clientRequestIds.get(clientId); + requestIds.push(requestId); + } + }, { + key: 'deleteClientSubscription', + value: function deleteClientSubscription(clientId, requestId) { + var requestIds = this.clientRequestIds.get(clientId); + if (typeof requestIds === 'undefined') { + _PLog2.default.error('Can not find client %d to delete', clientId); + return; + } + + var index = requestIds.indexOf(requestId); + if (index < 0) { + _PLog2.default.error('Can not find client %d subscription %d to delete', clientId, requestId); + return; + } + requestIds.splice(index, 1); + // Delete client reference if it has no subscription + if (requestIds.length == 0) { + this.clientRequestIds.delete(clientId); + } + } + }, { + key: 'hasSubscribingClient', + value: function hasSubscribingClient() { + return this.clientRequestIds.size > 0; + } + }]); + + return Subscription; +}(); + +exports.Subscription = Subscription; \ No newline at end of file diff --git a/lib/LiveQuery/equalObjects.js b/lib/LiveQuery/equalObjects.js new file mode 100644 index 0000000000..a0e058a2c9 --- /dev/null +++ b/lib/LiveQuery/equalObjects.js @@ -0,0 +1,52 @@ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var toString = Object.prototype.toString; + +/** + * Determines whether two objects represent the same primitive, special Parse + * type, or full Parse Object. + */ +function equalObjects(a, b) { + if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) !== (typeof b === 'undefined' ? 'undefined' : _typeof(b))) { + return false; + } + if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) !== 'object') { + return a === b; + } + if (a === b) { + return true; + } + if (toString.call(a) === '[object Date]') { + if (toString.call(b) === '[object Date]') { + return +a === +b; + } + return false; + } + if (Array.isArray(a)) { + if (Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + for (var i = 0; i < a.length; i++) { + if (!equalObjects(a[i], b[i])) { + return false; + } + } + return true; + } + return false; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (var key in a) { + if (!equalObjects(a[key], b[key])) { + return false; + } + } + return true; +} + +module.exports = equalObjects; \ No newline at end of file diff --git a/lib/ParseServer.js b/lib/ParseServer.js new file mode 100644 index 0000000000..cc019c6676 --- /dev/null +++ b/lib/ParseServer.js @@ -0,0 +1,453 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _logger = require('./logger'); + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +var _Config = require('./Config'); + +var _Config2 = _interopRequireDefault(_Config); + +var _package = require('../package.json'); + +var _package2 = _interopRequireDefault(_package); + +var _PromiseRouter = require('./PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _requiredParameter = require('./requiredParameter'); + +var _requiredParameter2 = _interopRequireDefault(_requiredParameter); + +var _AnalyticsRouter = require('./Routers/AnalyticsRouter'); + +var _ClassesRouter = require('./Routers/ClassesRouter'); + +var _FeaturesRouter = require('./Routers/FeaturesRouter'); + +var _InMemoryCacheAdapter = require('./Adapters/Cache/InMemoryCacheAdapter'); + +var _AnalyticsController = require('./Controllers/AnalyticsController'); + +var _CacheController = require('./Controllers/CacheController'); + +var _AnalyticsAdapter = require('./Adapters/Analytics/AnalyticsAdapter'); + +var _FileLoggerAdapter = require('./Adapters/Logger/FileLoggerAdapter'); + +var _FilesController = require('./Controllers/FilesController'); + +var _FilesRouter = require('./Routers/FilesRouter'); + +var _FunctionsRouter = require('./Routers/FunctionsRouter'); + +var _GlobalConfigRouter = require('./Routers/GlobalConfigRouter'); + +var _GridStoreAdapter = require('./Adapters/Files/GridStoreAdapter'); + +var _HooksController = require('./Controllers/HooksController'); + +var _HooksRouter = require('./Routers/HooksRouter'); + +var _IAPValidationRouter = require('./Routers/IAPValidationRouter'); + +var _InstallationsRouter = require('./Routers/InstallationsRouter'); + +var _AdapterLoader = require('./Adapters/AdapterLoader'); + +var _LiveQueryController = require('./Controllers/LiveQueryController'); + +var _LoggerController = require('./Controllers/LoggerController'); + +var _LogsRouter = require('./Routers/LogsRouter'); + +var _ParseLiveQueryServer = require('./LiveQuery/ParseLiveQueryServer'); + +var _PublicAPIRouter = require('./Routers/PublicAPIRouter'); + +var _PushController = require('./Controllers/PushController'); + +var _PushRouter = require('./Routers/PushRouter'); + +var _cryptoUtils = require('./cryptoUtils'); + +var _RolesRouter = require('./Routers/RolesRouter'); + +var _SchemasRouter = require('./Routers/SchemasRouter'); + +var _SessionsRouter = require('./Routers/SessionsRouter'); + +var _UserController = require('./Controllers/UserController'); + +var _UsersRouter = require('./Routers/UsersRouter'); + +var _PurgeRouter = require('./Routers/PurgeRouter'); + +var _DatabaseController = require('./Controllers/DatabaseController'); + +var _DatabaseController2 = _interopRequireDefault(_DatabaseController); + +var _SchemaCache = require('./Controllers/SchemaCache'); + +var _SchemaCache2 = _interopRequireDefault(_SchemaCache); + +var _parseServerPushAdapter = require('parse-server-push-adapter'); + +var _parseServerPushAdapter2 = _interopRequireDefault(_parseServerPushAdapter); + +var _MongoStorageAdapter = require('./Adapters/Storage/Mongo/MongoStorageAdapter'); + +var _MongoStorageAdapter2 = _interopRequireDefault(_MongoStorageAdapter); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// ParseServer - open-source compatible API Server for Parse apps + +var batch = require('./batch'), + bodyParser = require('body-parser'), + DatabaseAdapter = require('./DatabaseAdapter'), + express = require('express'), + middlewares = require('./middlewares'), + multer = require('multer'), + Parse = require('parse/node').Parse, + path = require('path'), + authDataManager = require('./authDataManager'); + +if (!global._babelPolyfill) { + require('babel-polyfill'); +} + +var SchemaController = require('./Controllers/SchemaController'); + +// Mutate the Parse object to add the Cloud Code handlers +addParseCloud(); + +var requiredUserFields = { fields: _extends({}, SchemaController.defaultColumns._Default, SchemaController.defaultColumns._User) }; + +// ParseServer works like a constructor of an express app. +// The args that we understand are: +// "analyticsAdapter": an adapter class for analytics +// "filesAdapter": a class like GridStoreAdapter providing create, get, +// and delete +// "loggerAdapter": a class like FileLoggerAdapter providing info, error, +// and query +// "jsonLogs": log as structured JSON objects +// "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us +// what database this Parse API connects to. +// "cloud": relative location to cloud code to require, or a function +// that is given an instance of Parse as a parameter. Use this instance of Parse +// to register your cloud code hooks and functions. +// "appId": the application id to host +// "masterKey": the master key for requests to this app +// "facebookAppIds": an array of valid Facebook Application IDs, required +// if using Facebook login +// "collectionPrefix": optional prefix for database collection names +// "fileKey": optional key from Parse dashboard for supporting older files +// hosted by Parse +// "clientKey": optional key from Parse dashboard +// "dotNetKey": optional key from Parse dashboard +// "restAPIKey": optional key from Parse dashboard +// "webhookKey": optional key from Parse dashboard +// "javascriptKey": optional key from Parse dashboard +// "push": optional key from configure push +// "sessionLength": optional length in seconds for how long Sessions should be valid for + +var ParseServer = function () { + function ParseServer(_ref) { + var _ref$appId = _ref.appId; + var appId = _ref$appId === undefined ? (0, _requiredParameter2.default)('You must provide an appId!') : _ref$appId; + var _ref$masterKey = _ref.masterKey; + var masterKey = _ref$masterKey === undefined ? (0, _requiredParameter2.default)('You must provide a masterKey!') : _ref$masterKey; + var appName = _ref.appName; + var _ref$analyticsAdapter = _ref.analyticsAdapter; + var analyticsAdapter = _ref$analyticsAdapter === undefined ? undefined : _ref$analyticsAdapter; + var filesAdapter = _ref.filesAdapter; + var push = _ref.push; + var loggerAdapter = _ref.loggerAdapter; + var jsonLogs = _ref.jsonLogs; + var logsFolder = _ref.logsFolder; + var databaseURI = _ref.databaseURI; + var databaseOptions = _ref.databaseOptions; + var databaseAdapter = _ref.databaseAdapter; + var cloud = _ref.cloud; + var _ref$collectionPrefix = _ref.collectionPrefix; + var collectionPrefix = _ref$collectionPrefix === undefined ? '' : _ref$collectionPrefix; + var clientKey = _ref.clientKey; + var javascriptKey = _ref.javascriptKey; + var dotNetKey = _ref.dotNetKey; + var restAPIKey = _ref.restAPIKey; + var webhookKey = _ref.webhookKey; + var _ref$fileKey = _ref.fileKey; + var fileKey = _ref$fileKey === undefined ? undefined : _ref$fileKey; + var _ref$facebookAppIds = _ref.facebookAppIds; + var facebookAppIds = _ref$facebookAppIds === undefined ? [] : _ref$facebookAppIds; + var _ref$enableAnonymousU = _ref.enableAnonymousUsers; + var enableAnonymousUsers = _ref$enableAnonymousU === undefined ? true : _ref$enableAnonymousU; + var _ref$allowClientClass = _ref.allowClientClassCreation; + var allowClientClassCreation = _ref$allowClientClass === undefined ? true : _ref$allowClientClass; + var _ref$oauth = _ref.oauth; + var oauth = _ref$oauth === undefined ? {} : _ref$oauth; + var _ref$serverURL = _ref.serverURL; + var serverURL = _ref$serverURL === undefined ? (0, _requiredParameter2.default)('You must provide a serverURL!') : _ref$serverURL; + var _ref$maxUploadSize = _ref.maxUploadSize; + var maxUploadSize = _ref$maxUploadSize === undefined ? '20mb' : _ref$maxUploadSize; + var _ref$verifyUserEmails = _ref.verifyUserEmails; + var verifyUserEmails = _ref$verifyUserEmails === undefined ? false : _ref$verifyUserEmails; + var _ref$preventLoginWith = _ref.preventLoginWithUnverifiedEmail; + var preventLoginWithUnverifiedEmail = _ref$preventLoginWith === undefined ? false : _ref$preventLoginWith; + var emailVerifyTokenValidityDuration = _ref.emailVerifyTokenValidityDuration; + var cacheAdapter = _ref.cacheAdapter; + var emailAdapter = _ref.emailAdapter; + var publicServerURL = _ref.publicServerURL; + var _ref$customPages = _ref.customPages; + var customPages = _ref$customPages === undefined ? { + invalidLink: undefined, + verifyEmailSuccess: undefined, + choosePassword: undefined, + passwordResetSuccess: undefined + } : _ref$customPages; + var _ref$liveQuery = _ref.liveQuery; + var liveQuery = _ref$liveQuery === undefined ? {} : _ref$liveQuery; + var _ref$sessionLength = _ref.sessionLength; + var sessionLength = _ref$sessionLength === undefined ? 31536000 : _ref$sessionLength; + var _ref$expireInactiveSe = _ref.expireInactiveSessions; + var expireInactiveSessions = _ref$expireInactiveSe === undefined ? true : _ref$expireInactiveSe; + var _ref$verbose = _ref.verbose; + var verbose = _ref$verbose === undefined ? false : _ref$verbose; + var _ref$revokeSessionOnP = _ref.revokeSessionOnPasswordReset; + var revokeSessionOnPasswordReset = _ref$revokeSessionOnP === undefined ? true : _ref$revokeSessionOnP; + var _ref$schemaCacheTTL = _ref.schemaCacheTTL; + var schemaCacheTTL = _ref$schemaCacheTTL === undefined ? 5 : _ref$schemaCacheTTL; + var _ref$__indexBuildComp = _ref.__indexBuildCompletionCallbackForTests; + + var __indexBuildCompletionCallbackForTests = _ref$__indexBuildComp === undefined ? function () {} : _ref$__indexBuildComp; + + _classCallCheck(this, ParseServer); + + // Initialize the node client SDK automatically + Parse.initialize(appId, javascriptKey || 'unused', masterKey); + Parse.serverURL = serverURL; + if ((databaseOptions || databaseURI || collectionPrefix !== '') && databaseAdapter) { + throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/connectionPrefix.'; + } else if (!databaseAdapter) { + databaseAdapter = new _MongoStorageAdapter2.default({ + uri: databaseURI, + collectionPrefix: collectionPrefix, + mongoOptions: databaseOptions + }); + } else { + databaseAdapter = (0, _AdapterLoader.loadAdapter)(databaseAdapter); + } + + if (!filesAdapter && !databaseURI) { + throw 'When using an explicit database adapter, you must also use and explicit filesAdapter.'; + } + + if (logsFolder) { + (0, _logger.configureLogger)({ logsFolder: logsFolder, jsonLogs: jsonLogs }); + } + + if (cloud) { + addParseCloud(); + if (typeof cloud === 'function') { + cloud(Parse); + } else if (typeof cloud === 'string') { + require(path.resolve(process.cwd(), cloud)); + } else { + throw "argument 'cloud' must either be a string or a function"; + } + } + + if (verbose || process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER) { + (0, _logger.configureLogger)({ level: 'silly', jsonLogs: jsonLogs }); + } + + var filesControllerAdapter = (0, _AdapterLoader.loadAdapter)(filesAdapter, function () { + return new _GridStoreAdapter.GridStoreAdapter(databaseURI); + }); + // Pass the push options too as it works with the default + var pushControllerAdapter = (0, _AdapterLoader.loadAdapter)(push && push.adapter, _parseServerPushAdapter2.default, push || {}); + var loggerControllerAdapter = (0, _AdapterLoader.loadAdapter)(loggerAdapter, _FileLoggerAdapter.FileLoggerAdapter); + var emailControllerAdapter = (0, _AdapterLoader.loadAdapter)(emailAdapter); + var cacheControllerAdapter = (0, _AdapterLoader.loadAdapter)(cacheAdapter, _InMemoryCacheAdapter.InMemoryCacheAdapter, { appId: appId }); + var analyticsControllerAdapter = (0, _AdapterLoader.loadAdapter)(analyticsAdapter, _AnalyticsAdapter.AnalyticsAdapter); + + // We pass the options and the base class for the adatper, + // Note that passing an instance would work too + var filesController = new _FilesController.FilesController(filesControllerAdapter, appId); + var pushController = new _PushController.PushController(pushControllerAdapter, appId, push); + var loggerController = new _LoggerController.LoggerController(loggerControllerAdapter, appId); + var userController = new _UserController.UserController(emailControllerAdapter, appId, { verifyUserEmails: verifyUserEmails }); + var liveQueryController = new _LiveQueryController.LiveQueryController(liveQuery); + var cacheController = new _CacheController.CacheController(cacheControllerAdapter, appId); + var databaseController = new _DatabaseController2.default(databaseAdapter, new _SchemaCache2.default(cacheController, schemaCacheTTL)); + var hooksController = new _HooksController.HooksController(appId, databaseController, webhookKey); + var analyticsController = new _AnalyticsController.AnalyticsController(analyticsControllerAdapter); + + // TODO: create indexes on first creation of a _User object. Otherwise it's impossible to + // have a Parse app without it having a _User collection. + var userClassPromise = databaseController.loadSchema().then(function (schema) { + return schema.enforceClassExists('_User'); + }); + + var usernameUniqueness = userClassPromise.then(function () { + return databaseController.adapter.ensureUniqueness('_User', requiredUserFields, ['username']); + }).catch(function (error) { + _logger.logger.warn('Unable to ensure uniqueness for usernames: ', error); + return Promise.reject(error); + }); + + var emailUniqueness = userClassPromise.then(function () { + return databaseController.adapter.ensureUniqueness('_User', requiredUserFields, ['email']); + }).catch(function (error) { + _logger.logger.warn('Unable to ensure uniqueness for user email addresses: ', error); + return Promise.reject(error); + }); + + _cache2.default.put(appId, { + appId: appId, + masterKey: masterKey, + serverURL: serverURL, + collectionPrefix: collectionPrefix, + clientKey: clientKey, + javascriptKey: javascriptKey, + dotNetKey: dotNetKey, + restAPIKey: restAPIKey, + webhookKey: webhookKey, + fileKey: fileKey, + facebookAppIds: facebookAppIds, + analyticsController: analyticsController, + cacheController: cacheController, + filesController: filesController, + pushController: pushController, + loggerController: loggerController, + hooksController: hooksController, + userController: userController, + verifyUserEmails: verifyUserEmails, + preventLoginWithUnverifiedEmail: preventLoginWithUnverifiedEmail, + emailVerifyTokenValidityDuration: emailVerifyTokenValidityDuration, + allowClientClassCreation: allowClientClassCreation, + authDataManager: authDataManager(oauth, enableAnonymousUsers), + appName: appName, + publicServerURL: publicServerURL, + customPages: customPages, + maxUploadSize: maxUploadSize, + liveQueryController: liveQueryController, + sessionLength: Number(sessionLength), + expireInactiveSessions: expireInactiveSessions, + jsonLogs: jsonLogs, + revokeSessionOnPasswordReset: revokeSessionOnPasswordReset, + databaseController: databaseController, + schemaCacheTTL: schemaCacheTTL + }); + + // To maintain compatibility. TODO: Remove in some version that breaks backwards compatability + if (process.env.FACEBOOK_APP_ID) { + _cache2.default.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID); + } + + _Config2.default.validate(_cache2.default.get(appId)); + this.config = _cache2.default.get(appId); + hooksController.load(); + + // Note: Tests will start to fail if any validation happens after this is called. + if (process.env.TESTING) { + __indexBuildCompletionCallbackForTests(Promise.all([usernameUniqueness, emailUniqueness])); + } + } + + _createClass(ParseServer, [{ + key: 'app', + get: function get() { + return ParseServer.app(this.config); + } + }], [{ + key: 'app', + value: function app(_ref2) { + var _ref2$maxUploadSize = _ref2.maxUploadSize; + var maxUploadSize = _ref2$maxUploadSize === undefined ? '20mb' : _ref2$maxUploadSize; + var appId = _ref2.appId; + + // This app serves the Parse API directly. + // It's the equivalent of https://api.parse.com/1 in the hosted Parse API. + var api = express(); + //api.use("/apps", express.static(__dirname + "/public")); + // File handling needs to be before default middlewares are applied + api.use('/', middlewares.allowCrossDomain, new _FilesRouter.FilesRouter().getExpressRouter({ + maxUploadSize: maxUploadSize + })); + + api.use('/', bodyParser.urlencoded({ extended: false }), new _PublicAPIRouter.PublicAPIRouter().expressApp()); + + // TODO: separate this from the regular ParseServer object + if (process.env.TESTING == 1) { + api.use('/', require('./testing-routes').router); + } + + api.use(bodyParser.json({ 'type': '*/*', limit: maxUploadSize })); + api.use(middlewares.allowCrossDomain); + api.use(middlewares.allowMethodOverride); + api.use(middlewares.handleParseHeaders); + + var routers = [new _ClassesRouter.ClassesRouter(), new _UsersRouter.UsersRouter(), new _SessionsRouter.SessionsRouter(), new _RolesRouter.RolesRouter(), new _AnalyticsRouter.AnalyticsRouter(), new _InstallationsRouter.InstallationsRouter(), new _FunctionsRouter.FunctionsRouter(), new _SchemasRouter.SchemasRouter(), new _PushRouter.PushRouter(), new _LogsRouter.LogsRouter(), new _IAPValidationRouter.IAPValidationRouter(), new _FeaturesRouter.FeaturesRouter(), new _GlobalConfigRouter.GlobalConfigRouter(), new _PurgeRouter.PurgeRouter()]; + + if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) { + routers.push(new _HooksRouter.HooksRouter()); + } + + var routes = routers.reduce(function (memo, router) { + return memo.concat(router.routes); + }, []); + + var appRouter = new _PromiseRouter2.default(routes, appId); + + batch.mountOnto(appRouter); + + api.use(appRouter.expressApp()); + + api.use(middlewares.handleParseErrors); + + //This causes tests to spew some useless warnings, so disable in test + if (!process.env.TESTING) { + process.on('uncaughtException', function (err) { + if (err.code === "EADDRINUSE") { + // user-friendly message for this common error + console.error('Unable to listen on port ' + err.port + '. The port is already in use.'); + process.exit(0); + } else { + throw err; + } + }); + } + return api; + } + }, { + key: 'createLiveQueryServer', + value: function createLiveQueryServer(httpServer, config) { + return new _ParseLiveQueryServer.ParseLiveQueryServer(httpServer, config); + } + }]); + + return ParseServer; +}(); + +function addParseCloud() { + var ParseCloud = require("./cloud-code/Parse.Cloud"); + Object.assign(Parse.Cloud, ParseCloud); + global.Parse = Parse; +} + +exports.default = ParseServer; \ No newline at end of file diff --git a/lib/PromiseRouter.js b/lib/PromiseRouter.js new file mode 100644 index 0000000000..49b8a26828 --- /dev/null +++ b/lib/PromiseRouter.js @@ -0,0 +1,397 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // A router that is based on promises rather than req/res/next. +// This is intended to replace the use of express.Router to handle +// subsections of the API surface. +// This will make it easier to have methods like 'batch' that +// themselves use our routing information, without disturbing express +// components that external developers may be modifying. + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +var _express = require('express'); + +var _express2 = _interopRequireDefault(_express); + +var _url = require('url'); + +var _url2 = _interopRequireDefault(_url); + +var _logger = require('./logger'); + +var _logger2 = _interopRequireDefault(_logger); + +var _util = require('util'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var PromiseRouter = function () { + // Each entry should be an object with: + // path: the path to route, in express format + // method: the HTTP method that this route handles. + // Must be one of: POST, GET, PUT, DELETE + // handler: a function that takes request, and returns a promise. + // Successful handlers should resolve to an object with fields: + // status: optional. the http status code. defaults to 200 + // response: a json object with the content of the response + // location: optional. a location header + function PromiseRouter() { + var routes = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + var appId = arguments[1]; + + _classCallCheck(this, PromiseRouter); + + this.routes = routes; + this.appId = appId; + this.mountRoutes(); + } + + // Leave the opportunity to + // subclasses to mount their routes by overriding + + + _createClass(PromiseRouter, [{ + key: 'mountRoutes', + value: function mountRoutes() {} + + // Merge the routes into this one + + }, { + key: 'merge', + value: function merge(router) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = router.routes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var route = _step.value; + + this.routes.push(route); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + }, { + key: 'route', + value: function route(method, path) { + for (var _len = arguments.length, handlers = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + handlers[_key - 2] = arguments[_key]; + } + + switch (method) { + case 'POST': + case 'GET': + case 'PUT': + case 'DELETE': + break; + default: + throw 'cannot route method: ' + method; + } + + var handler = handlers[0]; + + if (handlers.length > 1) { + var length = handlers.length; + handler = function handler(req) { + return handlers.reduce(function (promise, handler) { + return promise.then(function (result) { + return handler(req); + }); + }, Promise.resolve()); + }; + } + + this.routes.push({ + path: path, + method: method, + handler: handler + }); + } + }, { + key: 'match', + + + // Returns an object with: + // handler: the handler that should deal with this request + // params: any :-params that got parsed from the path + // Returns undefined if there is no match. + value: function match(method, path) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = this.routes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var route = _step2.value; + + if (route.method != method) { + continue; + } + // NOTE: we can only route the specific wildcards :className and + // :objectId, and in that order. + // This is pretty hacky but I don't want to rebuild the entire + // express route matcher. Maybe there's a way to reuse its logic. + var pattern = '^' + route.path + '$'; + + pattern = pattern.replace(':className', '(_?[A-Za-z][A-Za-z_0-9]*)'); + pattern = pattern.replace(':objectId', '([A-Za-z0-9]+)'); + var re = new RegExp(pattern); + var m = path.match(re); + if (!m) { + continue; + } + var params = {}; + if (m[1]) { + params.className = m[1]; + } + if (m[2]) { + params.objectId = m[2]; + } + + return { params: params, handler: route.handler }; + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + } + }, { + key: 'mountOnto', + + + // Mount the routes on this router onto an express app (or express router) + value: function mountOnto(expressApp) { + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = this.routes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var route = _step3.value; + + switch (route.method) { + case 'POST': + expressApp.post(route.path, makeExpressHandler(this.appId, route.handler)); + break; + case 'GET': + expressApp.get(route.path, makeExpressHandler(this.appId, route.handler)); + break; + case 'PUT': + expressApp.put(route.path, makeExpressHandler(this.appId, route.handler)); + break; + case 'DELETE': + expressApp.delete(route.path, makeExpressHandler(this.appId, route.handler)); + break; + default: + throw 'unexpected code branch'; + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + } + }, { + key: 'expressApp', + value: function expressApp() { + var expressApp = (0, _express2.default)(); + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = this.routes[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var route = _step4.value; + + switch (route.method) { + case 'POST': + expressApp.post(route.path, makeExpressHandler(this.appId, route.handler)); + break; + case 'GET': + expressApp.get(route.path, makeExpressHandler(this.appId, route.handler)); + break; + case 'PUT': + expressApp.put(route.path, makeExpressHandler(this.appId, route.handler)); + break; + case 'DELETE': + expressApp.delete(route.path, makeExpressHandler(this.appId, route.handler)); + break; + default: + throw 'unexpected code branch'; + } + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + return expressApp; + } + }]); + + return PromiseRouter; +}(); + +// A helper function to make an express handler out of a a promise +// handler. +// Express handlers should never throw; if a promise handler throws we +// just treat it like it resolved to an error. + + +exports.default = PromiseRouter; +function makeExpressHandler(appId, promiseHandler) { + var config = _cache2.default.get(appId); + return function (req, res, next) { + try { + (function () { + var url = maskSensitiveUrl(req); + var body = maskSensitiveBody(req); + var stringifiedBody = JSON.stringify(body, null, 2); + _logger2.default.verbose('REQUEST for [' + req.method + '] ' + url + ': ' + stringifiedBody, { + method: req.method, + url: url, + headers: req.headers, + body: body + }); + promiseHandler(req).then(function (result) { + if (!result.response && !result.location && !result.text) { + _logger2.default.error('the handler did not include a "response" or a "location" field'); + throw 'control should not get here'; + } + + var stringifiedResponse = JSON.stringify(result, null, 2); + _logger2.default.verbose('RESPONSE from [' + req.method + '] ' + url + ': ' + stringifiedResponse, { result: result }); + + var status = result.status || 200; + res.status(status); + + if (result.text) { + res.send(result.text); + return next(); + } + + if (result.location) { + res.set('Location', result.location); + // Override the default expressjs response + // as it double encodes %encoded chars in URL + if (!result.response) { + res.send('Found. Redirecting to ' + result.location); + return next(); + } + } + if (result.headers) { + Object.keys(result.headers).forEach(function (header) { + res.set(header, result.headers[header]); + }); + } + res.json(result.response); + next(); + }, function (e) { + _logger2.default.error('Error generating response. ' + (0, _util.inspect)(e), { error: e }); + next(e); + }); + })(); + } catch (e) { + _logger2.default.error('Error handling request: ' + (0, _util.inspect)(e), { error: e }); + next(e); + } + }; +} + +function maskSensitiveBody(req) { + var maskBody = Object.assign({}, req.body); + var shouldMaskBody = req.method === 'POST' && req.originalUrl.endsWith('/users') && !req.originalUrl.includes('classes') || req.method === 'PUT' && /users\/\w+$/.test(req.originalUrl) && !req.originalUrl.includes('classes') || req.originalUrl.includes('classes/_User'); + if (shouldMaskBody) { + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = Object.keys(maskBody)[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var key = _step5.value; + + if (key == 'password') { + maskBody[key] = '********'; + break; + } + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + } + return maskBody; +} + +function maskSensitiveUrl(req) { + var maskUrl = req.originalUrl.toString(); + var shouldMaskUrl = req.method === 'GET' && req.originalUrl.includes('/login') && !req.originalUrl.includes('classes'); + if (shouldMaskUrl) { + var password = _url2.default.parse(req.originalUrl, true).query.password; + if (password) { + maskUrl = maskUrl.replace('password=' + password, 'password=********'); + } + } + return maskUrl; +} \ No newline at end of file diff --git a/lib/RestQuery.js b/lib/RestQuery.js new file mode 100644 index 0000000000..634c4142c7 --- /dev/null +++ b/lib/RestQuery.js @@ -0,0 +1,892 @@ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _FilesController = require('./Controllers/FilesController'); + +var _FilesController2 = _interopRequireDefault(_FilesController); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// An object that encapsulates everything we need to run a 'find' +// operation, encoded in the REST API format. + +var SchemaController = require('./Controllers/SchemaController'); +var Parse = require('parse/node').Parse; + +// restOptions can include: +// skip +// limit +// order +// count +// include +// keys +// redirectClassNameForKey +function RestQuery(config, auth, className) { + var restWhere = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; + var restOptions = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; + var clientSDK = arguments[5]; + + + this.config = config; + this.auth = auth; + this.className = className; + this.restWhere = restWhere; + this.clientSDK = clientSDK; + this.response = null; + this.findOptions = {}; + if (!this.auth.isMaster) { + this.findOptions.acl = this.auth.user ? [this.auth.user.id] : null; + if (this.className == '_Session') { + if (!this.findOptions.acl) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'This session token is invalid.'); + } + this.restWhere = { + '$and': [this.restWhere, { + 'user': { + __type: 'Pointer', + className: '_User', + objectId: this.auth.user.id + } + }] + }; + } + } + + this.doCount = false; + + // The format for this.include is not the same as the format for the + // include option - it's the paths we should include, in order, + // stored as arrays, taking into account that we need to include foo + // before including foo.bar. Also it should dedupe. + // For example, passing an arg of include=foo.bar,foo.baz could lead to + // this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']] + this.include = []; + + for (var option in restOptions) { + switch (option) { + case 'keys': + this.keys = new Set(restOptions.keys.split(',')); + this.keys.add('objectId'); + this.keys.add('createdAt'); + this.keys.add('updatedAt'); + break; + case 'count': + this.doCount = true; + break; + case 'skip': + case 'limit': + this.findOptions[option] = restOptions[option]; + break; + case 'order': + var fields = restOptions.order.split(','); + var sortMap = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = fields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var field = _step.value; + + if (field[0] == '-') { + sortMap[field.slice(1)] = -1; + } else { + sortMap[field] = 1; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + this.findOptions.sort = sortMap; + break; + case 'include': + var paths = restOptions.include.split(','); + var pathSet = {}; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = paths[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var path = _step2.value; + + // Add all prefixes with a .-split to pathSet + var parts = path.split('.'); + for (var len = 1; len <= parts.length; len++) { + pathSet[parts.slice(0, len).join('.')] = true; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + this.include = Object.keys(pathSet).sort(function (a, b) { + return a.length - b.length; + }).map(function (s) { + return s.split('.'); + }); + break; + case 'redirectClassNameForKey': + this.redirectKey = restOptions.redirectClassNameForKey; + this.redirectClassName = null; + break; + default: + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad option: ' + option); + } + } +} + +// A convenient method to perform all the steps of processing a query +// in order. +// Returns a promise for the response - an object with optional keys +// 'results' and 'count'. +// TODO: consolidate the replaceX functions +RestQuery.prototype.execute = function () { + var _this = this; + + return Promise.resolve().then(function () { + return _this.buildRestWhere(); + }).then(function () { + return _this.runFind(); + }).then(function () { + return _this.runCount(); + }).then(function () { + return _this.handleInclude(); + }).then(function () { + return _this.response; + }); +}; + +RestQuery.prototype.buildRestWhere = function () { + var _this2 = this; + + return Promise.resolve().then(function () { + return _this2.getUserAndRoleACL(); + }).then(function () { + return _this2.redirectClassNameForKey(); + }).then(function () { + return _this2.validateClientClassCreation(); + }).then(function () { + return _this2.replaceSelect(); + }).then(function () { + return _this2.replaceDontSelect(); + }).then(function () { + return _this2.replaceInQuery(); + }).then(function () { + return _this2.replaceNotInQuery(); + }); +}; + +// Uses the Auth object to get the list of roles, adds the user id +RestQuery.prototype.getUserAndRoleACL = function () { + var _this3 = this; + + if (this.auth.isMaster || !this.auth.user) { + return Promise.resolve(); + } + return this.auth.getUserRoles().then(function (roles) { + roles.push(_this3.auth.user.id); + _this3.findOptions.acl = roles; + return Promise.resolve(); + }); +}; + +// Changes the className if redirectClassNameForKey is set. +// Returns a promise. +RestQuery.prototype.redirectClassNameForKey = function () { + var _this4 = this; + + if (!this.redirectKey) { + return Promise.resolve(); + } + + // We need to change the class name based on the schema + return this.config.database.redirectClassNameForKey(this.className, this.redirectKey).then(function (newClassName) { + _this4.className = newClassName; + _this4.redirectClassName = newClassName; + }); +}; + +// Validates this operation against the allowClientClassCreation config. +RestQuery.prototype.validateClientClassCreation = function () { + var _this5 = this; + + if (this.config.allowClientClassCreation === false && !this.auth.isMaster && SchemaController.systemClasses.indexOf(this.className) === -1) { + return this.config.database.loadSchema().then(function (schemaController) { + return schemaController.hasClass(_this5.className); + }).then(function (hasClass) { + if (hasClass !== true) { + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'This user is not allowed to access ' + 'non-existent class: ' + _this5.className); + } + }); + } else { + return Promise.resolve(); + } +}; + +function transformInQuery(inQueryObject, className, results) { + var values = []; + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = results[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var result = _step3.value; + + values.push({ + __type: 'Pointer', + className: className, + objectId: result.objectId + }); + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + delete inQueryObject['$inQuery']; + if (Array.isArray(inQueryObject['$in'])) { + inQueryObject['$in'] = inQueryObject['$in'].concat(values); + } else { + inQueryObject['$in'] = values; + } +} + +// Replaces a $inQuery clause by running the subquery, if there is an +// $inQuery clause. +// The $inQuery clause turns into an $in with values that are just +// pointers to the objects returned in the subquery. +RestQuery.prototype.replaceInQuery = function () { + var _this6 = this; + + var inQueryObject = findObjectWithKey(this.restWhere, '$inQuery'); + if (!inQueryObject) { + return; + } + + // The inQuery value must have precisely two keys - where and className + var inQueryValue = inQueryObject['$inQuery']; + if (!inQueryValue.where || !inQueryValue.className) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $inQuery'); + } + + var additionalOptions = { + redirectClassNameForKey: inQueryValue.redirectClassNameForKey + }; + + var subquery = new RestQuery(this.config, this.auth, inQueryValue.className, inQueryValue.where, additionalOptions); + return subquery.execute().then(function (response) { + transformInQuery(inQueryObject, subquery.className, response.results); + // Recurse to repeat + return _this6.replaceInQuery(); + }); +}; + +function transformNotInQuery(notInQueryObject, className, results) { + var values = []; + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = results[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var result = _step4.value; + + values.push({ + __type: 'Pointer', + className: className, + objectId: result.objectId + }); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + delete notInQueryObject['$notInQuery']; + if (Array.isArray(notInQueryObject['$nin'])) { + notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values); + } else { + notInQueryObject['$nin'] = values; + } +} + +// Replaces a $notInQuery clause by running the subquery, if there is an +// $notInQuery clause. +// The $notInQuery clause turns into a $nin with values that are just +// pointers to the objects returned in the subquery. +RestQuery.prototype.replaceNotInQuery = function () { + var _this7 = this; + + var notInQueryObject = findObjectWithKey(this.restWhere, '$notInQuery'); + if (!notInQueryObject) { + return; + } + + // The notInQuery value must have precisely two keys - where and className + var notInQueryValue = notInQueryObject['$notInQuery']; + if (!notInQueryValue.where || !notInQueryValue.className) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $notInQuery'); + } + + var additionalOptions = { + redirectClassNameForKey: notInQueryValue.redirectClassNameForKey + }; + + var subquery = new RestQuery(this.config, this.auth, notInQueryValue.className, notInQueryValue.where, additionalOptions); + return subquery.execute().then(function (response) { + transformNotInQuery(notInQueryObject, subquery.className, response.results); + // Recurse to repeat + return _this7.replaceNotInQuery(); + }); +}; + +var transformSelect = function transformSelect(selectObject, key, objects) { + var values = []; + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = objects[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var result = _step5.value; + + values.push(result[key]); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + + delete selectObject['$select']; + if (Array.isArray(selectObject['$in'])) { + selectObject['$in'] = selectObject['$in'].concat(values); + } else { + selectObject['$in'] = values; + } +}; + +// Replaces a $select clause by running the subquery, if there is a +// $select clause. +// The $select clause turns into an $in with values selected out of +// the subquery. +// Returns a possible-promise. +RestQuery.prototype.replaceSelect = function () { + var _this8 = this; + + var selectObject = findObjectWithKey(this.restWhere, '$select'); + if (!selectObject) { + return; + } + + // The select value must have precisely two keys - query and key + var selectValue = selectObject['$select']; + // iOS SDK don't send where if not set, let it pass + if (!selectValue.query || !selectValue.key || _typeof(selectValue.query) !== 'object' || !selectValue.query.className || Object.keys(selectValue).length !== 2) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $select'); + } + + var additionalOptions = { + redirectClassNameForKey: selectValue.query.redirectClassNameForKey + }; + + var subquery = new RestQuery(this.config, this.auth, selectValue.query.className, selectValue.query.where, additionalOptions); + return subquery.execute().then(function (response) { + transformSelect(selectObject, selectValue.key, response.results); + // Keep replacing $select clauses + return _this8.replaceSelect(); + }); +}; + +var transformDontSelect = function transformDontSelect(dontSelectObject, key, objects) { + var values = []; + var _iteratorNormalCompletion6 = true; + var _didIteratorError6 = false; + var _iteratorError6 = undefined; + + try { + for (var _iterator6 = objects[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { + var result = _step6.value; + + values.push(result[key]); + } + } catch (err) { + _didIteratorError6 = true; + _iteratorError6 = err; + } finally { + try { + if (!_iteratorNormalCompletion6 && _iterator6.return) { + _iterator6.return(); + } + } finally { + if (_didIteratorError6) { + throw _iteratorError6; + } + } + } + + delete dontSelectObject['$dontSelect']; + if (Array.isArray(dontSelectObject['$nin'])) { + dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values); + } else { + dontSelectObject['$nin'] = values; + } +}; + +// Replaces a $dontSelect clause by running the subquery, if there is a +// $dontSelect clause. +// The $dontSelect clause turns into an $nin with values selected out of +// the subquery. +// Returns a possible-promise. +RestQuery.prototype.replaceDontSelect = function () { + var _this9 = this; + + var dontSelectObject = findObjectWithKey(this.restWhere, '$dontSelect'); + if (!dontSelectObject) { + return; + } + + // The dontSelect value must have precisely two keys - query and key + var dontSelectValue = dontSelectObject['$dontSelect']; + if (!dontSelectValue.query || !dontSelectValue.key || _typeof(dontSelectValue.query) !== 'object' || !dontSelectValue.query.className || Object.keys(dontSelectValue).length !== 2) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $dontSelect'); + } + var additionalOptions = { + redirectClassNameForKey: dontSelectValue.query.redirectClassNameForKey + }; + + var subquery = new RestQuery(this.config, this.auth, dontSelectValue.query.className, dontSelectValue.query.where, additionalOptions); + return subquery.execute().then(function (response) { + transformDontSelect(dontSelectObject, dontSelectValue.key, response.results); + // Keep replacing $dontSelect clauses + return _this9.replaceDontSelect(); + }); +}; + +// Returns a promise for whether it was successful. +// Populates this.response with an object that only has 'results'. +RestQuery.prototype.runFind = function () { + var _this10 = this; + + if (this.findOptions.limit === 0) { + this.response = { results: [] }; + return Promise.resolve(); + } + return this.config.database.find(this.className, this.restWhere, this.findOptions).then(function (results) { + if (_this10.className === '_User') { + var _iteratorNormalCompletion7 = true; + var _didIteratorError7 = false; + var _iteratorError7 = undefined; + + try { + for (var _iterator7 = results[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { + var result = _step7.value; + + delete result.password; + + if (result.authData) { + Object.keys(result.authData).forEach(function (provider) { + if (result.authData[provider] === null) { + delete result.authData[provider]; + } + }); + if (Object.keys(result.authData).length == 0) { + delete result.authData; + } + } + } + } catch (err) { + _didIteratorError7 = true; + _iteratorError7 = err; + } finally { + try { + if (!_iteratorNormalCompletion7 && _iterator7.return) { + _iterator7.return(); + } + } finally { + if (_didIteratorError7) { + throw _iteratorError7; + } + } + } + } + + _this10.config.filesController.expandFilesInObject(_this10.config, results); + + if (_this10.keys) { + var keySet = _this10.keys; + results = results.map(function (object) { + var newObject = {}; + for (var key in object) { + if (keySet.has(key)) { + newObject[key] = object[key]; + } + } + return newObject; + }); + } + + if (_this10.redirectClassName) { + var _iteratorNormalCompletion8 = true; + var _didIteratorError8 = false; + var _iteratorError8 = undefined; + + try { + for (var _iterator8 = results[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { + var r = _step8.value; + + r.className = _this10.redirectClassName; + } + } catch (err) { + _didIteratorError8 = true; + _iteratorError8 = err; + } finally { + try { + if (!_iteratorNormalCompletion8 && _iterator8.return) { + _iterator8.return(); + } + } finally { + if (_didIteratorError8) { + throw _iteratorError8; + } + } + } + } + _this10.response = { results: results }; + }); +}; + +// Returns a promise for whether it was successful. +// Populates this.response.count with the count +RestQuery.prototype.runCount = function () { + var _this11 = this; + + if (!this.doCount) { + return; + } + this.findOptions.count = true; + delete this.findOptions.skip; + delete this.findOptions.limit; + return this.config.database.find(this.className, this.restWhere, this.findOptions).then(function (c) { + _this11.response.count = c; + }); +}; + +// Augments this.response with data at the paths provided in this.include. +RestQuery.prototype.handleInclude = function () { + var _this12 = this; + + if (this.include.length == 0) { + return; + } + + var pathResponse = includePath(this.config, this.auth, this.response, this.include[0]); + if (pathResponse.then) { + return pathResponse.then(function (newResponse) { + _this12.response = newResponse; + _this12.include = _this12.include.slice(1); + return _this12.handleInclude(); + }); + } else if (this.include.length > 0) { + this.include = this.include.slice(1); + return this.handleInclude(); + } + + return pathResponse; +}; + +// Adds included values to the response. +// Path is a list of field names. +// Returns a promise for an augmented response. +function includePath(config, auth, response, path) { + var pointers = findPointers(response.results, path); + if (pointers.length == 0) { + return response; + } + var pointersHash = {}; + var objectIds = {}; + var _iteratorNormalCompletion9 = true; + var _didIteratorError9 = false; + var _iteratorError9 = undefined; + + try { + for (var _iterator9 = pointers[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { + var pointer = _step9.value; + + var className = pointer.className; + // only include the good pointers + if (className) { + pointersHash[className] = pointersHash[className] || []; + pointersHash[className].push(pointer.objectId); + } + } + } catch (err) { + _didIteratorError9 = true; + _iteratorError9 = err; + } finally { + try { + if (!_iteratorNormalCompletion9 && _iterator9.return) { + _iterator9.return(); + } + } finally { + if (_didIteratorError9) { + throw _iteratorError9; + } + } + } + + var queryPromises = Object.keys(pointersHash).map(function (className) { + var where = { 'objectId': { '$in': pointersHash[className] } }; + var query = new RestQuery(config, auth, className, where); + return query.execute().then(function (results) { + results.className = className; + return Promise.resolve(results); + }); + }); + + // Get the objects for all these object ids + return Promise.all(queryPromises).then(function (responses) { + var replace = responses.reduce(function (replace, includeResponse) { + var _iteratorNormalCompletion10 = true; + var _didIteratorError10 = false; + var _iteratorError10 = undefined; + + try { + for (var _iterator10 = includeResponse.results[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { + var obj = _step10.value; + + obj.__type = 'Object'; + obj.className = includeResponse.className; + + if (obj.className == "_User" && !auth.isMaster) { + delete obj.sessionToken; + delete obj.authData; + } + replace[obj.objectId] = obj; + } + } catch (err) { + _didIteratorError10 = true; + _iteratorError10 = err; + } finally { + try { + if (!_iteratorNormalCompletion10 && _iterator10.return) { + _iterator10.return(); + } + } finally { + if (_didIteratorError10) { + throw _iteratorError10; + } + } + } + + return replace; + }, {}); + + var resp = { + results: replacePointers(response.results, path, replace) + }; + if (response.count) { + resp.count = response.count; + } + return resp; + }); +} + +// Object may be a list of REST-format object to find pointers in, or +// it may be a single object. +// If the path yields things that aren't pointers, this throws an error. +// Path is a list of fields to search into. +// Returns a list of pointers in REST format. +function findPointers(object, path) { + if (object instanceof Array) { + var answer = []; + var _iteratorNormalCompletion11 = true; + var _didIteratorError11 = false; + var _iteratorError11 = undefined; + + try { + for (var _iterator11 = object[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { + var x = _step11.value; + + answer = answer.concat(findPointers(x, path)); + } + } catch (err) { + _didIteratorError11 = true; + _iteratorError11 = err; + } finally { + try { + if (!_iteratorNormalCompletion11 && _iterator11.return) { + _iterator11.return(); + } + } finally { + if (_didIteratorError11) { + throw _iteratorError11; + } + } + } + + return answer; + } + + if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object') { + return []; + } + + if (path.length == 0) { + if (object.__type == 'Pointer') { + return [object]; + } + return []; + } + + var subobject = object[path[0]]; + if (!subobject) { + return []; + } + return findPointers(subobject, path.slice(1)); +} + +// Object may be a list of REST-format objects to replace pointers +// in, or it may be a single object. +// Path is a list of fields to search into. +// replace is a map from object id -> object. +// Returns something analogous to object, but with the appropriate +// pointers inflated. +function replacePointers(object, path, replace) { + if (object instanceof Array) { + return object.map(function (obj) { + return replacePointers(obj, path, replace); + }).filter(function (obj) { + return obj != null && obj != undefined; + }); + } + + if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object') { + return object; + } + + if (path.length === 0) { + if (object.__type === 'Pointer') { + return replace[object.objectId]; + } + return object; + } + + var subobject = object[path[0]]; + if (!subobject) { + return object; + } + var newsub = replacePointers(subobject, path.slice(1), replace); + var answer = {}; + for (var key in object) { + if (key == path[0]) { + answer[key] = newsub; + } else { + answer[key] = object[key]; + } + } + return answer; +} + +// Finds a subobject that has the given key, if there is one. +// Returns undefined otherwise. +function findObjectWithKey(root, key) { + if ((typeof root === 'undefined' ? 'undefined' : _typeof(root)) !== 'object') { + return; + } + if (root instanceof Array) { + var _iteratorNormalCompletion12 = true; + var _didIteratorError12 = false; + var _iteratorError12 = undefined; + + try { + for (var _iterator12 = root[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { + var item = _step12.value; + + var answer = findObjectWithKey(item, key); + if (answer) { + return answer; + } + } + } catch (err) { + _didIteratorError12 = true; + _iteratorError12 = err; + } finally { + try { + if (!_iteratorNormalCompletion12 && _iterator12.return) { + _iterator12.return(); + } + } finally { + if (_didIteratorError12) { + throw _iteratorError12; + } + } + } + } + if (root && root[key]) { + return root; + } + for (var subkey in root) { + var answer = findObjectWithKey(root[subkey], key); + if (answer) { + return answer; + } + } +} + +module.exports = RestQuery; \ No newline at end of file diff --git a/lib/RestWrite.js b/lib/RestWrite.js new file mode 100644 index 0000000000..034ba5c9a0 --- /dev/null +++ b/lib/RestWrite.js @@ -0,0 +1,944 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _RestQuery = require('./RestQuery'); + +var _RestQuery2 = _interopRequireDefault(_RestQuery); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// A RestWrite encapsulates everything we need to run an operation +// that writes to the database. +// This could be either a "create" or an "update". + +var SchemaController = require('./Controllers/SchemaController'); +var deepcopy = require('deepcopy'); + +var Auth = require('./Auth'); +var Config = require('./Config'); +var cryptoUtils = require('./cryptoUtils'); +var passwordCrypto = require('./password'); +var Parse = require('parse/node'); +var triggers = require('./triggers'); +var ClientSDK = require('./ClientSDK'); + + +// query and data are both provided in REST API format. So data +// types are encoded by plain old objects. +// If query is null, this is a "create" and the data in data should be +// created. +// Otherwise this is an "update" - the object matching the query +// should get updated with data. +// RestWrite will handle objectId, createdAt, and updatedAt for +// everything. It also knows to use triggers and special modifications +// for the _User class. +function RestWrite(config, auth, className, query, data, originalData, clientSDK) { + this.config = config; + this.auth = auth; + this.className = className; + this.clientSDK = clientSDK; + this.storage = {}; + this.runOptions = {}; + if (!query && data.objectId) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.'); + } + + // When the operation is complete, this.response may have several + // fields. + // response: the actual data to be returned + // status: the http status code. if not present, treated like a 200 + // location: the location header. if not present, no location header + this.response = null; + + // Processing this operation may mutate our data, so we operate on a + // copy + this.query = deepcopy(query); + this.data = deepcopy(data); + // We never change originalData, so we do not need a deep copy + this.originalData = originalData; + + // The timestamp we'll use for this whole operation + this.updatedAt = Parse._encode(new Date()).iso; +} + +// A convenient method to perform all the steps of processing the +// write, in order. +// Returns a promise for a {response, status, location} object. +// status and location are optional. +RestWrite.prototype.execute = function () { + var _this = this; + + return Promise.resolve().then(function () { + return _this.getUserAndRoleACL(); + }).then(function () { + return _this.validateClientClassCreation(); + }).then(function () { + return _this.validateSchema(); + }).then(function () { + return _this.handleInstallation(); + }).then(function () { + return _this.handleSession(); + }).then(function () { + return _this.validateAuthData(); + }).then(function () { + return _this.runBeforeTrigger(); + }).then(function () { + return _this.setRequiredFieldsIfNeeded(); + }).then(function () { + return _this.transformUser(); + }).then(function () { + return _this.expandFilesForExistingObjects(); + }).then(function () { + return _this.runDatabaseOperation(); + }).then(function () { + return _this.createSessionTokenIfNeeded(); + }).then(function () { + return _this.handleFollowup(); + }).then(function () { + return _this.runAfterTrigger(); + }).then(function () { + return _this.cleanUserAuthData(); + }).then(function () { + return _this.response; + }); +}; + +// Uses the Auth object to get the list of roles, adds the user id +RestWrite.prototype.getUserAndRoleACL = function () { + var _this2 = this; + + if (this.auth.isMaster) { + return Promise.resolve(); + } + + this.runOptions.acl = ['*']; + + if (this.auth.user) { + return this.auth.getUserRoles().then(function (roles) { + roles.push(_this2.auth.user.id); + _this2.runOptions.acl = _this2.runOptions.acl.concat(roles); + return; + }); + } else { + return Promise.resolve(); + } +}; + +// Validates this operation against the allowClientClassCreation config. +RestWrite.prototype.validateClientClassCreation = function () { + var _this3 = this; + + if (this.config.allowClientClassCreation === false && !this.auth.isMaster && SchemaController.systemClasses.indexOf(this.className) === -1) { + return this.config.database.loadSchema().then(function (schemaController) { + return schemaController.hasClass(_this3.className); + }).then(function (hasClass) { + if (hasClass !== true) { + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'This user is not allowed to access ' + 'non-existent class: ' + _this3.className); + } + }); + } else { + return Promise.resolve(); + } +}; + +// Validates this operation against the schema. +RestWrite.prototype.validateSchema = function () { + return this.config.database.validateObject(this.className, this.data, this.query, this.runOptions); +}; + +// Runs any beforeSave triggers against this operation. +// Any change leads to our data being mutated. +RestWrite.prototype.runBeforeTrigger = function () { + var _this4 = this; + + if (this.response) { + return; + } + + // Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class. + if (!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)) { + return Promise.resolve(); + } + + // Cloud code gets a bit of extra data for its objects + var extraData = { className: this.className }; + if (this.query && this.query.objectId) { + extraData.objectId = this.query.objectId; + } + + var originalObject = null; + var updatedObject = triggers.inflate(extraData, this.originalData); + if (this.query && this.query.objectId) { + // This is an update for existing object. + originalObject = triggers.inflate(extraData, this.originalData); + } + updatedObject.set(this.sanitizedData()); + + return Promise.resolve().then(function () { + return triggers.maybeRunTrigger(triggers.Types.beforeSave, _this4.auth, updatedObject, originalObject, _this4.config); + }).then(function (response) { + if (response && response.object) { + if (!_lodash2.default.isEqual(_this4.data, response.object)) { + _this4.storage.changedByTrigger = true; + } + _this4.data = response.object; + // We should delete the objectId for an update write + if (_this4.query && _this4.query.objectId) { + delete _this4.data.objectId; + } + return _this4.validateSchema(); + } + }); +}; + +RestWrite.prototype.setRequiredFieldsIfNeeded = function () { + if (this.data) { + // Add default fields + this.data.updatedAt = this.updatedAt; + if (!this.query) { + this.data.createdAt = this.updatedAt; + + // Only assign new objectId if we are creating new object + if (!this.data.objectId) { + this.data.objectId = cryptoUtils.newObjectId(); + } + } + } + return Promise.resolve(); +}; + +// Transforms auth data for a user object. +// Does nothing if this isn't a user object. +// Returns a promise for when we're done if it can't finish this tick. +RestWrite.prototype.validateAuthData = function () { + if (this.className !== '_User') { + return; + } + + if (!this.query && !this.data.authData) { + if (typeof this.data.username !== 'string') { + throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'bad or missing username'); + } + if (typeof this.data.password !== 'string') { + throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required'); + } + } + + if (!this.data.authData || !Object.keys(this.data.authData).length) { + return; + } + + var authData = this.data.authData; + var providers = Object.keys(authData); + if (providers.length > 0) { + var canHandleAuthData = providers.reduce(function (canHandle, provider) { + var providerAuthData = authData[provider]; + var hasToken = providerAuthData && providerAuthData.id; + return canHandle && (hasToken || providerAuthData == null); + }, true); + if (canHandleAuthData) { + return this.handleAuthData(authData); + } + } + throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.'); +}; + +RestWrite.prototype.handleAuthDataValidation = function (authData) { + var _this5 = this; + + var validations = Object.keys(authData).map(function (provider) { + if (authData[provider] === null) { + return Promise.resolve(); + } + var validateAuthData = _this5.config.authDataManager.getValidatorForProvider(provider); + if (!validateAuthData) { + throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.'); + }; + return validateAuthData(authData[provider]); + }); + return Promise.all(validations); +}; + +RestWrite.prototype.findUsersWithAuthData = function (authData) { + var providers = Object.keys(authData); + var query = providers.reduce(function (memo, provider) { + if (!authData[provider]) { + return memo; + } + var queryKey = 'authData.' + provider + '.id'; + var query = {}; + query[queryKey] = authData[provider].id; + memo.push(query); + return memo; + }, []).filter(function (q) { + return (typeof q === 'undefined' ? 'undefined' : _typeof(q)) !== undefined; + }); + + var findPromise = Promise.resolve([]); + if (query.length > 0) { + findPromise = this.config.database.find(this.className, { '$or': query }, {}); + } + + return findPromise; +}; + +RestWrite.prototype.handleAuthData = function (authData) { + var _this6 = this; + + var results = void 0; + return this.handleAuthDataValidation(authData).then(function () { + return _this6.findUsersWithAuthData(authData); + }).then(function (r) { + results = r; + if (results.length > 1) { + // More than 1 user with the passed id's + throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + } + + _this6.storage['authProvider'] = Object.keys(authData).join(','); + + if (results.length > 0) { + if (!_this6.query) { + var _ret = function () { + // Login with auth data + delete results[0].password; + var userResult = results[0]; + + // need to set the objectId first otherwise location has trailing undefined + _this6.data.objectId = userResult.objectId; + + // Determine if authData was updated + var mutatedAuthData = {}; + Object.keys(authData).forEach(function (provider) { + var providerData = authData[provider]; + var userAuthData = userResult.authData[provider]; + if (!_lodash2.default.isEqual(providerData, userAuthData)) { + mutatedAuthData[provider] = providerData; + } + }); + + _this6.response = { + response: userResult, + location: _this6.location() + }; + + // We have authData that is updated on login + // that can happen when token are refreshed, + // We should update the token and let the user in + if (Object.keys(mutatedAuthData).length > 0) { + // Assign the new authData in the response + Object.keys(mutatedAuthData).forEach(function (provider) { + _this6.response.response.authData[provider] = mutatedAuthData[provider]; + }); + // Run the DB update directly, as 'master' + // Just update the authData part + return { + v: _this6.config.database.update(_this6.className, { objectId: _this6.data.objectId }, { authData: mutatedAuthData }, {}) + }; + } + return { + v: void 0 + }; + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } else if (_this6.query && _this6.query.objectId) { + // Trying to update auth data but users + // are different + if (results[0].objectId !== _this6.query.objectId) { + throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + } + } + } + return; + }); +}; + +// The non-third-party parts of User transformation +RestWrite.prototype.transformUser = function () { + var _this7 = this; + + if (this.className !== '_User') { + return; + } + + var promise = Promise.resolve(); + + if (this.query) { + // If we're updating a _User object, we need to clear out the cache for that user. Find all their + // session tokens, and remove them from the cache. + promise = new _RestQuery2.default(this.config, Auth.master(this.config), '_Session', { user: { + __type: "Pointer", + className: "_User", + objectId: this.objectId() + } }).execute().then(function (results) { + results.results.forEach(function (session) { + return _this7.config.cacheController.user.del(session.sessionToken); + }); + }); + } + + return promise.then(function () { + // Transform the password + if (!_this7.data.password) { + return; + } + if (_this7.query && !_this7.auth.isMaster) { + _this7.storage['clearSessions'] = true; + _this7.storage['generateNewSession'] = true; + } + return passwordCrypto.hash(_this7.data.password).then(function (hashedPassword) { + _this7.data._hashed_password = hashedPassword; + delete _this7.data.password; + }); + }).then(function () { + // Check for username uniqueness + if (!_this7.data.username) { + if (!_this7.query) { + _this7.data.username = cryptoUtils.randomString(25); + _this7.responseShouldHaveUsername = true; + } + return; + } + // We need to a find to check for duplicate username in case they are missing the unique index on usernames + // TODO: Check if there is a unique index, and if so, skip this query. + return _this7.config.database.find(_this7.className, { username: _this7.data.username, objectId: { '$ne': _this7.objectId() } }, { limit: 1 }).then(function (results) { + if (results.length > 0) { + throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.'); + } + return; + }); + }).then(function () { + if (!_this7.data.email || _this7.data.email.__op === 'Delete') { + return; + } + // Validate basic email address format + if (!_this7.data.email.match(/^.+@.+$/)) { + throw new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.'); + } + // Same problem for email as above for username + return _this7.config.database.find(_this7.className, { email: _this7.data.email, objectId: { '$ne': _this7.objectId() } }, { limit: 1 }).then(function (results) { + if (results.length > 0) { + throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.'); + } + // We updated the email, send a new validation + _this7.storage['sendVerificationEmail'] = true; + _this7.config.userController.setEmailVerifyToken(_this7.data); + }); + }); +}; + +RestWrite.prototype.createSessionTokenIfNeeded = function () { + if (this.className !== '_User') { + return; + } + if (this.query) { + return; + } + return this.createSessionToken(); +}; + +RestWrite.prototype.createSessionToken = function () { + var token = 'r:' + cryptoUtils.newToken(); + + var expiresAt = this.config.generateSessionExpiresAt(); + var sessionData = { + sessionToken: token, + user: { + __type: 'Pointer', + className: '_User', + objectId: this.objectId() + }, + createdWith: { + 'action': 'signup', + 'authProvider': this.storage['authProvider'] || 'password' + }, + restricted: false, + installationId: this.auth.installationId, + expiresAt: Parse._encode(expiresAt) + }; + if (this.response && this.response.response) { + this.response.response.sessionToken = token; + } + var create = new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData); + return create.execute(); +}; + +// Handles any followup logic +RestWrite.prototype.handleFollowup = function () { + if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) { + var sessionQuery = { + user: { + __type: 'Pointer', + className: '_User', + objectId: this.objectId() + } + }; + delete this.storage['clearSessions']; + return this.config.database.destroy('_Session', sessionQuery).then(this.handleFollowup.bind(this)); + } + + if (this.storage && this.storage['generateNewSession']) { + delete this.storage['generateNewSession']; + return this.createSessionToken().then(this.handleFollowup.bind(this)); + } + + if (this.storage && this.storage['sendVerificationEmail']) { + delete this.storage['sendVerificationEmail']; + // Fire and forget! + this.config.userController.sendVerificationEmail(this.data); + return this.handleFollowup.bind(this); + } +}; + +// Handles the _Session class specialness. +// Does nothing if this isn't an installation object. +RestWrite.prototype.handleSession = function () { + var _this8 = this; + + if (this.response || this.className !== '_Session') { + return; + } + + if (!this.auth.user && !this.auth.isMaster) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); + } + + // TODO: Verify proper error to throw + if (this.data.ACL) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot set ' + 'ACL on a Session.'); + } + + if (!this.query && !this.auth.isMaster) { + var token = 'r:' + cryptoUtils.newToken(); + var expiresAt = this.config.generateSessionExpiresAt(); + var sessionData = { + sessionToken: token, + user: { + __type: 'Pointer', + className: '_User', + objectId: this.auth.user.id + }, + createdWith: { + 'action': 'create' + }, + restricted: true, + expiresAt: Parse._encode(expiresAt) + }; + for (var key in this.data) { + if (key == 'objectId') { + continue; + } + sessionData[key] = this.data[key]; + } + var create = new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData); + return create.execute().then(function (results) { + if (!results.response) { + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Error creating session.'); + } + sessionData['objectId'] = results.response['objectId']; + _this8.response = { + status: 201, + location: results.location, + response: sessionData + }; + }); + } +}; + +// Handles the _Installation class specialness. +// Does nothing if this isn't an installation object. +// If an installation is found, this can mutate this.query and turn a create +// into an update. +// Returns a promise for when we're done if it can't finish this tick. +RestWrite.prototype.handleInstallation = function () { + var _this9 = this; + + if (this.response || this.className !== '_Installation') { + return; + } + + if (!this.query && !this.data.deviceToken && !this.data.installationId) { + throw new Parse.Error(135, 'at least one ID field (deviceToken, installationId) ' + 'must be specified in this operation'); + } + + if (!this.query && !this.data.deviceType) { + throw new Parse.Error(135, 'deviceType must be specified in this operation'); + } + + // If the device token is 64 characters long, we assume it is for iOS + // and lowercase it. + if (this.data.deviceToken && this.data.deviceToken.length == 64) { + this.data.deviceToken = this.data.deviceToken.toLowerCase(); + } + + // TODO: We may need installationId from headers, plumb through Auth? + // per installation_handler.go + + // We lowercase the installationId if present + if (this.data.installationId) { + this.data.installationId = this.data.installationId.toLowerCase(); + } + + var promise = Promise.resolve(); + + var idMatch; // Will be a match on either objectId or installationId + var objectIdMatch; + var installationIdMatch; + var deviceTokenMatches = []; + + // Instead of issuing 3 reads, let's do it with one OR. + var orQueries = []; + if (this.query && this.query.objectId) { + orQueries.push({ + objectId: this.query.objectId + }); + } + if (this.data.installationId) { + orQueries.push({ + 'installationId': this.data.installationId + }); + } + if (this.data.deviceToken) { + orQueries.push({ 'deviceToken': this.data.deviceToken }); + } + + if (orQueries.length == 0) { + return; + } + + promise = promise.then(function () { + return _this9.config.database.find('_Installation', { + '$or': orQueries + }, {}); + }).then(function (results) { + results.forEach(function (result) { + if (_this9.query && _this9.query.objectId && result.objectId == _this9.query.objectId) { + objectIdMatch = result; + } + if (result.installationId == _this9.data.installationId) { + installationIdMatch = result; + } + if (result.deviceToken == _this9.data.deviceToken) { + deviceTokenMatches.push(result); + } + }); + + // Sanity checks when running a query + if (_this9.query && _this9.query.objectId) { + if (!objectIdMatch) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for update.'); + } + if (_this9.data.installationId && objectIdMatch.installationId && _this9.data.installationId !== objectIdMatch.installationId) { + throw new Parse.Error(136, 'installationId may not be changed in this ' + 'operation'); + } + if (_this9.data.deviceToken && objectIdMatch.deviceToken && _this9.data.deviceToken !== objectIdMatch.deviceToken && !_this9.data.installationId && !objectIdMatch.installationId) { + throw new Parse.Error(136, 'deviceToken may not be changed in this ' + 'operation'); + } + if (_this9.data.deviceType && _this9.data.deviceType && _this9.data.deviceType !== objectIdMatch.deviceType) { + throw new Parse.Error(136, 'deviceType may not be changed in this ' + 'operation'); + } + } + + if (_this9.query && _this9.query.objectId && objectIdMatch) { + idMatch = objectIdMatch; + } + + if (_this9.data.installationId && installationIdMatch) { + idMatch = installationIdMatch; + } + }).then(function () { + if (!idMatch) { + if (!deviceTokenMatches.length) { + return; + } else if (deviceTokenMatches.length == 1 && (!deviceTokenMatches[0]['installationId'] || !_this9.data.installationId)) { + // Single match on device token but none on installationId, and either + // the passed object or the match is missing an installationId, so we + // can just return the match. + return deviceTokenMatches[0]['objectId']; + } else if (!_this9.data.installationId) { + throw new Parse.Error(132, 'Must specify installationId when deviceToken ' + 'matches multiple Installation objects'); + } else { + // Multiple device token matches and we specified an installation ID, + // or a single match where both the passed and matching objects have + // an installation ID. Try cleaning out old installations that match + // the deviceToken, and return nil to signal that a new object should + // be created. + var delQuery = { + 'deviceToken': _this9.data.deviceToken, + 'installationId': { + '$ne': _this9.data.installationId + } + }; + if (_this9.data.appIdentifier) { + delQuery['appIdentifier'] = _this9.data.appIdentifier; + } + _this9.config.database.destroy('_Installation', delQuery); + return; + } + } else { + if (deviceTokenMatches.length == 1 && !deviceTokenMatches[0]['installationId']) { + // Exactly one device token match and it doesn't have an installation + // ID. This is the one case where we want to merge with the existing + // object. + var delQuery = { objectId: idMatch.objectId }; + return _this9.config.database.destroy('_Installation', delQuery).then(function () { + return deviceTokenMatches[0]['objectId']; + }); + } else { + if (_this9.data.deviceToken && idMatch.deviceToken != _this9.data.deviceToken) { + // We're setting the device token on an existing installation, so + // we should try cleaning out old installations that match this + // device token. + var delQuery = { + 'deviceToken': _this9.data.deviceToken + }; + // We have a unique install Id, use that to preserve + // the interesting installation + if (_this9.data.installationId) { + delQuery['installationId'] = { + '$ne': _this9.data.installationId + }; + } else if (idMatch.objectId && _this9.data.objectId && idMatch.objectId == _this9.data.objectId) { + // we passed an objectId, preserve that instalation + delQuery['objectId'] = { + '$ne': idMatch.objectId + }; + } else { + // What to do here? can't really clean up everything... + return idMatch.objectId; + } + if (_this9.data.appIdentifier) { + delQuery['appIdentifier'] = _this9.data.appIdentifier; + } + _this9.config.database.destroy('_Installation', delQuery); + } + // In non-merge scenarios, just return the installation match id + return idMatch.objectId; + } + } + }).then(function (objId) { + if (objId) { + _this9.query = { objectId: objId }; + delete _this9.data.objectId; + delete _this9.data.createdAt; + } + // TODO: Validate ops (add/remove on channels, $inc on badge, etc.) + }); + return promise; +}; + +// If we short-circuted the object response - then we need to make sure we expand all the files, +// since this might not have a query, meaning it won't return the full result back. +// TODO: (nlutsenko) This should die when we move to per-class based controllers on _Session/_User +RestWrite.prototype.expandFilesForExistingObjects = function () { + // Check whether we have a short-circuited response - only then run expansion. + if (this.response && this.response.response) { + this.config.filesController.expandFilesInObject(this.config, this.response.response); + } +}; + +RestWrite.prototype.runDatabaseOperation = function () { + var _this10 = this; + + if (this.response) { + return; + } + + if (this.className === '_Role') { + this.config.cacheController.role.clear(); + } + + if (this.className === '_User' && this.query && !this.auth.couldUpdateUserId(this.query.objectId)) { + throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Cannot modify user ' + this.query.objectId + '.'); + } + + if (this.className === '_Product' && this.data.download) { + this.data.downloadName = this.data.download.name; + } + + // TODO: Add better detection for ACL, ensuring a user can't be locked from + // their own user record. + if (this.data.ACL && this.data.ACL['*unresolved']) { + throw new Parse.Error(Parse.Error.INVALID_ACL, 'Invalid ACL.'); + } + + if (this.query) { + // Force the user to not lockout + // Matched with parse.com + if (this.className === '_User' && this.data.ACL) { + this.data.ACL[this.query.objectId] = { read: true, write: true }; + } + // Run an update + return this.config.database.update(this.className, this.query, this.data, this.runOptions).then(function (response) { + response.updatedAt = _this10.updatedAt; + if (_this10.storage.changedByTrigger) { + _this10.updateResponseWithData(response, _this10.data); + } + _this10.response = { response: response }; + }); + } else { + // Set the default ACL for the new _User + if (this.className === '_User') { + var ACL = this.data.ACL; + // default public r/w ACL + if (!ACL) { + ACL = {}; + ACL['*'] = { read: true, write: false }; + } + // make sure the user is not locked down + ACL[this.data.objectId] = { read: true, write: true }; + this.data.ACL = ACL; + } + + // Run a create + return this.config.database.create(this.className, this.data, this.runOptions).catch(function (error) { + if (_this10.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) { + throw error; + } + // If this was a failed user creation due to username or email already taken, we need to + // check whether it was username or email and return the appropriate error. + + // TODO: See if we can later do this without additional queries by using named indexes. + return _this10.config.database.find(_this10.className, { username: _this10.data.username, objectId: { '$ne': _this10.objectId() } }, { limit: 1 }).then(function (results) { + if (results.length > 0) { + throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.'); + } + return _this10.config.database.find(_this10.className, { email: _this10.data.email, objectId: { '$ne': _this10.objectId() } }, { limit: 1 }); + }).then(function (results) { + if (results.length > 0) { + throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.'); + } + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided'); + }); + }).then(function (response) { + response.objectId = _this10.data.objectId; + response.createdAt = _this10.data.createdAt; + + if (_this10.responseShouldHaveUsername) { + response.username = _this10.data.username; + } + if (_this10.storage.changedByTrigger) { + _this10.updateResponseWithData(response, _this10.data); + } + _this10.response = { + status: 201, + response: response, + location: _this10.location() + }; + }); + } +}; + +// Returns nothing - doesn't wait for the trigger. +RestWrite.prototype.runAfterTrigger = function () { + if (!this.response || !this.response.response) { + return; + } + + // Avoid doing any setup for triggers if there is no 'afterSave' trigger for this class. + var hasAfterSaveHook = triggers.triggerExists(this.className, triggers.Types.afterSave, this.config.applicationId); + var hasLiveQuery = this.config.liveQueryController.hasLiveQuery(this.className); + if (!hasAfterSaveHook && !hasLiveQuery) { + return Promise.resolve(); + } + + var extraData = { className: this.className }; + if (this.query && this.query.objectId) { + extraData.objectId = this.query.objectId; + } + + // Build the original object, we only do this for a update write. + var originalObject = void 0; + if (this.query && this.query.objectId) { + originalObject = triggers.inflate(extraData, this.originalData); + } + + // Build the inflated object, different from beforeSave, originalData is not empty + // since developers can change data in the beforeSave. + var updatedObject = triggers.inflate(extraData, this.originalData); + updatedObject.set(this.sanitizedData()); + updatedObject._handleSaveResponse(this.response.response, this.response.status || 200); + + // Notifiy LiveQueryServer if possible + this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject); + + // Run afterSave trigger + triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config); +}; + +// A helper to figure out what location this operation happens at. +RestWrite.prototype.location = function () { + var middle = this.className === '_User' ? '/users/' : '/classes/' + this.className + '/'; + return this.config.mount + middle + this.data.objectId; +}; + +// A helper to get the object id for this operation. +// Because it could be either on the query or on the data +RestWrite.prototype.objectId = function () { + return this.data.objectId || this.query.objectId; +}; + +// Returns a copy of the data and delete bad keys (_auth_data, _hashed_password...) +RestWrite.prototype.sanitizedData = function () { + var data = Object.keys(this.data).reduce(function (data, key) { + // Regexp comes from Parse.Object.prototype.validate + if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) { + delete data[key]; + } + return data; + }, deepcopy(this.data)); + return Parse._decode(undefined, data); +}; + +RestWrite.prototype.cleanUserAuthData = function () { + var _this11 = this; + + if (this.response && this.response.response && this.className === '_User') { + (function () { + var user = _this11.response.response; + if (user.authData) { + Object.keys(user.authData).forEach(function (provider) { + if (user.authData[provider] === null) { + delete user.authData[provider]; + } + }); + if (Object.keys(user.authData).length == 0) { + delete user.authData; + } + } + })(); + } +}; + +RestWrite.prototype.updateResponseWithData = function (response, data) { + var clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK); + Object.keys(data).forEach(function (fieldName) { + var dataValue = data[fieldName]; + var responseValue = response[fieldName]; + + response[fieldName] = responseValue || dataValue; + + // Strips operations from responses + if (response[fieldName] && response[fieldName].__op) { + delete response[fieldName]; + if (clientSupportsDelete && dataValue.__op == 'Delete') { + response[fieldName] = dataValue; + } + } + }); + return response; +}; + +exports.default = RestWrite; + +module.exports = RestWrite; \ No newline at end of file diff --git a/lib/Routers/AnalyticsRouter.js b/lib/Routers/AnalyticsRouter.js new file mode 100644 index 0000000000..c8632f069b --- /dev/null +++ b/lib/Routers/AnalyticsRouter.js @@ -0,0 +1,51 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.AnalyticsRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // AnalyticsRouter.js + + +function appOpened(req) { + var analyticsController = req.config.analyticsController; + return analyticsController.appOpened(req); +} + +function trackEvent(req) { + var analyticsController = req.config.analyticsController; + return analyticsController.trackEvent(req); +} + +var AnalyticsRouter = exports.AnalyticsRouter = function (_PromiseRouter) { + _inherits(AnalyticsRouter, _PromiseRouter); + + function AnalyticsRouter() { + _classCallCheck(this, AnalyticsRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(AnalyticsRouter).apply(this, arguments)); + } + + _createClass(AnalyticsRouter, [{ + key: 'mountRoutes', + value: function mountRoutes() { + this.route('POST', '/events/AppOpened', appOpened); + this.route('POST', '/events/:eventName', trackEvent); + } + }]); + + return AnalyticsRouter; +}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/ClassesRouter.js b/lib/Routers/ClassesRouter.js new file mode 100644 index 0000000000..811e4880cb --- /dev/null +++ b/lib/Routers/ClassesRouter.js @@ -0,0 +1,278 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ClassesRouter = undefined; + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +var _url = require('url'); + +var _url2 = _interopRequireDefault(_url); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var ALLOWED_GET_QUERY_KEYS = ['keys', 'include']; + +var ClassesRouter = exports.ClassesRouter = function (_PromiseRouter) { + _inherits(ClassesRouter, _PromiseRouter); + + function ClassesRouter() { + _classCallCheck(this, ClassesRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(ClassesRouter).apply(this, arguments)); + } + + _createClass(ClassesRouter, [{ + key: 'handleFind', + value: function handleFind(req) { + var body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); + var options = {}; + var allowConstraints = ['skip', 'limit', 'order', 'count', 'keys', 'include', 'redirectClassNameForKey', 'where']; + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = Object.keys(body)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var key = _step.value; + + if (allowConstraints.indexOf(key) === -1) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Invalid parameter for query: ' + key); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + if (body.skip) { + options.skip = Number(body.skip); + } + if (body.limit || body.limit === 0) { + options.limit = Number(body.limit); + } else { + options.limit = Number(100); + } + if (body.order) { + options.order = String(body.order); + } + if (body.count) { + options.count = true; + } + if (typeof body.keys == 'string') { + options.keys = body.keys; + } + if (body.include) { + options.include = String(body.include); + } + if (body.redirectClassNameForKey) { + options.redirectClassNameForKey = String(body.redirectClassNameForKey); + } + if (typeof body.where === 'string') { + body.where = JSON.parse(body.where); + } + return _rest2.default.find(req.config, req.auth, req.params.className, body.where, options, req.info.clientSDK).then(function (response) { + if (response && response.results) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = response.results[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var result = _step2.value; + + if (result.sessionToken) { + result.sessionToken = req.info.sessionToken || result.sessionToken; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + } + return { response: response }; + }); + } + + // Returns a promise for a {response} object. + + }, { + key: 'handleGet', + value: function handleGet(req) { + var body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); + var options = {}; + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = Object.keys(body)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var key = _step3.value; + + if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter'); + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + if (typeof body.keys == 'string') { + options.keys = body.keys; + } + if (body.include) { + options.include = String(body.include); + } + + return _rest2.default.get(req.config, req.auth, req.params.className, req.params.objectId, options, req.info.clientSDK).then(function (response) { + if (!response.results || response.results.length == 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } + + if (req.params.className === "_User") { + + delete response.results[0].sessionToken; + + var user = response.results[0]; + + if (req.auth.user && user.objectId == req.auth.user.id) { + // Force the session token + response.results[0].sessionToken = req.info.sessionToken; + } + } + return { response: response.results[0] }; + }); + } + }, { + key: 'handleCreate', + value: function handleCreate(req) { + return _rest2.default.create(req.config, req.auth, req.params.className, req.body, req.info.clientSDK); + } + }, { + key: 'handleUpdate', + value: function handleUpdate(req) { + return _rest2.default.update(req.config, req.auth, req.params.className, req.params.objectId, req.body, req.info.clientSDK); + } + }, { + key: 'handleDelete', + value: function handleDelete(req) { + return _rest2.default.del(req.config, req.auth, req.params.className, req.params.objectId, req.info.clientSDK).then(function () { + return { response: {} }; + }); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/classes/:className', function (req) { + return _this2.handleFind(req); + }); + this.route('GET', '/classes/:className/:objectId', function (req) { + return _this2.handleGet(req); + }); + this.route('POST', '/classes/:className', function (req) { + return _this2.handleCreate(req); + }); + this.route('PUT', '/classes/:className/:objectId', function (req) { + return _this2.handleUpdate(req); + }); + this.route('DELETE', '/classes/:className/:objectId', function (req) { + return _this2.handleDelete(req); + }); + } + }], [{ + key: 'JSONFromQuery', + value: function JSONFromQuery(query) { + var json = {}; + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = Object.entries(query)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var _step4$value = _slicedToArray(_step4.value, 2); + + var key = _step4$value[0]; + var value = _step4$value[1]; + + try { + json[key] = JSON.parse(value); + } catch (e) { + json[key] = value; + } + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + return json; + } + }]); + + return ClassesRouter; +}(_PromiseRouter3.default); + +exports.default = ClassesRouter; \ No newline at end of file diff --git a/lib/Routers/FeaturesRouter.js b/lib/Routers/FeaturesRouter.js new file mode 100644 index 0000000000..4319e4abef --- /dev/null +++ b/lib/Routers/FeaturesRouter.js @@ -0,0 +1,90 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FeaturesRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _package = require('../../package.json'); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require('../middlewares'); + +var middleware = _interopRequireWildcard(_middlewares); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var FeaturesRouter = exports.FeaturesRouter = function (_PromiseRouter) { + _inherits(FeaturesRouter, _PromiseRouter); + + function FeaturesRouter() { + _classCallCheck(this, FeaturesRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(FeaturesRouter).apply(this, arguments)); + } + + _createClass(FeaturesRouter, [{ + key: 'mountRoutes', + value: function mountRoutes() { + this.route('GET', '/serverInfo', middleware.promiseEnforceMasterKeyAccess, function (req) { + var features = { + globalConfig: { + create: true, + read: true, + update: true, + delete: true + }, + hooks: { + create: false, + read: false, + update: false, + delete: false + }, + logs: { + level: true, + size: true, + order: true, + until: true, + from: true + }, + push: { + immediatePush: req.config.pushController.pushIsAvailable, + scheduledPush: false, + storedPushData: req.config.pushController.pushIsAvailable, + pushAudiences: false + }, + schemas: { + addField: true, + removeField: true, + addClass: true, + removeClass: true, + clearAllDataFromClass: true, + exportClass: false, + editClassLevelPermissions: true, + editPointerPermissions: true + } + }; + + return { response: { + features: features, + parseServerVersion: _package.version + } }; + }); + } + }]); + + return FeaturesRouter; +}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/FilesRouter.js b/lib/Routers/FilesRouter.js new file mode 100644 index 0000000000..7e7dbcda6a --- /dev/null +++ b/lib/Routers/FilesRouter.js @@ -0,0 +1,126 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FilesRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _express = require('express'); + +var _express2 = _interopRequireDefault(_express); + +var _bodyParser = require('body-parser'); + +var _bodyParser2 = _interopRequireDefault(_bodyParser); + +var _middlewares = require('../middlewares'); + +var Middlewares = _interopRequireWildcard(_middlewares); + +var _cryptoUtils = require('../cryptoUtils'); + +var _Config = require('../Config'); + +var _Config2 = _interopRequireDefault(_Config); + +var _mime = require('mime'); + +var _mime2 = _interopRequireDefault(_mime); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var FilesRouter = exports.FilesRouter = function () { + function FilesRouter() { + _classCallCheck(this, FilesRouter); + } + + _createClass(FilesRouter, [{ + key: 'getExpressRouter', + value: function getExpressRouter() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var router = _express2.default.Router(); + router.get('/files/:appId/:filename', this.getHandler); + + router.post('/files', function (req, res, next) { + next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename not provided.')); + }); + + router.post('/files/:filename', Middlewares.allowCrossDomain, _bodyParser2.default.raw({ type: function type() { + return true; + }, limit: options.maxUploadSize || '20mb' }), // Allow uploads without Content-Type, or with any Content-Type. + Middlewares.handleParseHeaders, this.createHandler); + + router.delete('/files/:filename', Middlewares.allowCrossDomain, Middlewares.handleParseHeaders, Middlewares.enforceMasterKeyAccess, this.deleteHandler); + return router; + } + }, { + key: 'getHandler', + value: function getHandler(req, res) { + var config = new _Config2.default(req.params.appId); + var filesController = config.filesController; + var filename = req.params.filename; + filesController.getFileData(config, filename).then(function (data) { + res.status(200); + var contentType = _mime2.default.lookup(filename); + res.set('Content-Type', contentType); + res.end(data); + }).catch(function (err) { + res.status(404); + res.set('Content-Type', 'text/plain'); + res.end('File not found.'); + }); + } + }, { + key: 'createHandler', + value: function createHandler(req, res, next) { + if (!req.body || !req.body.length) { + next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')); + return; + } + + if (req.params.filename.length > 128) { + next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.')); + return; + } + + if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) { + next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.')); + return; + } + + var filename = req.params.filename; + var contentType = req.get('Content-type'); + var config = req.config; + var filesController = config.filesController; + + filesController.createFile(config, filename, req.body, contentType).then(function (result) { + res.status(201); + res.set('Location', result.url); + res.json(result); + }).catch(function (err) { + next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Could not store file.')); + }); + } + }, { + key: 'deleteHandler', + value: function deleteHandler(req, res, next) { + var filesController = req.config.filesController; + filesController.deleteFile(req.config, req.params.filename).then(function () { + res.status(200); + // TODO: return useful JSON here? + res.end(); + }).catch(function (error) { + next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR, 'Could not delete file.')); + }); + } + }]); + + return FilesRouter; +}(); \ No newline at end of file diff --git a/lib/Routers/FunctionsRouter.js b/lib/Routers/FunctionsRouter.js new file mode 100644 index 0000000000..bd9221f3a4 --- /dev/null +++ b/lib/Routers/FunctionsRouter.js @@ -0,0 +1,155 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FunctionsRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _lodash = require('lodash'); + +var _lodash2 = _interopRequireDefault(_lodash); + +var _logger = require('../logger'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +// FunctionsRouter.js + +var express = require('express'), + Parse = require('parse/node').Parse, + triggers = require('../triggers'); + +function parseObject(obj) { + if (Array.isArray(obj)) { + return obj.map(function (item) { + return parseObject(item); + }); + } else if (obj && obj.__type == 'Date') { + return Object.assign(new Date(obj.iso), obj); + } else if (obj && obj.__type == 'File') { + return Parse.File.fromJSON(obj); + } else if (obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object') { + return parseParams(obj); + } else { + return obj; + } +} + +function parseParams(params) { + return _lodash2.default.mapValues(params, parseObject); +} + +var FunctionsRouter = exports.FunctionsRouter = function (_PromiseRouter) { + _inherits(FunctionsRouter, _PromiseRouter); + + function FunctionsRouter() { + _classCallCheck(this, FunctionsRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(FunctionsRouter).apply(this, arguments)); + } + + _createClass(FunctionsRouter, [{ + key: 'mountRoutes', + value: function mountRoutes() { + this.route('POST', '/functions/:functionName', FunctionsRouter.handleCloudFunction); + } + }], [{ + key: 'createResponseObject', + value: function createResponseObject(resolve, reject) { + return { + success: function success(result) { + resolve({ + response: { + result: Parse._encode(result) + } + }); + }, + error: function error(code, message) { + if (!message) { + message = code; + code = Parse.Error.SCRIPT_FAILED; + } + reject(new Parse.Error(code, message)); + } + }; + } + }, { + key: 'handleCloudFunction', + value: function handleCloudFunction(req) { + var applicationId = req.config.applicationId; + var theFunction = triggers.getFunction(req.params.functionName, applicationId); + var theValidator = triggers.getValidator(req.params.functionName, applicationId); + if (theFunction) { + var request; + var result; + + var _ret = function () { + var params = Object.assign({}, req.body, req.query); + params = parseParams(params); + request = { + params: params, + master: req.auth && req.auth.isMaster, + user: req.auth && req.auth.user, + installationId: req.info.installationId, + log: req.config.loggerController && req.config.loggerController.adapter, + headers: req.headers + }; + + + if (theValidator && typeof theValidator === "function") { + result = theValidator(request); + + if (!result) { + throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed.'); + } + } + + return { + v: new Promise(function (resolve, reject) { + var response = FunctionsRouter.createResponseObject(function (result) { + _logger.logger.info('Ran cloud function ' + req.params.functionName + ' with:\nInput: ' + JSON.stringify(params) + '\nResult: ' + JSON.stringify(result.response.result), { + functionName: req.params.functionName, + params: params, + result: result.response.resut + }); + resolve(result); + }, function (error) { + _logger.logger.error('Failed running cloud function ' + req.params.functionName + ' with:\nInput: ' + JSON.stringify(params) + 'Error: ' + JSON.stringify(error), { + functionName: req.params.functionName, + params: params, + error: error + }); + reject(error); + }); + // Force the keys before the function calls. + Parse.applicationId = req.config.applicationId; + Parse.javascriptKey = req.config.javascriptKey; + Parse.masterKey = req.config.masterKey; + theFunction(request, response); + }) + }; + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } else { + throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function.'); + } + } + }]); + + return FunctionsRouter; +}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/GlobalConfigRouter.js b/lib/Routers/GlobalConfigRouter.js new file mode 100644 index 0000000000..d59b8a3b03 --- /dev/null +++ b/lib/Routers/GlobalConfigRouter.js @@ -0,0 +1,79 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.GlobalConfigRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require('../middlewares'); + +var middleware = _interopRequireWildcard(_middlewares); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // global_config.js + +var GlobalConfigRouter = exports.GlobalConfigRouter = function (_PromiseRouter) { + _inherits(GlobalConfigRouter, _PromiseRouter); + + function GlobalConfigRouter() { + _classCallCheck(this, GlobalConfigRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(GlobalConfigRouter).apply(this, arguments)); + } + + _createClass(GlobalConfigRouter, [{ + key: 'getGlobalConfig', + value: function getGlobalConfig(req) { + return req.config.database.find('_GlobalConfig', { objectId: 1 }, { limit: 1 }).then(function (results) { + if (results.length != 1) { + // If there is no config in the database - return empty config. + return { response: { params: {} } }; + } + var globalConfig = results[0]; + return { response: { params: globalConfig.params } }; + }); + } + }, { + key: 'updateGlobalConfig', + value: function updateGlobalConfig(req) { + var params = req.body.params; + // Transform in dot notation to make sure it works + var update = Object.keys(params).reduce(function (acc, key) { + acc['params.' + key] = params[key]; + return acc; + }, {}); + return req.config.database.update('_GlobalConfig', { objectId: 1 }, update, { upsert: true }).then(function () { + return { response: { result: true } }; + }); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/config', function (req) { + return _this2.getGlobalConfig(req); + }); + this.route('PUT', '/config', middleware.promiseEnforceMasterKeyAccess, function (req) { + return _this2.updateGlobalConfig(req); + }); + } + }]); + + return GlobalConfigRouter; +}(_PromiseRouter3.default); + +exports.default = GlobalConfigRouter; \ No newline at end of file diff --git a/lib/Routers/HooksRouter.js b/lib/Routers/HooksRouter.js new file mode 100644 index 0000000000..909c0d88dc --- /dev/null +++ b/lib/Routers/HooksRouter.js @@ -0,0 +1,155 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.HooksRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _node = require('parse/node'); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require('../middlewares'); + +var middleware = _interopRequireWildcard(_middlewares); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var HooksRouter = exports.HooksRouter = function (_PromiseRouter) { + _inherits(HooksRouter, _PromiseRouter); + + function HooksRouter() { + _classCallCheck(this, HooksRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(HooksRouter).apply(this, arguments)); + } + + _createClass(HooksRouter, [{ + key: 'createHook', + value: function createHook(aHook, config) { + return config.hooksController.createHook(aHook).then(function (hook) { + return { response: hook }; + }); + } + }, { + key: 'updateHook', + value: function updateHook(aHook, config) { + return config.hooksController.updateHook(aHook).then(function (hook) { + return { response: hook }; + }); + } + }, { + key: 'handlePost', + value: function handlePost(req) { + return this.createHook(req.body, req.config); + } + }, { + key: 'handleGetFunctions', + value: function handleGetFunctions(req) { + var hooksController = req.config.hooksController; + if (req.params.functionName) { + return hooksController.getFunction(req.params.functionName).then(function (foundFunction) { + if (!foundFunction) { + throw new _node.Parse.Error(143, 'no function named: ' + req.params.functionName + ' is defined'); + } + return Promise.resolve({ response: foundFunction }); + }); + } + + return hooksController.getFunctions().then(function (functions) { + return { response: functions || [] }; + }, function (err) { + throw err; + }); + } + }, { + key: 'handleGetTriggers', + value: function handleGetTriggers(req) { + var hooksController = req.config.hooksController; + if (req.params.className && req.params.triggerName) { + + return hooksController.getTrigger(req.params.className, req.params.triggerName).then(function (foundTrigger) { + if (!foundTrigger) { + throw new _node.Parse.Error(143, 'class ' + req.params.className + ' does not exist'); + } + return Promise.resolve({ response: foundTrigger }); + }); + } + + return hooksController.getTriggers().then(function (triggers) { + return { response: triggers || [] }; + }); + } + }, { + key: 'handleDelete', + value: function handleDelete(req) { + var hooksController = req.config.hooksController; + if (req.params.functionName) { + return hooksController.deleteFunction(req.params.functionName).then(function () { + return { response: {} }; + }); + } else if (req.params.className && req.params.triggerName) { + return hooksController.deleteTrigger(req.params.className, req.params.triggerName).then(function () { + return { response: {} }; + }); + } + return Promise.resolve({ response: {} }); + } + }, { + key: 'handleUpdate', + value: function handleUpdate(req) { + var hook; + if (req.params.functionName && req.body.url) { + hook = {}; + hook.functionName = req.params.functionName; + hook.url = req.body.url; + } else if (req.params.className && req.params.triggerName && req.body.url) { + hook = {}; + hook.className = req.params.className; + hook.triggerName = req.params.triggerName; + hook.url = req.body.url; + } else { + throw new _node.Parse.Error(143, "invalid hook declaration"); + } + return this.updateHook(hook, req.config); + } + }, { + key: 'handlePut', + value: function handlePut(req) { + var body = req.body; + if (body.__op == "Delete") { + return this.handleDelete(req); + } else { + return this.handleUpdate(req); + } + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + this.route('GET', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this)); + this.route('GET', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this)); + this.route('GET', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this)); + this.route('GET', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this)); + this.route('POST', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this)); + this.route('POST', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this)); + this.route('PUT', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this)); + this.route('PUT', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this)); + } + }]); + + return HooksRouter; +}(_PromiseRouter3.default); + +exports.default = HooksRouter; \ No newline at end of file diff --git a/lib/Routers/IAPValidationRouter.js b/lib/Routers/IAPValidationRouter.js new file mode 100644 index 0000000000..ce91f9846e --- /dev/null +++ b/lib/Routers/IAPValidationRouter.js @@ -0,0 +1,143 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.IAPValidationRouter = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require("../PromiseRouter"); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var request = require("request"); +var rest = require("../rest"); +var Auth = require("../Auth"); + +// TODO move validation logic in IAPValidationController +var IAP_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt"; +var IAP_PRODUCTION_URL = "https://buy.itunes.apple.com/verifyReceipt"; + +var APP_STORE_ERRORS = { + 21000: "The App Store could not read the JSON object you provided.", + 21002: "The data in the receipt-data property was malformed or missing.", + 21003: "The receipt could not be authenticated.", + 21004: "The shared secret you provided does not match the shared secret on file for your account.", + 21005: "The receipt server is not currently available.", + 21006: "This receipt is valid but the subscription has expired.", + 21007: "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.", + 21008: "This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead." +}; + +function appStoreError(status) { + status = parseInt(status); + var errorString = APP_STORE_ERRORS[status] || "unknown error."; + return { status: status, error: errorString }; +} + +function validateWithAppStore(url, receipt) { + return new Promise(function (fulfill, reject) { + request.post({ + url: url, + body: { "receipt-data": receipt }, + json: true + }, function (err, res, body) { + var status = body.status; + if (status == 0) { + // No need to pass anything, status is OK + return fulfill(); + } + // receipt is from test and should go to test + return reject(body); + }); + }); +} + +function getFileForProductIdentifier(productIdentifier, req) { + return rest.find(req.config, req.auth, '_Product', { productIdentifier: productIdentifier }, undefined, req.info.clientSDK).then(function (result) { + var products = result.results; + if (!products || products.length != 1) { + // Error not found or too many + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } + + var download = products[0].download; + return Promise.resolve({ response: download }); + }); +} + +var IAPValidationRouter = exports.IAPValidationRouter = function (_PromiseRouter) { + _inherits(IAPValidationRouter, _PromiseRouter); + + function IAPValidationRouter() { + _classCallCheck(this, IAPValidationRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(IAPValidationRouter).apply(this, arguments)); + } + + _createClass(IAPValidationRouter, [{ + key: "handleRequest", + value: function handleRequest(req) { + var receipt = req.body.receipt; + var productIdentifier = req.body.productIdentifier; + + if (!receipt || !productIdentifier) { + // TODO: Error, malformed request + throw new Parse.Error(Parse.Error.INVALID_JSON, "missing receipt or productIdentifier"); + } + + // Transform the object if there + // otherwise assume it's in Base64 already + if ((typeof receipt === "undefined" ? "undefined" : _typeof(receipt)) == "object") { + if (receipt["__type"] == "Bytes") { + receipt = receipt.base64; + } + } + + if (process.env.NODE_ENV == "test" && req.body.bypassAppStoreValidation) { + return getFileForProductIdentifier(productIdentifier, req); + } + + function successCallback() { + return getFileForProductIdentifier(productIdentifier, req); + }; + + function errorCallback(error) { + return Promise.resolve({ response: appStoreError(error.status) }); + } + + return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then(function () { + + return successCallback(); + }, function (error) { + if (error.status == 21007) { + return validateWithAppStore(IAP_SANDBOX_URL, receipt).then(function () { + return successCallback(); + }, function (error) { + return errorCallback(error); + }); + } + + return errorCallback(error); + }); + } + }, { + key: "mountRoutes", + value: function mountRoutes() { + this.route("POST", "/validate_purchase", this.handleRequest); + } + }]); + + return IAPValidationRouter; +}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/InstallationsRouter.js b/lib/Routers/InstallationsRouter.js new file mode 100644 index 0000000000..7b8d5ac4c6 --- /dev/null +++ b/lib/Routers/InstallationsRouter.js @@ -0,0 +1,117 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.InstallationsRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _ClassesRouter2 = require('./ClassesRouter'); + +var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); + +var _PromiseRouter = require('../PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // InstallationsRouter.js + +var InstallationsRouter = exports.InstallationsRouter = function (_ClassesRouter) { + _inherits(InstallationsRouter, _ClassesRouter); + + function InstallationsRouter() { + _classCallCheck(this, InstallationsRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(InstallationsRouter).apply(this, arguments)); + } + + _createClass(InstallationsRouter, [{ + key: 'handleFind', + value: function handleFind(req) { + var body = Object.assign(req.body, _ClassesRouter3.default.JSONFromQuery(req.query)); + var options = {}; + + if (body.skip) { + options.skip = Number(body.skip); + } + if (body.limit || body.limit === 0) { + options.limit = Number(body.limit); + } + if (body.order) { + options.order = String(body.order); + } + if (body.count) { + options.count = true; + } + if (body.include) { + options.include = String(body.include); + } + + return _rest2.default.find(req.config, req.auth, '_Installation', body.where, options, req.info.clientSDK).then(function (response) { + return { response: response }; + }); + } + }, { + key: 'handleGet', + value: function handleGet(req) { + req.params.className = '_Installation'; + return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleGet', this).call(this, req); + } + }, { + key: 'handleCreate', + value: function handleCreate(req) { + req.params.className = '_Installation'; + return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleCreate', this).call(this, req); + } + }, { + key: 'handleUpdate', + value: function handleUpdate(req) { + req.params.className = '_Installation'; + return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleUpdate', this).call(this, req); + } + }, { + key: 'handleDelete', + value: function handleDelete(req) { + req.params.className = '_Installation'; + return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleDelete', this).call(this, req); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/installations', function (req) { + return _this2.handleFind(req); + }); + this.route('GET', '/installations/:objectId', function (req) { + return _this2.handleGet(req); + }); + this.route('POST', '/installations', function (req) { + return _this2.handleCreate(req); + }); + this.route('PUT', '/installations/:objectId', function (req) { + return _this2.handleUpdate(req); + }); + this.route('DELETE', '/installations/:objectId', function (req) { + return _this2.handleDelete(req); + }); + } + }]); + + return InstallationsRouter; +}(_ClassesRouter3.default); + +exports.default = InstallationsRouter; \ No newline at end of file diff --git a/lib/Routers/LogsRouter.js b/lib/Routers/LogsRouter.js new file mode 100644 index 0000000000..79b4076a0c --- /dev/null +++ b/lib/Routers/LogsRouter.js @@ -0,0 +1,96 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LogsRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _node = require('parse/node'); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require('../middlewares'); + +var middleware = _interopRequireWildcard(_middlewares); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var LogsRouter = exports.LogsRouter = function (_PromiseRouter) { + _inherits(LogsRouter, _PromiseRouter); + + function LogsRouter() { + _classCallCheck(this, LogsRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(LogsRouter).apply(this, arguments)); + } + + _createClass(LogsRouter, [{ + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/scriptlog', middleware.promiseEnforceMasterKeyAccess, this.validateRequest, function (req) { + return _this2.handleGET(req); + }); + } + }, { + key: 'validateRequest', + value: function validateRequest(req) { + if (!req.config || !req.config.loggerController) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not availabe'); + } + } + + // Returns a promise for a {response} object. + // query params: + // level (optional) Level of logging you want to query for (info || error) + // from (optional) Start time for the search. Defaults to 1 week ago. + // until (optional) End time for the search. Defaults to current time. + // order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”. + // size (optional) Number of rows returned by search. Defaults to 10 + // n same as size, overrides size if set + + }, { + key: 'handleGET', + value: function handleGET(req) { + var from = req.query.from; + var until = req.query.until; + var size = req.query.size; + if (req.query.n) { + size = req.query.n; + } + + var order = req.query.order; + var level = req.query.level; + var options = { + from: from, + until: until, + size: size, + order: order, + level: level + }; + + return req.config.loggerController.getLogs(options).then(function (result) { + return Promise.resolve({ + response: result + }); + }); + } + }]); + + return LogsRouter; +}(_PromiseRouter3.default); + +exports.default = LogsRouter; \ No newline at end of file diff --git a/lib/Routers/PublicAPIRouter.js b/lib/Routers/PublicAPIRouter.js new file mode 100644 index 0000000000..ce5c28050e --- /dev/null +++ b/lib/Routers/PublicAPIRouter.js @@ -0,0 +1,239 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PublicAPIRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _UserController = require('../Controllers/UserController'); + +var _UserController2 = _interopRequireDefault(_UserController); + +var _Config = require('../Config'); + +var _Config2 = _interopRequireDefault(_Config); + +var _express = require('express'); + +var _express2 = _interopRequireDefault(_express); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _querystring = require('querystring'); + +var _querystring2 = _interopRequireDefault(_querystring); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var public_html = _path2.default.resolve(__dirname, "../../public_html"); +var views = _path2.default.resolve(__dirname, '../../views'); + +var PublicAPIRouter = exports.PublicAPIRouter = function (_PromiseRouter) { + _inherits(PublicAPIRouter, _PromiseRouter); + + function PublicAPIRouter() { + _classCallCheck(this, PublicAPIRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(PublicAPIRouter).apply(this, arguments)); + } + + _createClass(PublicAPIRouter, [{ + key: 'verifyEmail', + value: function verifyEmail(req) { + var _this2 = this; + + var _req$query = req.query; + var token = _req$query.token; + var username = _req$query.username; + + var appId = req.params.appId; + var config = new _Config2.default(appId); + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } + + if (!token || !username) { + return this.invalidLink(req); + } + + var userController = config.userController; + return userController.verifyEmail(username, token).then(function () { + var params = _querystring2.default.stringify({ username: username }); + return Promise.resolve({ + status: 302, + location: config.verifyEmailSuccessURL + '?' + params + }); + }, function () { + return _this2.invalidLink(req); + }); + } + }, { + key: 'changePassword', + value: function changePassword(req) { + return new Promise(function (resolve, reject) { + var config = new _Config2.default(req.query.id); + if (!config.publicServerURL) { + return resolve({ + status: 404, + text: 'Not found.' + }); + } + // Should we keep the file in memory or leave like that? + _fs2.default.readFile(_path2.default.resolve(views, "choose_password"), 'utf-8', function (err, data) { + if (err) { + return reject(err); + } + data = data.replace("PARSE_SERVER_URL", '\'' + config.publicServerURL + '\''); + resolve({ + text: data + }); + }); + }); + } + }, { + key: 'requestResetPassword', + value: function requestResetPassword(req) { + var _this3 = this; + + var config = req.config; + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } + + var _req$query2 = req.query; + var username = _req$query2.username; + var token = _req$query2.token; + + + if (!username || !token) { + return this.invalidLink(req); + } + + return config.userController.checkResetTokenValidity(username, token).then(function (user) { + var params = _querystring2.default.stringify({ token: token, id: config.applicationId, username: username, app: config.appName }); + return Promise.resolve({ + status: 302, + location: config.choosePasswordURL + '?' + params + }); + }, function () { + return _this3.invalidLink(req); + }); + } + }, { + key: 'resetPassword', + value: function resetPassword(req) { + + var config = req.config; + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } + + var _req$body = req.body; + var username = _req$body.username; + var token = _req$body.token; + var new_password = _req$body.new_password; + + + if (!username || !token || !new_password) { + return this.invalidLink(req); + } + + return config.userController.updatePassword(username, token, new_password).then(function (result) { + return Promise.resolve({ + status: 302, + location: config.passwordResetSuccessURL + }); + }, function (err) { + var params = _querystring2.default.stringify({ username: username, token: token, id: config.applicationId, error: err, app: config.appName }); + return Promise.resolve({ + status: 302, + location: config.choosePasswordURL + '?' + params + }); + }); + } + }, { + key: 'invalidLink', + value: function invalidLink(req) { + return Promise.resolve({ + status: 302, + location: req.config.invalidLinkURL + }); + } + }, { + key: 'missingPublicServerURL', + value: function missingPublicServerURL() { + return Promise.resolve({ + text: 'Not found.', + status: 404 + }); + } + }, { + key: 'setConfig', + value: function setConfig(req) { + req.config = new _Config2.default(req.params.appId); + return Promise.resolve(); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this4 = this; + + this.route('GET', '/apps/:appId/verify_email', function (req) { + _this4.setConfig(req); + }, function (req) { + return _this4.verifyEmail(req); + }); + + this.route('GET', '/apps/choose_password', function (req) { + return _this4.changePassword(req); + }); + + this.route('POST', '/apps/:appId/request_password_reset', function (req) { + _this4.setConfig(req); + }, function (req) { + return _this4.resetPassword(req); + }); + + this.route('GET', '/apps/:appId/request_password_reset', function (req) { + _this4.setConfig(req); + }, function (req) { + return _this4.requestResetPassword(req); + }); + } + }, { + key: 'expressApp', + value: function expressApp() { + var router = (0, _express2.default)(); + router.use("/apps", _express2.default.static(public_html)); + router.use("/", _get(Object.getPrototypeOf(PublicAPIRouter.prototype), 'expressApp', this).call(this)); + return router; + } + }]); + + return PublicAPIRouter; +}(_PromiseRouter3.default); + +exports.default = PublicAPIRouter; \ No newline at end of file diff --git a/lib/Routers/PurgeRouter.js b/lib/Routers/PurgeRouter.js new file mode 100644 index 0000000000..9b26b79b7b --- /dev/null +++ b/lib/Routers/PurgeRouter.js @@ -0,0 +1,64 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PurgeRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require('../middlewares'); + +var middleware = _interopRequireWildcard(_middlewares); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var PurgeRouter = exports.PurgeRouter = function (_PromiseRouter) { + _inherits(PurgeRouter, _PromiseRouter); + + function PurgeRouter() { + _classCallCheck(this, PurgeRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(PurgeRouter).apply(this, arguments)); + } + + _createClass(PurgeRouter, [{ + key: 'handlePurge', + value: function handlePurge(req) { + return req.config.database.purgeCollection(req.params.className).then(function () { + var cacheAdapter = req.config.cacheController; + if (req.params.className == '_Session') { + cacheAdapter.user.clear(); + } else if (req.params.className == '_Role') { + cacheAdapter.role.clear(); + } + return { response: {} }; + }); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('DELETE', '/purge/:className', middleware.promiseEnforceMasterKeyAccess, function (req) { + return _this2.handlePurge(req); + }); + } + }]); + + return PurgeRouter; +}(_PromiseRouter3.default); + +exports.default = PurgeRouter; \ No newline at end of file diff --git a/lib/Routers/PushRouter.js b/lib/Routers/PushRouter.js new file mode 100644 index 0000000000..dadd1cc065 --- /dev/null +++ b/lib/Routers/PushRouter.js @@ -0,0 +1,104 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PushRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require("../PromiseRouter"); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require("../middlewares"); + +var middleware = _interopRequireWildcard(_middlewares); + +var _node = require("parse/node"); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var PushRouter = exports.PushRouter = function (_PromiseRouter) { + _inherits(PushRouter, _PromiseRouter); + + function PushRouter() { + _classCallCheck(this, PushRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(PushRouter).apply(this, arguments)); + } + + _createClass(PushRouter, [{ + key: "mountRoutes", + value: function mountRoutes() { + this.route("POST", "/push", middleware.promiseEnforceMasterKeyAccess, PushRouter.handlePOST); + } + }], [{ + key: "handlePOST", + value: function handlePOST(req) { + var pushController = req.config.pushController; + if (!pushController) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Push controller is not set'); + } + + var where = PushRouter.getQueryCondition(req); + var resolve = void 0; + var promise = new Promise(function (_resolve) { + resolve = _resolve; + }); + pushController.sendPush(req.body, where, req.config, req.auth, function (pushStatusId) { + resolve({ + headers: { + 'X-Parse-Push-Status-Id': pushStatusId + }, + response: { + result: true + } + }); + }); + return promise; + } + + /** + * Get query condition from the request body. + * @param {Object} req A request object + * @returns {Object} The query condition, the where field in a query api call + */ + + }, { + key: "getQueryCondition", + value: function getQueryCondition(req) { + var body = req.body || {}; + var hasWhere = typeof body.where !== 'undefined'; + var hasChannels = typeof body.channels !== 'undefined'; + + var where = void 0; + if (hasWhere && hasChannels) { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Channels and query can not be set at the same time.'); + } else if (hasWhere) { + where = body.where; + } else if (hasChannels) { + where = { + "channels": { + "$in": body.channels + } + }; + } else { + throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Sending a push requires either "channels" or a "where" query.'); + } + return where; + } + }]); + + return PushRouter; +}(_PromiseRouter3.default); + +exports.default = PushRouter; \ No newline at end of file diff --git a/lib/Routers/RolesRouter.js b/lib/Routers/RolesRouter.js new file mode 100644 index 0000000000..663099b76a --- /dev/null +++ b/lib/Routers/RolesRouter.js @@ -0,0 +1,97 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.RolesRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _ClassesRouter2 = require('./ClassesRouter'); + +var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); + +var _PromiseRouter = require('../PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var RolesRouter = exports.RolesRouter = function (_ClassesRouter) { + _inherits(RolesRouter, _ClassesRouter); + + function RolesRouter() { + _classCallCheck(this, RolesRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(RolesRouter).apply(this, arguments)); + } + + _createClass(RolesRouter, [{ + key: 'handleFind', + value: function handleFind(req) { + req.params.className = '_Role'; + return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleFind', this).call(this, req); + } + }, { + key: 'handleGet', + value: function handleGet(req) { + req.params.className = '_Role'; + return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleGet', this).call(this, req); + } + }, { + key: 'handleCreate', + value: function handleCreate(req) { + req.params.className = '_Role'; + return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleCreate', this).call(this, req); + } + }, { + key: 'handleUpdate', + value: function handleUpdate(req) { + req.params.className = '_Role'; + return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleUpdate', this).call(this, req); + } + }, { + key: 'handleDelete', + value: function handleDelete(req) { + req.params.className = '_Role'; + return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleDelete', this).call(this, req); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/roles', function (req) { + return _this2.handleFind(req); + }); + this.route('GET', '/roles/:objectId', function (req) { + return _this2.handleGet(req); + }); + this.route('POST', '/roles', function (req) { + return _this2.handleCreate(req); + }); + this.route('PUT', '/roles/:objectId', function (req) { + return _this2.handleUpdate(req); + }); + this.route('DELETE', '/roles/:objectId', function (req) { + return _this2.handleDelete(req); + }); + } + }]); + + return RolesRouter; +}(_ClassesRouter3.default); + +exports.default = RolesRouter; \ No newline at end of file diff --git a/lib/Routers/SchemasRouter.js b/lib/Routers/SchemasRouter.js new file mode 100644 index 0000000000..0eaedc4640 --- /dev/null +++ b/lib/Routers/SchemasRouter.js @@ -0,0 +1,126 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SchemasRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _PromiseRouter2 = require('../PromiseRouter'); + +var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); + +var _middlewares = require('../middlewares'); + +var middleware = _interopRequireWildcard(_middlewares); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +// schemas.js + +var express = require('express'), + Parse = require('parse/node').Parse, + SchemaController = require('../Controllers/SchemaController'); + +function classNameMismatchResponse(bodyClass, pathClass) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class name mismatch between ' + bodyClass + ' and ' + pathClass + '.'); +} + +function getAllSchemas(req) { + return req.config.database.loadSchema({ clearCache: true }).then(function (schemaController) { + return schemaController.getAllClasses(true); + }).then(function (schemas) { + return { response: { results: schemas } }; + }); +} + +function getOneSchema(req) { + var className = req.params.className; + return req.config.database.loadSchema({ clearCache: true }).then(function (schemaController) { + return schemaController.getOneSchema(className, true); + }).then(function (schema) { + return { response: schema }; + }).catch(function (error) { + if (error === undefined) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' does not exist.'); + } else { + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.'); + } + }); +} + +function createSchema(req) { + if (req.params.className && req.body.className) { + if (req.params.className != req.body.className) { + return classNameMismatchResponse(req.body.className, req.params.className); + } + } + + var className = req.params.className || req.body.className; + if (!className) { + throw new Parse.Error(135, 'POST ' + req.path + ' needs a class name.'); + } + + return req.config.database.loadSchema({ clearCache: true }).then(function (schema) { + return schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions); + }).then(function (schema) { + return { response: schema }; + }); +} + +function modifySchema(req) { + if (req.body.className && req.body.className != req.params.className) { + return classNameMismatchResponse(req.body.className, req.params.className); + } + + var submittedFields = req.body.fields || {}; + var className = req.params.className; + + return req.config.database.loadSchema({ clearCache: true }).then(function (schema) { + return schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database); + }).then(function (result) { + return { response: result }; + }); +} + +var deleteSchema = function deleteSchema(req) { + if (!SchemaController.classNameIsValid(req.params.className)) { + throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, SchemaController.invalidClassNameMessage(req.params.className)); + } + return req.config.database.deleteSchema(req.params.className).then(function () { + return { response: {} }; + }); +}; + +var SchemasRouter = exports.SchemasRouter = function (_PromiseRouter) { + _inherits(SchemasRouter, _PromiseRouter); + + function SchemasRouter() { + _classCallCheck(this, SchemasRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(SchemasRouter).apply(this, arguments)); + } + + _createClass(SchemasRouter, [{ + key: 'mountRoutes', + value: function mountRoutes() { + this.route('GET', '/schemas', middleware.promiseEnforceMasterKeyAccess, getAllSchemas); + this.route('GET', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, getOneSchema); + this.route('POST', '/schemas', middleware.promiseEnforceMasterKeyAccess, createSchema); + this.route('POST', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, createSchema); + this.route('PUT', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, modifySchema); + this.route('DELETE', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, deleteSchema); + } + }]); + + return SchemasRouter; +}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/SessionsRouter.js b/lib/Routers/SessionsRouter.js new file mode 100644 index 0000000000..48c932f0be --- /dev/null +++ b/lib/Routers/SessionsRouter.js @@ -0,0 +1,120 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SessionsRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _ClassesRouter2 = require('./ClassesRouter'); + +var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); + +var _PromiseRouter = require('../PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +var _Auth = require('../Auth'); + +var _Auth2 = _interopRequireDefault(_Auth); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var SessionsRouter = exports.SessionsRouter = function (_ClassesRouter) { + _inherits(SessionsRouter, _ClassesRouter); + + function SessionsRouter() { + _classCallCheck(this, SessionsRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(SessionsRouter).apply(this, arguments)); + } + + _createClass(SessionsRouter, [{ + key: 'handleFind', + value: function handleFind(req) { + req.params.className = '_Session'; + return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleFind', this).call(this, req); + } + }, { + key: 'handleGet', + value: function handleGet(req) { + req.params.className = '_Session'; + return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleGet', this).call(this, req); + } + }, { + key: 'handleCreate', + value: function handleCreate(req) { + req.params.className = '_Session'; + return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleCreate', this).call(this, req); + } + }, { + key: 'handleUpdate', + value: function handleUpdate(req) { + req.params.className = '_Session'; + return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleUpdate', this).call(this, req); + } + }, { + key: 'handleDelete', + value: function handleDelete(req) { + req.params.className = '_Session'; + return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleDelete', this).call(this, req); + } + }, { + key: 'handleMe', + value: function handleMe(req) { + // TODO: Verify correct behavior + if (!req.info || !req.info.sessionToken) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); + } + return _rest2.default.find(req.config, _Auth2.default.master(req.config), '_Session', { sessionToken: req.info.sessionToken }, undefined, req.info.clientSDK).then(function (response) { + if (!response.results || response.results.length == 0) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.'); + } + return { + response: response.results[0] + }; + }); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/sessions/me', function (req) { + return _this2.handleMe(req); + }); + this.route('GET', '/sessions', function (req) { + return _this2.handleFind(req); + }); + this.route('GET', '/sessions/:objectId', function (req) { + return _this2.handleGet(req); + }); + this.route('POST', '/sessions', function (req) { + return _this2.handleCreate(req); + }); + this.route('PUT', '/sessions/:objectId', function (req) { + return _this2.handleUpdate(req); + }); + this.route('DELETE', '/sessions/:objectId', function (req) { + return _this2.handleDelete(req); + }); + } + }]); + + return SessionsRouter; +}(_ClassesRouter3.default); + +exports.default = SessionsRouter; \ No newline at end of file diff --git a/lib/Routers/UsersRouter.js b/lib/Routers/UsersRouter.js new file mode 100644 index 0000000000..aff588d827 --- /dev/null +++ b/lib/Routers/UsersRouter.js @@ -0,0 +1,284 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.UsersRouter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + +var _deepcopy = require('deepcopy'); + +var _deepcopy2 = _interopRequireDefault(_deepcopy); + +var _Config = require('../Config'); + +var _Config2 = _interopRequireDefault(_Config); + +var _ClassesRouter2 = require('./ClassesRouter'); + +var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); + +var _PromiseRouter = require('../PromiseRouter'); + +var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); + +var _rest = require('../rest'); + +var _rest2 = _interopRequireDefault(_rest); + +var _Auth = require('../Auth'); + +var _Auth2 = _interopRequireDefault(_Auth); + +var _password = require('../password'); + +var _password2 = _interopRequireDefault(_password); + +var _RestWrite = require('../RestWrite'); + +var _RestWrite2 = _interopRequireDefault(_RestWrite); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // These methods handle the User-related routes. + +var cryptoUtils = require('../cryptoUtils'); +var triggers = require('../triggers'); + +var UsersRouter = exports.UsersRouter = function (_ClassesRouter) { + _inherits(UsersRouter, _ClassesRouter); + + function UsersRouter() { + _classCallCheck(this, UsersRouter); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(UsersRouter).apply(this, arguments)); + } + + _createClass(UsersRouter, [{ + key: 'handleFind', + value: function handleFind(req) { + req.params.className = '_User'; + return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleFind', this).call(this, req); + } + }, { + key: 'handleGet', + value: function handleGet(req) { + req.params.className = '_User'; + return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleGet', this).call(this, req); + } + }, { + key: 'handleCreate', + value: function handleCreate(req) { + var data = (0, _deepcopy2.default)(req.body); + req.body = data; + req.params.className = '_User'; + + return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleCreate', this).call(this, req); + } + }, { + key: 'handleUpdate', + value: function handleUpdate(req) { + req.params.className = '_User'; + return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleUpdate', this).call(this, req); + } + }, { + key: 'handleDelete', + value: function handleDelete(req) { + req.params.className = '_User'; + return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleDelete', this).call(this, req); + } + }, { + key: 'handleMe', + value: function handleMe(req) { + if (!req.info || !req.info.sessionToken) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); + } + var sessionToken = req.info.sessionToken; + return _rest2.default.find(req.config, _Auth2.default.master(req.config), '_Session', { sessionToken: sessionToken }, { include: 'user' }, req.info.clientSDK).then(function (response) { + if (!response.results || response.results.length == 0 || !response.results[0].user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); + } else { + var user = response.results[0].user; + // Send token back on the login, because SDKs expect that. + user.sessionToken = sessionToken; + return { response: user }; + } + }); + } + }, { + key: 'handleLogIn', + value: function handleLogIn(req) { + // Use query parameters instead if provided in url + if (!req.body.username && req.query.username) { + req.body = req.query; + } + + // TODO: use the right error codes / descriptions. + if (!req.body.username) { + throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'username is required.'); + } + if (!req.body.password) { + throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required.'); + } + + var user = void 0; + return req.config.database.find('_User', { username: req.body.username }).then(function (results) { + if (!results.length) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + } + user = results[0]; + + if (req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified) { + throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + } + + return _password2.default.compare(req.body.password, user.password); + }).then(function (correct) { + + if (!correct) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + } + + var token = 'r:' + cryptoUtils.newToken(); + user.sessionToken = token; + delete user.password; + + // Sometimes the authData still has null on that keys + // https://github.com/ParsePlatform/parse-server/issues/935 + if (user.authData) { + Object.keys(user.authData).forEach(function (provider) { + if (user.authData[provider] === null) { + delete user.authData[provider]; + } + }); + if (Object.keys(user.authData).length == 0) { + delete user.authData; + } + } + + req.config.filesController.expandFilesInObject(req.config, user); + + var expiresAt = req.config.generateSessionExpiresAt(); + var sessionData = { + sessionToken: token, + user: { + __type: 'Pointer', + className: '_User', + objectId: user.objectId + }, + createdWith: { + 'action': 'login', + 'authProvider': 'password' + }, + restricted: false, + expiresAt: Parse._encode(expiresAt) + }; + + if (req.info.installationId) { + sessionData.installationId = req.info.installationId; + } + + var create = new _RestWrite2.default(req.config, _Auth2.default.master(req.config), '_Session', null, sessionData); + return create.execute(); + }).then(function () { + return { response: user }; + }); + } + }, { + key: 'handleLogOut', + value: function handleLogOut(req) { + var success = { response: {} }; + if (req.info && req.info.sessionToken) { + return _rest2.default.find(req.config, _Auth2.default.master(req.config), '_Session', { sessionToken: req.info.sessionToken }, undefined, req.info.clientSDK).then(function (records) { + if (records.results && records.results.length) { + return _rest2.default.del(req.config, _Auth2.default.master(req.config), '_Session', records.results[0].objectId).then(function () { + return Promise.resolve(success); + }); + } + return Promise.resolve(success); + }); + } + return Promise.resolve(success); + } + }, { + key: 'handleResetRequest', + value: function handleResetRequest(req) { + try { + _Config2.default.validateEmailConfiguration({ + emailAdapter: req.config.userController.adapter, + appName: req.config.appName, + publicServerURL: req.config.publicServerURL, + emailVerifyTokenValidityDuration: req.config.emailVerifyTokenValidityDuration + }); + } catch (e) { + if (typeof e === 'string') { + // Maybe we need a Bad Configuration error, but the SDKs won't understand it. For now, Internal Server Error. + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'An appName, publicServerURL, and emailAdapter are required for password reset functionality.'); + } else { + throw e; + } + } + var email = req.body.email; + + if (!email) { + throw new Parse.Error(Parse.Error.EMAIL_MISSING, "you must provide an email"); + } + var userController = req.config.userController; + return userController.sendPasswordResetEmail(email).then(function (token) { + return Promise.resolve({ + response: {} + }); + }, function (err) { + if (err.code === Parse.Error.OBJECT_NOT_FOUND) { + throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'No user found with email ' + email + '.'); + } else { + throw err; + } + }); + } + }, { + key: 'mountRoutes', + value: function mountRoutes() { + var _this2 = this; + + this.route('GET', '/users', function (req) { + return _this2.handleFind(req); + }); + this.route('POST', '/users', function (req) { + return _this2.handleCreate(req); + }); + this.route('GET', '/users/me', function (req) { + return _this2.handleMe(req); + }); + this.route('GET', '/users/:objectId', function (req) { + return _this2.handleGet(req); + }); + this.route('PUT', '/users/:objectId', function (req) { + return _this2.handleUpdate(req); + }); + this.route('DELETE', '/users/:objectId', function (req) { + return _this2.handleDelete(req); + }); + this.route('GET', '/login', function (req) { + return _this2.handleLogIn(req); + }); + this.route('POST', '/logout', function (req) { + return _this2.handleLogOut(req); + }); + this.route('POST', '/requestPasswordReset', function (req) { + return _this2.handleResetRequest(req); + }); + } + }]); + + return UsersRouter; +}(_ClassesRouter3.default); + +exports.default = UsersRouter; \ No newline at end of file diff --git a/lib/TestUtils.js b/lib/TestUtils.js new file mode 100644 index 0000000000..8ddd7759ee --- /dev/null +++ b/lib/TestUtils.js @@ -0,0 +1,21 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _DatabaseAdapter = require('./DatabaseAdapter'); + +var unsupported = function unsupported() { + throw 'Only supported in test environment'; +}; + +var _destroyAllDataPermanently = void 0; +if (process.env.TESTING) { + _destroyAllDataPermanently = _DatabaseAdapter.destroyAllDataPermanently; +} else { + _destroyAllDataPermanently = unsupported; +} + +exports.default = { + destroyAllDataPermanently: _destroyAllDataPermanently }; \ No newline at end of file diff --git a/lib/authDataManager/OAuth1Client.js b/lib/authDataManager/OAuth1Client.js new file mode 100644 index 0000000000..afdb8caf7b --- /dev/null +++ b/lib/authDataManager/OAuth1Client.js @@ -0,0 +1,220 @@ +'use strict'; + +var https = require('https'), + crypto = require('crypto'); + +var OAuth = function OAuth(options) { + this.consumer_key = options.consumer_key; + this.consumer_secret = options.consumer_secret; + this.auth_token = options.auth_token; + this.auth_token_secret = options.auth_token_secret; + this.host = options.host; + this.oauth_params = options.oauth_params || {}; +}; + +OAuth.prototype.send = function (method, path, params, body) { + + var request = this.buildRequest(method, path, params, body); + // Encode the body properly, the current Parse Implementation don't do it properly + return new Promise(function (resolve, reject) { + var httpRequest = https.request(request, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to make an OAuth request'); + }); + if (request.body) { + httpRequest.write(request.body); + } + httpRequest.end(); + }); +}; + +OAuth.prototype.buildRequest = function (method, path, params, body) { + if (path.indexOf("/") != 0) { + path = "/" + path; + } + if (params && Object.keys(params).length > 0) { + path += "?" + OAuth.buildParameterString(params); + } + + var request = { + host: this.host, + path: path, + method: method.toUpperCase() + }; + + var oauth_params = this.oauth_params || {}; + oauth_params.oauth_consumer_key = this.consumer_key; + if (this.auth_token) { + oauth_params["oauth_token"] = this.auth_token; + } + + request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret); + + if (body && Object.keys(body).length > 0) { + request.body = OAuth.buildParameterString(body); + } + return request; +}; + +OAuth.prototype.get = function (path, params) { + return this.send("GET", path, params); +}; + +OAuth.prototype.post = function (path, params, body) { + return this.send("POST", path, params, body); +}; + +/* + Proper string %escape encoding +*/ +OAuth.encode = function (str) { + // discuss at: http://phpjs.org/functions/rawurlencode/ + // original by: Brett Zamir (http://brett-zamir.me) + // input by: travc + // input by: Brett Zamir (http://brett-zamir.me) + // input by: Michael Grier + // input by: Ratheous + // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // bugfixed by: Brett Zamir (http://brett-zamir.me) + // bugfixed by: Joris + // reimplemented by: Brett Zamir (http://brett-zamir.me) + // reimplemented by: Brett Zamir (http://brett-zamir.me) + // note: This reflects PHP 5.3/6.0+ behavior + // note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on + // note: pages served as UTF-8 + // example 1: rawurlencode('Kevin van Zonneveld!'); + // returns 1: 'Kevin%20van%20Zonneveld%21' + // example 2: rawurlencode('http://kevin.vanzonneveld.net/'); + // returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F' + // example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a'); + // returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a' + + str = (str + '').toString(); + + // Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current + // PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following. + return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A'); +}; + +OAuth.signatureMethod = "HMAC-SHA1"; +OAuth.version = "1.0"; + +/* + Generate a nonce +*/ +OAuth.nonce = function () { + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (var i = 0; i < 30; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + }return text; +}; + +OAuth.buildParameterString = function (obj) { + var result = {}; + + // Sort keys and encode values + if (obj) { + var keys = Object.keys(obj).sort(); + + // Map key=value, join them by & + return keys.map(function (key) { + return key + "=" + OAuth.encode(obj[key]); + }).join("&"); + } + + return ""; +}; + +/* + Build the signature string from the object +*/ + +OAuth.buildSignatureString = function (method, url, parameters) { + return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join("&"); +}; + +/* + Retuns encoded HMAC-SHA1 from key and text +*/ +OAuth.signature = function (text, key) { + crypto = require("crypto"); + return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64')); +}; + +OAuth.signRequest = function (request, oauth_parameters, consumer_secret, auth_token_secret) { + oauth_parameters = oauth_parameters || {}; + + // Set default values + if (!oauth_parameters.oauth_nonce) { + oauth_parameters.oauth_nonce = OAuth.nonce(); + } + if (!oauth_parameters.oauth_timestamp) { + oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime() / 1000); + } + if (!oauth_parameters.oauth_signature_method) { + oauth_parameters.oauth_signature_method = OAuth.signatureMethod; + } + if (!oauth_parameters.oauth_version) { + oauth_parameters.oauth_version = OAuth.version; + } + + if (!auth_token_secret) { + auth_token_secret = ""; + } + // Force GET method if unset + if (!request.method) { + request.method = "GET"; + } + + // Collect all the parameters in one signatureParameters object + var signatureParams = {}; + var parametersToMerge = [request.params, request.body, oauth_parameters]; + for (var i in parametersToMerge) { + var parameters = parametersToMerge[i]; + for (var k in parameters) { + signatureParams[k] = parameters[k]; + } + } + + // Create a string based on the parameters + var parameterString = OAuth.buildParameterString(signatureParams); + + // Build the signature string + var url = "https://" + request.host + "" + request.path; + + var signatureString = OAuth.buildSignatureString(request.method, url, parameterString); + // Hash the signature string + var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join("&"); + + var signature = OAuth.signature(signatureString, signatureKey); + + // Set the signature in the params + oauth_parameters.oauth_signature = signature; + if (!request.headers) { + request.headers = {}; + } + + // Set the authorization header + var signature = Object.keys(oauth_parameters).sort().map(function (key) { + var value = oauth_parameters[key]; + return key + '="' + value + '"'; + }).join(", "); + + request.headers.Authorization = 'OAuth ' + signature; + + // Set the content type header + request.headers["Content-Type"] = "application/x-www-form-urlencoded"; + return request; +}; + +module.exports = OAuth; \ No newline at end of file diff --git a/lib/authDataManager/facebook.js b/lib/authDataManager/facebook.js new file mode 100644 index 0000000000..6e3c2ae97b --- /dev/null +++ b/lib/authDataManager/facebook.js @@ -0,0 +1,52 @@ +'use strict'; + +// Helper functions for accessing the Facebook Graph API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return graphRequest('me?fields=id&access_token=' + authData.access_token).then(function (data) { + if (data && data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId(appIds, authData) { + var access_token = authData.access_token; + if (!appIds.length) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is not configured.'); + } + return graphRequest('app?access_token=' + access_token).then(function (data) { + if (data && appIds.indexOf(data.id) != -1) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); + }); +} + +// A promisey wrapper for FB graph requests. +function graphRequest(path) { + return new Promise(function (resolve, reject) { + https.get('https://graph.facebook.com/v2.5/' + path, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Facebook.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/github.js b/lib/authDataManager/github.js new file mode 100644 index 0000000000..6d8a13c298 --- /dev/null +++ b/lib/authDataManager/github.js @@ -0,0 +1,50 @@ +'use strict'; + +// Helper functions for accessing the github API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return request('user', authData.access_token).then(function (data) { + if (data && data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Github auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(path, access_token) { + return new Promise(function (resolve, reject) { + https.get({ + host: 'api.github.com', + path: '/' + path, + headers: { + 'Authorization': 'bearer ' + access_token, + 'User-Agent': 'parse-server' + } + }, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Github.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/google.js b/lib/authDataManager/google.js new file mode 100644 index 0000000000..c2bff64beb --- /dev/null +++ b/lib/authDataManager/google.js @@ -0,0 +1,43 @@ +'use strict'; + +// Helper functions for accessing the google API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return request("tokeninfo?id_token=" + authData.access_token).then(function (response) { + if (response && response.sub == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Google auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(path) { + return new Promise(function (resolve, reject) { + https.get("https://www.googleapis.com/oauth2/v3/" + path, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Google.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/index.js b/lib/authDataManager/index.js new file mode 100644 index 0000000000..ef7bfbc3e3 --- /dev/null +++ b/lib/authDataManager/index.js @@ -0,0 +1,103 @@ +"use strict"; + +var facebook = require('./facebook'); +var instagram = require("./instagram"); +var linkedin = require("./linkedin"); +var meetup = require("./meetup"); +var google = require("./google"); +var github = require("./github"); +var twitter = require("./twitter"); +var spotify = require("./spotify"); +var digits = require("./twitter"); // digits tokens are validated by twitter + +var anonymous = { + validateAuthData: function validateAuthData() { + return Promise.resolve(); + }, + validateAppId: function validateAppId() { + return Promise.resolve(); + } +}; + +var providers = { + facebook: facebook, + instagram: instagram, + linkedin: linkedin, + meetup: meetup, + google: google, + github: github, + twitter: twitter, + spotify: spotify, + anonymous: anonymous, + digits: digits +}; + +module.exports = function () { + var oauthOptions = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var enableAnonymousUsers = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; + + var _enableAnonymousUsers = enableAnonymousUsers; + var setEnableAnonymousUsers = function setEnableAnonymousUsers(enable) { + _enableAnonymousUsers = enable; + }; + // To handle the test cases on configuration + var getValidatorForProvider = function getValidatorForProvider(provider) { + + if (provider === 'anonymous' && !_enableAnonymousUsers) { + return; + } + + var defaultProvider = providers[provider]; + var optionalProvider = oauthOptions[provider]; + + if (!defaultProvider && !optionalProvider) { + return; + } + + var appIds = void 0; + if (optionalProvider) { + appIds = optionalProvider.appIds; + } + + var validateAuthData; + var validateAppId; + + if (defaultProvider) { + validateAuthData = defaultProvider.validateAuthData; + validateAppId = defaultProvider.validateAppId; + } + + // Try the configuration methods + if (optionalProvider) { + if (optionalProvider.module) { + validateAuthData = require(optionalProvider.module).validateAuthData; + validateAppId = require(optionalProvider.module).validateAppId; + }; + + if (optionalProvider.validateAuthData) { + validateAuthData = optionalProvider.validateAuthData; + } + if (optionalProvider.validateAppId) { + validateAppId = optionalProvider.validateAppId; + } + } + + if (!validateAuthData || !validateAppId) { + return; + } + + return function (authData) { + return validateAuthData(authData, optionalProvider).then(function () { + if (appIds) { + return validateAppId(appIds, authData, optionalProvider); + } + return Promise.resolve(); + }); + }; + }; + + return Object.freeze({ + getValidatorForProvider: getValidatorForProvider, + setEnableAnonymousUsers: setEnableAnonymousUsers + }); +}; \ No newline at end of file diff --git a/lib/authDataManager/instagram.js b/lib/authDataManager/instagram.js new file mode 100644 index 0000000000..fb22f73e61 --- /dev/null +++ b/lib/authDataManager/instagram.js @@ -0,0 +1,43 @@ +'use strict'; + +// Helper functions for accessing the instagram API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return request("users/self/?access_token=" + authData.access_token).then(function (response) { + if (response && response.data && response.data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Instagram auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(path) { + return new Promise(function (resolve, reject) { + https.get("https://api.instagram.com/v1/" + path, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Instagram.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/linkedin.js b/lib/authDataManager/linkedin.js new file mode 100644 index 0000000000..d4bc36c189 --- /dev/null +++ b/lib/authDataManager/linkedin.js @@ -0,0 +1,50 @@ +'use strict'; + +// Helper functions for accessing the linkedin API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return request('people/~:(id)', authData.access_token).then(function (data) { + if (data && data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(path, access_token) { + return new Promise(function (resolve, reject) { + https.get({ + host: 'api.linkedin.com', + path: '/v1/' + path, + headers: { + 'Authorization': 'Bearer ' + access_token, + 'x-li-format': 'json' + } + }, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Linkedin.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/meetup.js b/lib/authDataManager/meetup.js new file mode 100644 index 0000000000..7908a49d91 --- /dev/null +++ b/lib/authDataManager/meetup.js @@ -0,0 +1,49 @@ +'use strict'; + +// Helper functions for accessing the meetup API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return request('member/self', authData.access_token).then(function (data) { + if (data && data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(path, access_token) { + return new Promise(function (resolve, reject) { + https.get({ + host: 'api.meetup.com', + path: '/2/' + path, + headers: { + 'Authorization': 'bearer ' + access_token + } + }, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Meetup.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/spotify.js b/lib/authDataManager/spotify.js new file mode 100644 index 0000000000..7f13954349 --- /dev/null +++ b/lib/authDataManager/spotify.js @@ -0,0 +1,58 @@ +'use strict'; + +// Helper functions for accessing the Spotify API. +var https = require('https'); +var Parse = require('parse/node').Parse; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData) { + return request('me', authData.access_token).then(function (data) { + if (data && data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills if this app id is valid. +function validateAppId(appIds, authData) { + var access_token = authData.access_token; + if (!appIds.length) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is not configured.'); + } + return request('me', access_token).then(function (data) { + if (data && appIds.indexOf(data.id) != -1) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.'); + }); +} + +// A promisey wrapper for Spotify API requests. +function request(path, access_token) { + return new Promise(function (resolve, reject) { + https.get({ + host: 'api.spotify.com', + path: '/v1/' + path, + headers: { + 'Authorization': 'Bearer ' + access_token + } + }, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }).on('error', function (e) { + reject('Failed to validate this access token with Spotify.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; \ No newline at end of file diff --git a/lib/authDataManager/twitter.js b/lib/authDataManager/twitter.js new file mode 100644 index 0000000000..a23c7925cf --- /dev/null +++ b/lib/authDataManager/twitter.js @@ -0,0 +1,55 @@ +'use strict'; + +// Helper functions for accessing the twitter API. +var OAuth = require('./OAuth1Client'); +var Parse = require('parse/node').Parse; +var logger = require('../logger').default; + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData, options) { + options = handleMultipleConfigurations(authData, options); + var client = new OAuth(options); + client.host = "api.twitter.com"; + client.auth_token = authData.auth_token; + client.auth_token_secret = authData.auth_token_secret; + + return client.get("/1.1/account/verify_credentials.json").then(function (data) { + if (data && data.id == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + return Promise.resolve(); +} + +function handleMultipleConfigurations(authData, options) { + if (Array.isArray(options)) { + (function () { + var consumer_key = authData.consumer_key; + if (!consumer_key) { + logger.error('Twitter Auth', 'Multiple twitter configurations are available, by no consumer_key was sent by the client.'); + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); + } + options = options.filter(function (option) { + return option.consumer_key == consumer_key; + }); + + if (options.length == 0) { + logger.error('Twitter Auth', 'Cannot find a configuration for the provided consumer_key'); + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); + } + options = options[0]; + })(); + } + return options; +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData, + handleMultipleConfigurations: handleMultipleConfigurations +}; \ No newline at end of file diff --git a/lib/batch.js b/lib/batch.js new file mode 100644 index 0000000000..85554bac69 --- /dev/null +++ b/lib/batch.js @@ -0,0 +1,91 @@ +'use strict'; + +var Parse = require('parse/node').Parse; + +// These methods handle batch requests. +var batchPath = '/batch'; + +// Mounts a batch-handler onto a PromiseRouter. +function mountOnto(router) { + router.route('POST', batchPath, function (req) { + return handleBatch(router, req); + }); +} + +// Returns a promise for a {response} object. +// TODO: pass along auth correctly +function handleBatch(router, req) { + if (!req.body.requests instanceof Array) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'requests must be an array'); + } + + // The batch paths are all from the root of our domain. + // That means they include the API prefix, that the API is mounted + // to. However, our promise router does not route the api prefix. So + // we need to figure out the API prefix, so that we can strip it + // from all the subrequests. + if (!req.originalUrl.endsWith(batchPath)) { + throw 'internal routing problem - expected url to end with batch'; + } + var apiPrefixLength = req.originalUrl.length - batchPath.length; + var apiPrefix = req.originalUrl.slice(0, apiPrefixLength); + + var promises = []; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = req.body.requests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var restRequest = _step.value; + + // The routablePath is the path minus the api prefix + if (restRequest.path.slice(0, apiPrefixLength) != apiPrefix) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route batch path ' + restRequest.path); + } + var routablePath = restRequest.path.slice(apiPrefixLength); + + // Use the router to figure out what handler to use + var match = router.match(restRequest.method, routablePath); + if (!match) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route ' + restRequest.method + ' ' + routablePath); + } + + // Construct a request that we can send to a handler + var request = { + body: restRequest.body, + params: match.params, + config: req.config, + auth: req.auth, + info: req.info + }; + + promises.push(match.handler(request).then(function (response) { + return { success: response.response }; + }, function (error) { + return { error: { code: error.code, error: error.message } }; + })); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return Promise.all(promises).then(function (results) { + return { response: results }; + }); +} + +module.exports = { + mountOnto: mountOnto +}; \ No newline at end of file diff --git a/lib/cache.js b/lib/cache.js new file mode 100644 index 0000000000..51641150c3 --- /dev/null +++ b/lib/cache.js @@ -0,0 +1,11 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.AppCache = undefined; + +var _InMemoryCache = require('./Adapters/Cache/InMemoryCache'); + +var AppCache = exports.AppCache = new _InMemoryCache.InMemoryCache({ ttl: NaN }); +exports.default = AppCache; \ No newline at end of file diff --git a/lib/cli/cli-definitions.js b/lib/cli/cli-definitions.js new file mode 100644 index 0000000000..925168d383 --- /dev/null +++ b/lib/cli/cli-definitions.js @@ -0,0 +1,214 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +function numberParser(key) { + return function (opt) { + opt = parseInt(opt); + if (!Number.isInteger(opt)) { + throw new Error('The ' + key + ' is invalid'); + } + return opt; + }; +} + +function objectParser(opt) { + if ((typeof opt === 'undefined' ? 'undefined' : _typeof(opt)) == 'object') { + return opt; + } + return JSON.parse(opt); +} + +function moduleOrObjectParser(opt) { + if ((typeof opt === 'undefined' ? 'undefined' : _typeof(opt)) == 'object') { + return opt; + } + try { + return JSON.parse(opt); + } catch (e) {} + return opt; +} + +function booleanParser(opt) { + if (opt == true || opt == "true" || opt == "1") { + return true; + } + return false; +} + +exports.default = { + "appId": { + env: "PARSE_SERVER_APPLICATION_ID", + help: "Your Parse Application ID", + required: true + }, + "masterKey": { + env: "PARSE_SERVER_MASTER_KEY", + help: "Your Parse Master Key", + required: true + }, + "port": { + env: "PORT", + help: "The port to run the ParseServer. defaults to 1337.", + default: 1337, + action: numberParser("port") + }, + "databaseURI": { + env: "PARSE_SERVER_DATABASE_URI", + help: "The full URI to your mongodb database" + }, + "databaseOptions": { + env: "PARSE_SERVER_DATABASE_OPTIONS", + help: "Options to pass to the mongodb client", + action: objectParser + }, + "collectionPrefix": { + env: "PARSE_SERVER_COLLECTION_PREFIX", + help: 'A collection prefix for the classes' + }, + "serverURL": { + env: "PARSE_SERVER_URL", + help: "URL to your parse server with http:// or https://." + }, + "publicServerURL": { + env: "PARSE_PUBLIC_SERVER_URL", + help: "Public URL to your parse server with http:// or https://." + }, + "clientKey": { + env: "PARSE_SERVER_CLIENT_KEY", + help: "Key for iOS, MacOS, tvOS clients" + }, + "javascriptKey": { + env: "PARSE_SERVER_JAVASCRIPT_KEY", + help: "Key for the Javascript SDK" + }, + "restAPIKey": { + env: "PARSE_SERVER_REST_API_KEY", + help: "Key for REST calls" + }, + "dotNetKey": { + env: "PARSE_SERVER_DOT_NET_KEY", + help: "Key for Unity and .Net SDK" + }, + "webhookKey": { + env: "PARSE_SERVER_WEBHOOK_KEY", + help: "Key sent with outgoing webhook calls" + }, + "cloud": { + env: "PARSE_SERVER_CLOUD_CODE_MAIN", + help: "Full path to your cloud code main.js" + }, + "push": { + env: "PARSE_SERVER_PUSH", + help: "Configuration for push, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Push", + action: objectParser + }, + "oauth": { + env: "PARSE_SERVER_OAUTH_PROVIDERS", + help: "Configuration for your oAuth providers, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#oauth", + action: objectParser + }, + "fileKey": { + env: "PARSE_SERVER_FILE_KEY", + help: "Key for your files" + }, + "facebookAppIds": { + env: "PARSE_SERVER_FACEBOOK_APP_IDS", + help: "Comma separated list for your facebook app Ids", + type: "list", + action: function action(opt) { + return opt.split(","); + } + }, + "enableAnonymousUsers": { + env: "PARSE_SERVER_ENABLE_ANON_USERS", + help: "Enable (or disable) anon users, defaults to true", + action: booleanParser + }, + "allowClientClassCreation": { + env: "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION", + help: "Enable (or disable) client class creation, defaults to true", + action: booleanParser + }, + "mountPath": { + env: "PARSE_SERVER_MOUNT_PATH", + help: "Mount path for the server, defaults to /parse", + default: "/parse" + }, + "filesAdapter": { + env: "PARSE_SERVER_FILES_ADAPTER", + help: "Adapter module for the files sub-system", + action: moduleOrObjectParser + }, + "emailAdapter": { + env: "PARSE_SERVER_EMAIL_ADAPTER", + help: "Adapter module for the email sending", + action: moduleOrObjectParser + }, + "verifyUserEmails": { + env: "PARSE_SERVER_VERIFY_USER_EMAILS", + help: "Enable (or disable) user email validation, defaults to false", + action: booleanParser + }, + "preventLoginWithUnverifiedEmail": { + env: "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL", + help: "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false", + action: booleanParser + }, + "emailVerifyTokenValidityDuration": { + env: "PARSE_SERVER_EMAIL_VERIFY_TOKEN_VALIDITY_DURATION", + help: "Email verification token validity duration", + action: numberParser("emailVerifyTokenValidityDuration") + }, + "appName": { + env: "PARSE_SERVER_APP_NAME", + help: "Sets the app name" + }, + "loggerAdapter": { + env: "PARSE_SERVER_LOGGER_ADAPTER", + help: "Adapter module for the logging sub-system", + action: moduleOrObjectParser + }, + "liveQuery": { + env: "PARSE_SERVER_LIVE_QUERY_OPTIONS", + help: "liveQuery options", + action: objectParser + }, + "customPages": { + env: "PARSE_SERVER_CUSTOM_PAGES", + help: "custom pages for pasword validation and reset", + action: objectParser + }, + "maxUploadSize": { + env: "PARSE_SERVER_MAX_UPLOAD_SIZE", + help: "Max file size for uploads.", + default: "20mb" + }, + "sessionLength": { + env: "PARSE_SERVER_SESSION_LENGTH", + help: "Session duration, defaults to 1 year", + action: numberParser("sessionLength") + }, + "verbose": { + env: "VERBOSE", + help: "Set the logging to verbose" + }, + "jsonLogs": { + env: "JSON_LOGS", + help: "Log as structured JSON objects" + }, + "revokeSessionOnPasswordReset": { + env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET", + help: "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.", + action: booleanParser + }, + "schemaCacheTTL": { + env: "PARSE_SERVER_SCHEMA_CACHE_TTL", + help: "The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 0; disabled.", + action: numberParser("schemaCacheTTL") + } +}; \ No newline at end of file diff --git a/lib/cli/parse-server.js b/lib/cli/parse-server.js new file mode 100644 index 0000000000..acd9a20a47 --- /dev/null +++ b/lib/cli/parse-server.js @@ -0,0 +1,93 @@ +'use strict'; + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _express = require('express'); + +var _express2 = _interopRequireDefault(_express); + +var _index = require('../index'); + +var _cliDefinitions = require('./cli-definitions'); + +var _cliDefinitions2 = _interopRequireDefault(_cliDefinitions); + +var _commander = require('./utils/commander'); + +var _commander2 = _interopRequireDefault(_commander); + +var _colors = require('colors'); + +var _colors2 = _interopRequireDefault(_colors); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +_commander2.default.loadDefinitions(_cliDefinitions2.default); + +_commander2.default.usage('[options] '); + +_commander2.default.on('--help', function () { + console.log(' Get Started guide:'); + console.log(''); + console.log(' Please have a look at the get started guide!'); + console.log(' https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide'); + console.log(''); + console.log(''); + console.log(' Usage with npm start'); + console.log(''); + console.log(' $ npm start -- path/to/config.json'); + console.log(' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); + console.log(' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); + console.log(''); + console.log(''); + console.log(' Usage:'); + console.log(''); + console.log(' $ parse-server path/to/config.json'); + console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); + console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); + console.log(''); +}); + +_commander2.default.parse(process.argv, process.env); + +var options = _commander2.default.getOptions(); + +if (!options.serverURL) { + options.serverURL = 'http://localhost:' + options.port + options.mountPath; +} + +if (!options.appId || !options.masterKey || !options.serverURL) { + _commander2.default.outputHelp(); + console.error(""); + console.error(_colors2.default.red("ERROR: appId and masterKey are required")); + console.error(""); + process.exit(1); +} + +var app = (0, _express2.default)(); +var api = new _index.ParseServer(options); +app.use(options.mountPath, api); + +var server = app.listen(options.port, function () { + + for (var key in options) { + var value = options[key]; + if (key == "masterKey") { + value = "***REDACTED***"; + } + console.log(key + ': ' + value); + } + console.log(''); + console.log('parse-server running on ' + options.serverURL); +}); + +var handleShutdown = function handleShutdown() { + console.log('Termination signal received. Shutting down.'); + server.close(function () { + process.exit(0); + }); +}; +process.on('SIGTERM', handleShutdown); +process.on('SIGINT', handleShutdown); \ No newline at end of file diff --git a/lib/cli/utils/commander.js b/lib/cli/utils/commander.js new file mode 100644 index 0000000000..0935290b52 --- /dev/null +++ b/lib/cli/utils/commander.js @@ -0,0 +1,148 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _commander = require('commander'); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var _definitions = void 0; +var _reverseDefinitions = void 0; +var _defaults = void 0; + +_commander.Command.prototype.loadDefinitions = function (definitions) { + _definitions = definitions; + + Object.keys(definitions).reduce(function (program, opt) { + if (_typeof(definitions[opt]) == "object") { + var additionalOptions = definitions[opt]; + if (additionalOptions.required === true) { + return program.option('--' + opt + ' <' + opt + '>', additionalOptions.help, additionalOptions.action); + } else { + return program.option('--' + opt + ' [' + opt + ']', additionalOptions.help, additionalOptions.action); + } + } + return program.option('--' + opt + ' [' + opt + ']'); + }, this); + + _defaults = Object.keys(definitions).reduce(function (defs, opt) { + if (_definitions[opt].default) { + defs[opt] = _definitions[opt].default; + } + return defs; + }, {}); + + _reverseDefinitions = Object.keys(definitions).reduce(function (object, key) { + var value = definitions[key]; + if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) == "object") { + value = value.env; + } + if (value) { + object[value] = key; + } + return object; + }, {}); + + /* istanbul ignore next */ + this.on('--help', function () { + console.log(' Configure From Environment:'); + console.log(''); + Object.keys(_reverseDefinitions).forEach(function (key) { + console.log(' $ ' + key + '=\'' + _reverseDefinitions[key] + '\''); + }); + console.log(''); + }); +}; + +function parseEnvironment() { + var env = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + return Object.keys(_reverseDefinitions).reduce(function (options, key) { + if (env[key]) { + var originalKey = _reverseDefinitions[key]; + var action = function action(option) { + return option; + }; + if (_typeof(_definitions[originalKey]) === "object") { + action = _definitions[originalKey].action || action; + } + options[_reverseDefinitions[key]] = action(env[key]); + } + return options; + }, {}); +} + +function parseConfigFile(program) { + var options = {}; + if (program.args.length > 0) { + var jsonPath = program.args[0]; + jsonPath = _path2.default.resolve(jsonPath); + var jsonConfig = require(jsonPath); + if (jsonConfig.apps) { + if (jsonConfig.apps.length > 1) { + throw 'Multiple apps are not supported'; + } + options = jsonConfig.apps[0]; + } else { + options = jsonConfig; + } + Object.keys(options).forEach(function (key) { + var value = options[key]; + if (!_definitions[key]) { + throw 'error: unknown option ' + key; + } + var action = _definitions[key].action; + if (action) { + options[key] = action(value); + } + }); + console.log('Configuration loaded from ' + jsonPath); + } + return options; +} + +_commander.Command.prototype.setValuesIfNeeded = function (options) { + var _this = this; + + Object.keys(options).forEach(function (key) { + if (!_this[key]) { + _this[key] = options[key]; + } + }); +}; + +_commander.Command.prototype._parse = _commander.Command.prototype.parse; + +_commander.Command.prototype.parse = function (args, env) { + this._parse(args); + // Parse the environment first + var envOptions = parseEnvironment(env); + var fromFile = parseConfigFile(this); + // Load the env if not passed from command line + this.setValuesIfNeeded(envOptions); + // Load from file to override + this.setValuesIfNeeded(fromFile); + // Last set the defaults + this.setValuesIfNeeded(_defaults); +}; + +_commander.Command.prototype.getOptions = function () { + var _this2 = this; + + return Object.keys(_definitions).reduce(function (options, key) { + if (typeof _this2[key] !== 'undefined') { + options[key] = _this2[key]; + } + return options; + }, {}); +}; + +exports.default = new _commander.Command(); \ No newline at end of file diff --git a/lib/cloud-code/HTTPResponse.js b/lib/cloud-code/HTTPResponse.js new file mode 100644 index 0000000000..551a3621c1 --- /dev/null +++ b/lib/cloud-code/HTTPResponse.js @@ -0,0 +1,65 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var HTTPResponse = function HTTPResponse(response, body) { + var _this = this; + + _classCallCheck(this, HTTPResponse); + + var _text = void 0, + _data = void 0; + this.status = response.statusCode; + this.headers = response.headers || {}; + this.cookies = this.headers["set-cookie"]; + + if (typeof body == 'string') { + _text = body; + } else if (Buffer.isBuffer(body)) { + this.buffer = body; + } else if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) == 'object') { + _data = body; + } + + var getText = function getText() { + if (!_text && _this.buffer) { + _text = _this.buffer.toString('utf-8'); + } else if (!_text && _data) { + _text = JSON.stringify(_data); + } + return _text; + }; + + var getData = function getData() { + if (!_data) { + try { + _data = JSON.parse(getText()); + } catch (e) {} + } + return _data; + }; + + Object.defineProperty(this, 'body', { + get: function get() { + return body; + } + }); + + Object.defineProperty(this, 'text', { + enumerable: true, + get: getText + }); + + Object.defineProperty(this, 'data', { + enumerable: true, + get: getData + }); +}; + +exports.default = HTTPResponse; \ No newline at end of file diff --git a/lib/cloud-code/Parse.Cloud.js b/lib/cloud-code/Parse.Cloud.js new file mode 100644 index 0000000000..437d3cb1ca --- /dev/null +++ b/lib/cloud-code/Parse.Cloud.js @@ -0,0 +1,62 @@ +'use strict'; + +var _node = require('parse/node'); + +var _triggers = require('../triggers'); + +var triggers = _interopRequireWildcard(_triggers); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function validateClassNameForTriggers(className) { + var restrictedClassNames = ['_Session']; + if (restrictedClassNames.indexOf(className) != -1) { + throw 'Triggers are not supported for ' + className + ' class.'; + } + return className; +} + +function getClassName(parseClass) { + if (parseClass && parseClass.className) { + return validateClassNameForTriggers(parseClass.className); + } + return validateClassNameForTriggers(parseClass); +} + +var ParseCloud = {}; +ParseCloud.define = function (functionName, handler, validationHandler) { + triggers.addFunction(functionName, handler, validationHandler, _node.Parse.applicationId); +}; + +ParseCloud.beforeSave = function (parseClass, handler) { + var className = getClassName(parseClass); + triggers.addTrigger(triggers.Types.beforeSave, className, handler, _node.Parse.applicationId); +}; + +ParseCloud.beforeDelete = function (parseClass, handler) { + var className = getClassName(parseClass); + triggers.addTrigger(triggers.Types.beforeDelete, className, handler, _node.Parse.applicationId); +}; + +ParseCloud.afterSave = function (parseClass, handler) { + var className = getClassName(parseClass); + triggers.addTrigger(triggers.Types.afterSave, className, handler, _node.Parse.applicationId); +}; + +ParseCloud.afterDelete = function (parseClass, handler) { + var className = getClassName(parseClass); + triggers.addTrigger(triggers.Types.afterDelete, className, handler, _node.Parse.applicationId); +}; + +ParseCloud._removeHook = function (category, name, type, applicationId) { + applicationId = applicationId || _node.Parse.applicationId; + triggers._unregister(applicationId, category, name, type); +}; + +ParseCloud._removeAllHooks = function () { + triggers._unregisterAll(); +}; + +ParseCloud.httpRequest = require("./httpRequest"); + +module.exports = ParseCloud; \ No newline at end of file diff --git a/lib/cloud-code/httpRequest.js b/lib/cloud-code/httpRequest.js new file mode 100644 index 0000000000..7f0136eed2 --- /dev/null +++ b/lib/cloud-code/httpRequest.js @@ -0,0 +1,107 @@ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _request = require('request'); + +var _request2 = _interopRequireDefault(_request); + +var _node = require('parse/node'); + +var _node2 = _interopRequireDefault(_node); + +var _HTTPResponse = require('./HTTPResponse'); + +var _HTTPResponse2 = _interopRequireDefault(_HTTPResponse); + +var _querystring = require('querystring'); + +var _querystring2 = _interopRequireDefault(_querystring); + +var _logger = require('../logger'); + +var _logger2 = _interopRequireDefault(_logger); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var encodeBody = function encodeBody(_ref) { + var body = _ref.body; + var _ref$headers = _ref.headers; + var headers = _ref$headers === undefined ? {} : _ref$headers; + + if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) !== 'object') { + return { body: body, headers: headers }; + } + var contentTypeKeys = Object.keys(headers).filter(function (key) { + return key.match(/content-type/i) != null; + }); + + if (contentTypeKeys.length == 0) { + // no content type + // As per https://parse.com/docs/cloudcode/guide#cloud-code-advanced-sending-a-post-request the default encoding is supposedly x-www-form-urlencoded + + body = _querystring2.default.stringify(body); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else { + /* istanbul ignore next */ + if (contentTypeKeys.length > 1) { + _logger2.default.error('Parse.Cloud.httpRequest', 'multiple content-type headers are set.'); + } + // There maybe many, we'll just take the 1st one + var contentType = contentTypeKeys[0]; + if (headers[contentType].match(/application\/json/i)) { + body = JSON.stringify(body); + } else if (headers[contentType].match(/application\/x-www-form-urlencoded/i)) { + body = _querystring2.default.stringify(body); + } + } + return { body: body, headers: headers }; +}; + +module.exports = function (options) { + var promise = new _node2.default.Promise(); + var callbacks = { + success: options.success, + error: options.error + }; + delete options.success; + delete options.error; + delete options.uri; // not supported + options = Object.assign(options, encodeBody(options)); + // set follow redirects to false by default + options.followRedirect = options.followRedirects == true; + // support params options + if (_typeof(options.params) === 'object') { + options.qs = options.params; + } else if (typeof options.params === 'string') { + options.qs = _querystring2.default.parse(options.params); + } + // force the response as a buffer + options.encoding = null; + + (0, _request2.default)(options, function (error, response, body) { + if (error) { + if (callbacks.error) { + callbacks.error(error); + } + return promise.reject(error); + } + var httpResponse = new _HTTPResponse2.default(response, body); + + // Consider <200 && >= 400 as errors + if (httpResponse.status < 200 || httpResponse.status >= 400) { + if (callbacks.error) { + callbacks.error(httpResponse); + } + return promise.reject(httpResponse); + } else { + if (callbacks.success) { + callbacks.success(httpResponse); + } + return promise.resolve(httpResponse); + } + }); + return promise; +}; + +module.exports.encodeBody = encodeBody; \ No newline at end of file diff --git a/lib/cryptoUtils.js b/lib/cryptoUtils.js new file mode 100644 index 0000000000..ae36817935 --- /dev/null +++ b/lib/cryptoUtils.js @@ -0,0 +1,59 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.randomHexString = randomHexString; +exports.randomString = randomString; +exports.newObjectId = newObjectId; +exports.newToken = newToken; +exports.md5Hash = md5Hash; + +var _crypto = require('crypto'); + +// Returns a new random hex string of the given even size. +function randomHexString(size) { + if (size === 0) { + throw new Error('Zero-length randomHexString is useless.'); + } + if (size % 2 !== 0) { + throw new Error('randomHexString size must be divisible by 2.'); + } + return (0, _crypto.randomBytes)(size / 2).toString('hex'); +} + +// Returns a new random alphanumeric string of the given size. +// +// Note: to simplify implementation, the result has slight modulo bias, +// because chars length of 62 doesn't divide the number of all bytes +// (256) evenly. Such bias is acceptable for most cases when the output +// length is long enough and doesn't need to be uniform. + + +function randomString(size) { + if (size === 0) { + throw new Error('Zero-length randomString is useless.'); + } + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789'; + var objectId = ''; + var bytes = (0, _crypto.randomBytes)(size); + for (var i = 0; i < bytes.length; ++i) { + objectId += chars[bytes.readUInt8(i) % chars.length]; + } + return objectId; +} + +// Returns a new random alphanumeric string suitable for object ID. +function newObjectId() { + //TODO: increase length to better protect against collisions. + return randomString(10); +} + +// Returns a new random hex string suitable for secure tokens. +function newToken() { + return randomHexString(32); +} + +function md5Hash(string) { + return (0, _crypto.createHash)('md5').update(string).digest('hex'); +} \ No newline at end of file diff --git a/lib/deprecated.js b/lib/deprecated.js new file mode 100644 index 0000000000..3371f7cc51 --- /dev/null +++ b/lib/deprecated.js @@ -0,0 +1,11 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useExternal = useExternal; +function useExternal(name, moduleName) { + return function () { + throw name + " is not provided by parse-server anymore; please install " + moduleName; + }; +} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000000..63935f1d6a --- /dev/null +++ b/lib/index.js @@ -0,0 +1,53 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ParseServer = exports.logger = exports.TestUtils = exports.InMemoryCacheAdapter = exports.FileSystemAdapter = exports.GCSAdapter = exports.S3Adapter = undefined; + +var _ParseServer2 = require('./ParseServer'); + +var _ParseServer3 = _interopRequireDefault(_ParseServer2); + +var _logger = require('./logger'); + +var _logger2 = _interopRequireDefault(_logger); + +var _parseServerS3Adapter = require('parse-server-s3-adapter'); + +var _parseServerS3Adapter2 = _interopRequireDefault(_parseServerS3Adapter); + +var _parseServerFsAdapter = require('parse-server-fs-adapter'); + +var _parseServerFsAdapter2 = _interopRequireDefault(_parseServerFsAdapter); + +var _InMemoryCacheAdapter = require('./Adapters/Cache/InMemoryCacheAdapter'); + +var _InMemoryCacheAdapter2 = _interopRequireDefault(_InMemoryCacheAdapter); + +var _TestUtils = require('./TestUtils'); + +var _TestUtils2 = _interopRequireDefault(_TestUtils); + +var _deprecated = require('./deprecated'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// Factory function +var _ParseServer = function _ParseServer(options) { + var server = new _ParseServer3.default(options); + return server.app; +}; +// Mount the create liveQueryServer +_ParseServer.createLiveQueryServer = _ParseServer3.default.createLiveQueryServer; + +var GCSAdapter = (0, _deprecated.useExternal)('GCSAdapter', 'parse-server-gcs-adapter'); + +exports.default = _ParseServer3.default; +exports.S3Adapter = _parseServerS3Adapter2.default; +exports.GCSAdapter = GCSAdapter; +exports.FileSystemAdapter = _parseServerFsAdapter2.default; +exports.InMemoryCacheAdapter = _InMemoryCacheAdapter2.default; +exports.TestUtils = _TestUtils2.default; +exports.logger = _logger2.default; +exports.ParseServer = _ParseServer; \ No newline at end of file diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000000..939ce57d34 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,112 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.logger = undefined; +exports.configureLogger = configureLogger; +exports.addGroup = addGroup; + +var _winston = require('winston'); + +var _winston2 = _interopRequireDefault(_winston); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _path = require('path'); + +var _path2 = _interopRequireDefault(_path); + +var _winstonDailyRotateFile = require('winston-daily-rotate-file'); + +var _winstonDailyRotateFile2 = _interopRequireDefault(_winstonDailyRotateFile); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var LOGS_FOLDER = './logs/'; + +if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { + LOGS_FOLDER = './test_logs/'; +} + +LOGS_FOLDER = process.env.PARSE_SERVER_LOGS_FOLDER || LOGS_FOLDER; +var JSON_LOGS = process.env.JSON_LOGS || false; + +var currentLogsFolder = LOGS_FOLDER; + +function generateTransports(level) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var transports = [new _winstonDailyRotateFile2.default(Object.assign({ + filename: 'parse-server.info', + dirname: currentLogsFolder, + name: 'parse-server', + level: level + }, options)), new _winstonDailyRotateFile2.default(Object.assign({ + filename: 'parse-server.err', + dirname: currentLogsFolder, + name: 'parse-server-error', + level: 'error' + }), options)]; + if (!process.env.TESTING || process.env.VERBOSE) { + transports = [new _winston2.default.transports.Console(Object.assign({ + colorize: true, + level: level + }, options))].concat(transports); + } + return transports; +} + +var logger = new _winston2.default.Logger(); + +function configureLogger(_ref) { + var logsFolder = _ref.logsFolder; + var jsonLogs = _ref.jsonLogs; + var _ref$level = _ref.level; + var level = _ref$level === undefined ? _winston2.default.level : _ref$level; + + _winston2.default.level = level; + logsFolder = logsFolder || currentLogsFolder; + + if (!_path2.default.isAbsolute(logsFolder)) { + logsFolder = _path2.default.resolve(process.cwd(), logsFolder); + } + try { + _fs2.default.mkdirSync(logsFolder); + } catch (exception) { + // Ignore, assume the folder already exists + } + currentLogsFolder = logsFolder; + + var options = {}; + if (jsonLogs) { + options.json = true; + options.stringify = true; + } + var transports = generateTransports(level, options); + logger.configure({ + transports: transports + }); +} + +configureLogger({ logsFolder: LOGS_FOLDER, jsonLogs: JSON_LOGS }); + +function addGroup(groupName) { + var level = _winston2.default.level; + var transports = generateTransports().concat(new _winstonDailyRotateFile2.default({ + filename: groupName, + dirname: currentLogsFolder, + name: groupName, + level: level + })); + + _winston2.default.loggers.add(groupName, { + transports: transports + }); + return _winston2.default.loggers.get(groupName); +} + +exports.logger = logger; +exports.default = logger; \ No newline at end of file diff --git a/lib/middlewares.js b/lib/middlewares.js new file mode 100644 index 0000000000..f6f07be96e --- /dev/null +++ b/lib/middlewares.js @@ -0,0 +1,292 @@ +'use strict'; + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +var _logger = require('./logger'); + +var _logger2 = _interopRequireDefault(_logger); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var Parse = require('parse/node').Parse; + +var auth = require('./Auth'); +var Config = require('./Config'); +var ClientSDK = require('./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 +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) { + info.appId = basicAuth.appId; + 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 || !_cache2.default.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 && _cache2.default.get(req.body._ApplicationId) && (!info.masterKey || _cache2.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; + // 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 = _cache2.default.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. + var 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 auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken }).then(function (auth) { + if (auth) { + req.auth = auth; + next(); + } + }).catch(function (error) { + if (error instanceof Parse.Error) { + next(error); + return; + } else { + // TODO: Determine the correct error scenario. + _logger2.default.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(); +} + +var allowCrossDomain = 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(); + } +}; + +var allowMethodOverride = 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(); +}; + +var handleParseErrors = 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 { + _logger2.default.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); +}; + +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) { + var 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"}'); +} + +module.exports = { + allowCrossDomain: allowCrossDomain, + allowMethodOverride: allowMethodOverride, + handleParseErrors: handleParseErrors, + handleParseHeaders: handleParseHeaders, + enforceMasterKeyAccess: enforceMasterKeyAccess, + promiseEnforceMasterKeyAccess: promiseEnforceMasterKeyAccess +}; \ No newline at end of file diff --git a/lib/password.js b/lib/password.js new file mode 100644 index 0000000000..d050f6e306 --- /dev/null +++ b/lib/password.js @@ -0,0 +1,41 @@ +'use strict'; + +// Tools for encrypting and decrypting passwords. +// Basically promise-friendly wrappers for bcrypt. +var bcrypt = require('bcrypt-nodejs'); + +// Returns a promise for a hashed password string. +function hash(password) { + return new Promise(function (fulfill, reject) { + bcrypt.hash(password, null, null, function (err, hashedPassword) { + if (err) { + reject(err); + } else { + fulfill(hashedPassword); + } + }); + }); +} + +// Returns a promise for whether this password compares to equal this +// hashed password. +function compare(password, hashedPassword) { + return new Promise(function (fulfill, reject) { + // Cannot bcrypt compare when one is undefined + if (!password || !hashedPassword) { + return fulfill(false); + } + bcrypt.compare(password, hashedPassword, function (err, success) { + if (err) { + reject(err); + } else { + fulfill(success); + } + }); + }); +} + +module.exports = { + hash: hash, + compare: compare +}; \ No newline at end of file diff --git a/lib/pushStatusHandler.js b/lib/pushStatusHandler.js new file mode 100644 index 0000000000..3ca1e7485f --- /dev/null +++ b/lib/pushStatusHandler.js @@ -0,0 +1,130 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.flatten = flatten; +exports.default = pushStatusHandler; + +var _cryptoUtils = require('./cryptoUtils'); + +var _logger = require('./logger'); + +var PUSH_STATUS_COLLECTION = '_PushStatus'; + +function flatten(array) { + return array.reduce(function (memo, element) { + if (Array.isArray(element)) { + memo = memo.concat(flatten(element)); + } else { + memo = memo.concat(element); + } + return memo; + }, []); +} + +function pushStatusHandler(config) { + + var initialPromise = void 0; + var pushStatus = void 0; + var objectId = (0, _cryptoUtils.newObjectId)(); + var database = config.database; + var lastPromise = void 0; + var setInitial = function setInitial() { + var body = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var where = arguments[1]; + var options = arguments.length <= 2 || arguments[2] === undefined ? { source: 'rest' } : arguments[2]; + + var now = new Date(); + var data = body.data || {}; + var payloadString = JSON.stringify(data); + var object = { + objectId: objectId, + createdAt: now, + pushTime: now.toISOString(), + query: JSON.stringify(where), + payload: payloadString, + source: options.source, + title: options.title, + expiry: body.expiration_time, + status: "pending", + numSent: 0, + pushHash: (0, _cryptoUtils.md5Hash)(data.alert || ''), + // lockdown! + ACL: {} + }; + + lastPromise = database.create(PUSH_STATUS_COLLECTION, object).then(function () { + pushStatus = { + objectId: objectId + }; + return Promise.resolve(pushStatus); + }); + return lastPromise; + }; + + var setRunning = function setRunning(installations) { + _logger.logger.verbose('sending push to %d installations', installations.length); + lastPromise = lastPromise.then(function () { + return database.update(PUSH_STATUS_COLLECTION, { status: "pending", objectId: objectId }, { status: "running", updatedAt: new Date() }); + }); + return lastPromise; + }; + + var complete = function complete(results) { + var update = { + status: 'succeeded', + updatedAt: new Date(), + numSent: 0, + numFailed: 0 + }; + if (Array.isArray(results)) { + results = flatten(results); + results.reduce(function (memo, result) { + // Cannot handle that + if (!result || !result.device || !result.device.deviceType) { + return memo; + } + var deviceType = result.device.deviceType; + if (result.transmitted) { + memo.numSent++; + memo.sentPerType = memo.sentPerType || {}; + memo.sentPerType[deviceType] = memo.sentPerType[deviceType] || 0; + memo.sentPerType[deviceType]++; + } else { + memo.numFailed++; + memo.failedPerType = memo.failedPerType || {}; + memo.failedPerType[deviceType] = memo.failedPerType[deviceType] || 0; + memo.failedPerType[deviceType]++; + } + return memo; + }, update); + } + _logger.logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed); + lastPromise = lastPromise.then(function () { + return database.update(PUSH_STATUS_COLLECTION, { status: "running", objectId: objectId }, update); + }); + return lastPromise; + }; + + var fail = function fail(err) { + var update = { + errorMessage: JSON.stringify(err), + status: 'failed', + updatedAt: new Date() + }; + _logger.logger.info('warning: error while sending push', err); + lastPromise = lastPromise.then(function () { + return database.update(PUSH_STATUS_COLLECTION, { objectId: objectId }, update); + }); + return lastPromise; + }; + + return Object.freeze({ + objectId: objectId, + setInitial: setInitial, + setRunning: setRunning, + complete: complete, + fail: fail + }); +} \ No newline at end of file diff --git a/lib/requiredParameter.js b/lib/requiredParameter.js new file mode 100644 index 0000000000..6b8272e016 --- /dev/null +++ b/lib/requiredParameter.js @@ -0,0 +1,9 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +exports.default = function (errorMessage) { + throw errorMessage; +}; \ No newline at end of file diff --git a/lib/rest.js b/lib/rest.js new file mode 100644 index 0000000000..a4fe205098 --- /dev/null +++ b/lib/rest.js @@ -0,0 +1,140 @@ +'use strict'; + +var _Auth = require('./Auth'); + +var _Auth2 = _interopRequireDefault(_Auth); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// This file contains helpers for running operations in REST format. +// The goal is that handlers that explicitly handle an express route +// should just be shallow wrappers around things in this file, but +// these functions should not explicitly depend on the request +// object. +// This means that one of these handlers can support multiple +// routes. That's useful for the routes that do really similar +// things. + +var Parse = require('parse/node').Parse; + + +var RestQuery = require('./RestQuery'); +var RestWrite = require('./RestWrite'); +var triggers = require('./triggers'); + +// Returns a promise for an object with optional keys 'results' and 'count'. +function find(config, auth, className, restWhere, restOptions, clientSDK) { + enforceRoleSecurity('find', className, auth); + var query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK); + return query.execute(); +} + +// get is just like find but only queries an objectId. +var get = function get(config, auth, className, objectId, restOptions, clientSDK) { + enforceRoleSecurity('get', className, auth); + var query = new RestQuery(config, auth, className, { objectId: objectId }, restOptions, clientSDK); + return query.execute(); +}; + +// Returns a promise that doesn't resolve to any useful value. +function del(config, auth, className, objectId, clientSDK) { + if (typeof objectId !== 'string') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad objectId'); + } + + if (className === '_User' && !auth.couldUpdateUserId(objectId)) { + throw new Parse.Error(Parse.Error.SESSION_MISSING, 'insufficient auth to delete user'); + } + + enforceRoleSecurity('delete', className, auth); + + var inflatedObject; + + return Promise.resolve().then(function () { + if (triggers.getTrigger(className, triggers.Types.beforeDelete, config.applicationId) || triggers.getTrigger(className, triggers.Types.afterDelete, config.applicationId) || config.liveQueryController && config.liveQueryController.hasLiveQuery(className) || className == '_Session') { + return find(config, _Auth2.default.master(config), className, { objectId: objectId }).then(function (response) { + if (response && response.results && response.results.length) { + response.results[0].className = className; + + var cacheAdapter = config.cacheController; + cacheAdapter.user.del(response.results[0].sessionToken); + inflatedObject = Parse.Object.fromJSON(response.results[0]); + // Notify LiveQuery server if possible + config.liveQueryController.onAfterDelete(inflatedObject.className, inflatedObject); + return triggers.maybeRunTrigger(triggers.Types.beforeDelete, auth, inflatedObject, null, config); + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.'); + }); + } + return Promise.resolve({}); + }).then(function () { + if (!auth.isMaster) { + return auth.getUserRoles(); + } else { + return; + } + }).then(function () { + var options = {}; + if (!auth.isMaster) { + options.acl = ['*']; + if (auth.user) { + options.acl.push(auth.user.id); + options.acl = options.acl.concat(auth.userRoles); + } + } + + return config.database.destroy(className, { + objectId: objectId + }, options); + }).then(function () { + triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config); + return; + }); +} + +// Returns a promise for a {response, status, location} object. +function create(config, auth, className, restObject, clientSDK) { + enforceRoleSecurity('create', className, auth); + var write = new RestWrite(config, auth, className, null, restObject, null, clientSDK); + return write.execute(); +} + +// Returns a promise that contains the fields of the update that the +// REST API is supposed to return. +// Usually, this is just updatedAt. +function update(config, auth, className, objectId, restObject, clientSDK) { + enforceRoleSecurity('update', className, auth); + + return Promise.resolve().then(function () { + if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) || triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) || config.liveQueryController && config.liveQueryController.hasLiveQuery(className)) { + return find(config, _Auth2.default.master(config), className, { objectId: objectId }); + } + return Promise.resolve({}); + }).then(function (response) { + var originalRestObject; + if (response && response.results && response.results.length) { + originalRestObject = response.results[0]; + } + + var write = new RestWrite(config, auth, className, { objectId: objectId }, restObject, originalRestObject, clientSDK); + return write.execute(); + }); +} + +// Disallowing access to the _Role collection except by master key +function enforceRoleSecurity(method, className, auth) { + if (className === '_Installation' && !auth.isMaster) { + if (method === 'delete' || method === 'find') { + var error = 'Clients aren\'t allowed to perform the ' + method + ' operation on the installation collection.'; + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); + } + } +} + +module.exports = { + create: create, + del: del, + find: find, + get: get, + update: update +}; \ No newline at end of file diff --git a/lib/testing-routes.js b/lib/testing-routes.js new file mode 100644 index 0000000000..d6dd25deda --- /dev/null +++ b/lib/testing-routes.js @@ -0,0 +1,85 @@ +'use strict'; + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +var _middlewares = require('./middlewares'); + +var middlewares = _interopRequireWildcard(_middlewares); + +var _index = require('./index'); + +var _node = require('parse/node'); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// testing-routes.js +var express = require('express'), + cryptoUtils = require('./cryptoUtils'); + +var router = express.Router(); + +// creates a unique app in the cache, with a collection prefix +function createApp(req, res) { + var appId = cryptoUtils.randomHexString(32); + + (0, _index.ParseServer)({ + databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase', + appId: appId, + masterKey: 'master', + serverURL: _node.Parse.serverURL, + collectionPrefix: appId + }); + var keys = { + 'application_id': appId, + 'client_key': 'unused', + 'windows_key': 'unused', + 'javascript_key': 'unused', + 'webhook_key': 'unused', + 'rest_api_key': 'unused', + 'master_key': 'master' + }; + res.status(200).send(keys); +} + +// deletes all collections that belong to the app +function clearApp(req, res) { + if (!req.auth.isMaster) { + return res.status(401).send({ "error": "unauthorized" }); + } + return req.config.database.deleteEverything().then(function () { + res.status(200).send({}); + }); +} + +// deletes all collections and drops the app from cache +function dropApp(req, res) { + if (!req.auth.isMaster) { + return res.status(401).send({ "error": "unauthorized" }); + } + return req.config.database.deleteEverything().then(function () { + _cache2.default.del(req.config.applicationId); + res.status(200).send({}); + }); +} + +// Lets just return a success response and see what happens. +function notImplementedYet(req, res) { + res.status(200).send({}); +} + +router.post('/rest_clear_app', middlewares.handleParseHeaders, clearApp); +router.post('/rest_block', middlewares.handleParseHeaders, notImplementedYet); +router.post('/rest_mock_v8_client', middlewares.handleParseHeaders, notImplementedYet); +router.post('/rest_unmock_v8_client', middlewares.handleParseHeaders, notImplementedYet); +router.post('/rest_verify_analytics', middlewares.handleParseHeaders, notImplementedYet); +router.post('/rest_create_app', createApp); +router.post('/rest_drop_app', middlewares.handleParseHeaders, dropApp); +router.post('/rest_configure_app', middlewares.handleParseHeaders, notImplementedYet); + +module.exports = { + router: router +}; \ No newline at end of file diff --git a/lib/triggers.js b/lib/triggers.js new file mode 100644 index 0000000000..6efedb1cd7 --- /dev/null +++ b/lib/triggers.js @@ -0,0 +1,253 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Types = undefined; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; // triggers.js + + +exports.addFunction = addFunction; +exports.addTrigger = addTrigger; +exports.removeFunction = removeFunction; +exports.removeTrigger = removeTrigger; +exports._unregister = _unregister; +exports._unregisterAll = _unregisterAll; +exports.getTrigger = getTrigger; +exports.triggerExists = triggerExists; +exports.getFunction = getFunction; +exports.getValidator = getValidator; +exports.getRequestObject = getRequestObject; +exports.getResponseObject = getResponseObject; +exports.maybeRunTrigger = maybeRunTrigger; +exports.inflate = inflate; + +var _node = require('parse/node'); + +var _node2 = _interopRequireDefault(_node); + +var _cache = require('./cache'); + +var _cache2 = _interopRequireDefault(_cache); + +var _logger = require('./logger'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var Types = exports.Types = { + beforeSave: 'beforeSave', + afterSave: 'afterSave', + beforeDelete: 'beforeDelete', + afterDelete: 'afterDelete' +}; + +var baseStore = function baseStore() { + var Validators = {}; + var Functions = {}; + var Triggers = Object.keys(Types).reduce(function (base, key) { + base[key] = {}; + return base; + }, {}); + + return Object.freeze({ + Functions: Functions, + Validators: Validators, + Triggers: Triggers + }); +}; + +var _triggerStore = {}; + +function addFunction(functionName, handler, validationHandler, applicationId) { + applicationId = applicationId || _node2.default.applicationId; + _triggerStore[applicationId] = _triggerStore[applicationId] || baseStore(); + _triggerStore[applicationId].Functions[functionName] = handler; + _triggerStore[applicationId].Validators[functionName] = validationHandler; +} + +function addTrigger(type, className, handler, applicationId) { + applicationId = applicationId || _node2.default.applicationId; + _triggerStore[applicationId] = _triggerStore[applicationId] || baseStore(); + _triggerStore[applicationId].Triggers[type][className] = handler; +} + +function removeFunction(functionName, applicationId) { + applicationId = applicationId || _node2.default.applicationId; + delete _triggerStore[applicationId].Functions[functionName]; +} + +function removeTrigger(type, className, applicationId) { + applicationId = applicationId || _node2.default.applicationId; + delete _triggerStore[applicationId].Triggers[type][className]; +} + +function _unregister(appId, category, className, type) { + if (type) { + removeTrigger(className, type, appId); + delete _triggerStore[appId][category][className][type]; + } else { + delete _triggerStore[appId][category][className]; + } +} + +function _unregisterAll() { + Object.keys(_triggerStore).forEach(function (appId) { + return delete _triggerStore[appId]; + }); +} + +function getTrigger(className, triggerType, applicationId) { + if (!applicationId) { + throw "Missing ApplicationID"; + } + var manager = _triggerStore[applicationId]; + if (manager && manager.Triggers && manager.Triggers[triggerType] && manager.Triggers[triggerType][className]) { + return manager.Triggers[triggerType][className]; + } + return undefined; +}; + +function triggerExists(className, type, applicationId) { + return getTrigger(className, type, applicationId) != undefined; +} + +function getFunction(functionName, applicationId) { + var manager = _triggerStore[applicationId]; + if (manager && manager.Functions) { + return manager.Functions[functionName]; + }; + return undefined; +} + +function getValidator(functionName, applicationId) { + var manager = _triggerStore[applicationId]; + if (manager && manager.Validators) { + return manager.Validators[functionName]; + }; + return undefined; +} + +function getRequestObject(triggerType, auth, parseObject, originalParseObject, config) { + var request = { + triggerName: triggerType, + object: parseObject, + master: false, + log: config.loggerController && config.loggerController.adapter + }; + + if (originalParseObject) { + request.original = originalParseObject; + } + + if (!auth) { + return request; + } + if (auth.isMaster) { + request['master'] = true; + } + if (auth.user) { + request['user'] = auth.user; + } + if (auth.installationId) { + request['installationId'] = auth.installationId; + } + return request; +} + +// Creates the response object, and uses the request object to pass data +// The API will call this with REST API formatted objects, this will +// transform them to Parse.Object instances expected by Cloud Code. +// Any changes made to the object in a beforeSave will be included. +function getResponseObject(request, resolve, reject) { + return { + success: function success(response) { + // Use the JSON response + if (response && !request.object.equals(response) && request.triggerName === Types.beforeSave) { + return resolve(response); + } + response = {}; + if (request.triggerName === Types.beforeSave) { + response['object'] = request.object._getSaveJSON(); + } + return resolve(response); + }, + error: function error(code, message) { + if (!message) { + message = code; + code = _node2.default.Error.SCRIPT_FAILED; + } + var scriptError = new _node2.default.Error(code, message); + return reject(scriptError); + } + }; +}; + +function logTrigger(triggerType, className, input) { + if (triggerType.indexOf('after') != 0) { + return; + } + _logger.logger.info(triggerType + ' triggered for ' + className + '\nInput: ' + JSON.stringify(input), { + className: className, + triggerType: triggerType, + input: input + }); +} + +function logTriggerSuccess(triggerType, className, input, result) { + _logger.logger.info(triggerType + ' triggered for ' + className + '\nInput: ' + JSON.stringify(input) + '\nResult: ' + JSON.stringify(result), { + className: className, + triggerType: triggerType, + input: input, + result: result + }); +} + +function logTriggerError(triggerType, className, input, error) { + _logger.logger.error(triggerType + ' failed for ' + className + '\nInput: ' + JSON.stringify(input) + 'Error: ' + JSON.stringify(error), { + className: className, + triggerType: triggerType, + input: input, + error: error + }); +} + +// To be used as part of the promise chain when saving/deleting an object +// Will resolve successfully if no trigger is configured +// Resolves to an object, empty or containing an object key. A beforeSave +// trigger will set the object key to the rest format object to save. +// originalParseObject is optional, we only need that for befote/afterSave functions +function maybeRunTrigger(triggerType, auth, parseObject, originalParseObject, config) { + if (!parseObject) { + return Promise.resolve({}); + } + return new Promise(function (resolve, reject) { + var trigger = getTrigger(parseObject.className, triggerType, config.applicationId); + if (!trigger) return resolve(); + var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config); + var response = getResponseObject(request, function (object) { + logTriggerSuccess(triggerType, parseObject.className, parseObject.toJSON(), object); + resolve(object); + }, function (error) { + logTriggerError(triggerType, parseObject.className, parseObject.toJSON(), error); + reject(error); + }); + // Force the current Parse app before the trigger + _node2.default.applicationId = config.applicationId; + _node2.default.javascriptKey = config.javascriptKey || ''; + _node2.default.masterKey = config.masterKey; + // For the afterSuccess / afterDelete + logTrigger(triggerType, parseObject.className, parseObject.toJSON()); + trigger(request, response); + }); +}; + +// Converts a REST-format object to a Parse.Object +// data is either className or an object +function inflate(data, restObject) { + var copy = (typeof data === 'undefined' ? 'undefined' : _typeof(data)) == 'object' ? data : { className: data }; + for (var key in restObject) { + copy[key] = restObject[key]; + } + return _node2.default.Object.fromJSON(copy); +} \ No newline at end of file diff --git a/lib/vendor/mongodbUrl.js b/lib/vendor/mongodbUrl.js new file mode 100644 index 0000000000..fa60ea0c5c --- /dev/null +++ b/lib/vendor/mongodbUrl.js @@ -0,0 +1,928 @@ +// A slightly patched version of node's url module, with support for mongodb:// +// uris. +// +// See https://github.com/nodejs/node/blob/master/LICENSE for licensing +// information + +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var punycode = require('punycode'); + +exports.parse = urlParse; +exports.resolve = urlResolve; +exports.resolveObject = urlResolveObject; +exports.format = urlFormat; + +exports.Url = Url; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i; +var portPattern = /:[0-9]*$/; + +// Special case for a simple path URL +var simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; + +var hostnameMaxLen = 255; +// protocols that can allow "unsafe" and "unwise" chars. +var unsafeProtocol = { + 'javascript': true, + 'javascript:': true +}; +// protocols that never have a hostname. +var hostlessProtocol = { + 'javascript': true, + 'javascript:': true +}; +// protocols that always contain a // bit. +var slashedProtocol = { + 'http': true, + 'http:': true, + 'https': true, + 'https:': true, + 'ftp': true, + 'ftp:': true, + 'gopher': true, + 'gopher:': true, + 'file': true, + 'file:': true +}; +var querystring = require('querystring'); + +/* istanbul ignore next: improve coverage */ +function urlParse(url, parseQueryString, slashesDenoteHost) { + if (url instanceof Url) return url; + + var u = new Url(); + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} + +/* istanbul ignore next: improve coverage */ +Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { + if (typeof url !== 'string') { + throw new TypeError('Parameter "url" must be a string, not ' + (typeof url === 'undefined' ? 'undefined' : _typeof(url))); + } + + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + var hasHash = false; + var start = -1; + var end = -1; + var rest = ''; + var lastPos = 0; + var i = 0; + for (var inWs = false, split = false; i < url.length; ++i) { + var code = url.charCodeAt(i); + + // Find first and last non-whitespace characters for trimming + var isWs = code === 32 /* */ || code === 9 /*\t*/ || code === 13 /*\r*/ || code === 10 /*\n*/ || code === 12 /*\f*/ || code === 160 /*\u00A0*/ || code === 65279 /*\uFEFF*/; + if (start === -1) { + if (isWs) continue; + lastPos = start = i; + } else { + if (inWs) { + if (!isWs) { + end = -1; + inWs = false; + } + } else if (isWs) { + end = i; + inWs = true; + } + } + + // Only convert backslashes while we haven't seen a split character + if (!split) { + switch (code) { + case 35: + // '#' + hasHash = true; + // Fall through + case 63: + // '?' + split = true; + break; + case 92: + // '\\' + if (i - lastPos > 0) rest += url.slice(lastPos, i); + rest += '/'; + lastPos = i + 1; + break; + } + } else if (!hasHash && code === 35 /*#*/) { + hasHash = true; + } + } + + // Check if string was non-empty (including strings with only whitespace) + if (start !== -1) { + if (lastPos === start) { + // We didn't convert any backslashes + + if (end === -1) { + if (start === 0) rest = url;else rest = url.slice(start); + } else { + rest = url.slice(start, end); + } + } else if (end === -1 && lastPos < url.length) { + // We converted some backslashes and have only part of the entire string + rest += url.slice(lastPos); + } else if (end !== -1 && lastPos < end) { + // We converted some backslashes and have only part of the entire string + rest += url.slice(lastPos, end); + } + } + + if (!slashesDenoteHost && !hasHash) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.path = rest; + this.href = rest; + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + if (parseQueryString) { + this.query = querystring.parse(this.search.slice(1)); + } else { + this.query = this.search.slice(1); + } + } else if (parseQueryString) { + this.search = ''; + this.query = {}; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.slice(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || /^\/\/[^@\/]+@[^@\/]+/.test(rest)) { + var slashes = rest.charCodeAt(0) === 47 /*/*/ && rest.charCodeAt(1) === 47 /*/*/; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.slice(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && (slashes || proto && !slashedProtocol[proto])) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:b path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + var hostEnd = -1; + var atSign = -1; + var nonHost = -1; + for (i = 0; i < rest.length; ++i) { + switch (rest.charCodeAt(i)) { + case 9: // '\t' + case 10: // '\n' + case 13: // '\r' + case 32: // ' ' + case 34: // '"' + case 37: // '%' + case 39: // '\'' + case 59: // ';' + case 60: // '<' + case 62: // '>' + case 92: // '\\' + case 94: // '^' + case 96: // '`' + case 123: // '{' + case 124: // '|' + case 125: + // '}' + // Characters that are never ever allowed in a hostname from RFC 2396 + if (nonHost === -1) nonHost = i; + break; + case 35: // '#' + case 47: // '/' + case 63: + // '?' + // Find the first instance of any host-ending characters + if (nonHost === -1) nonHost = i; + hostEnd = i; + break; + case 64: + // '@' + // At this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + atSign = i; + nonHost = -1; + break; + } + if (hostEnd !== -1) break; + } + start = 0; + if (atSign !== -1) { + this.auth = decodeURIComponent(rest.slice(0, atSign)); + start = atSign + 1; + } + if (nonHost === -1) { + this.host = rest.slice(start); + rest = ''; + } else { + this.host = rest.slice(start, nonHost); + rest = rest.slice(nonHost); + } + + // pull out port. + this.parseHost(); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + if (typeof this.hostname !== 'string') this.hostname = ''; + + var hostname = this.hostname; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = hostname.charCodeAt(0) === 91 /*[*/ && hostname.charCodeAt(hostname.length - 1) === 93 /*]*/; + + // validate a little. + if (!ipv6Hostname) { + var result = validateHostname(this, rest, hostname); + if (result !== undefined) rest = result; + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + // IDNA Support: Returns a punycoded representation of "domain". + // It only converts parts of the domain name that + // have non-ASCII characters, i.e. it doesn't matter if + // you call it with a domain that already is ASCII-only. + this.hostname = punycode.toASCII(this.hostname); + } + + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.slice(1, -1); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } + } + + // now rest is set to the post-host stuff. + // chop off any delim chars. + if (!unsafeProtocol[lowerProto]) { + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + var _result = autoEscapeStr(rest); + if (_result !== undefined) rest = _result; + } + + var questionIdx = -1; + var hashIdx = -1; + for (i = 0; i < rest.length; ++i) { + var _code = rest.charCodeAt(i); + if (_code === 35 /*#*/) { + this.hash = rest.slice(i); + hashIdx = i; + break; + } else if (_code === 63 /*?*/ && questionIdx === -1) { + questionIdx = i; + } + } + + if (questionIdx !== -1) { + if (hashIdx === -1) { + this.search = rest.slice(questionIdx); + this.query = rest.slice(questionIdx + 1); + } else { + this.search = rest.slice(questionIdx, hashIdx); + this.query = rest.slice(questionIdx + 1, hashIdx); + } + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ''; + this.query = {}; + } + + var firstIdx = questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx) ? questionIdx : hashIdx; + if (firstIdx === -1) { + if (rest.length > 0) this.pathname = rest; + } else if (firstIdx > 0) { + this.pathname = rest.slice(0, firstIdx); + } + if (slashedProtocol[lowerProto] && this.hostname && !this.pathname) { + this.pathname = '/'; + } + + // to support http.request + if (this.pathname || this.search) { + var _p = this.pathname || ''; + var s = this.search || ''; + this.path = _p + s; + } + + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; +}; + +/* istanbul ignore next: improve coverage */ +function validateHostname(self, rest, hostname) { + for (var i = 0, lastPos; i <= hostname.length; ++i) { + var code; + if (i < hostname.length) code = hostname.charCodeAt(i); + if (code === 46 /*.*/ || i === hostname.length) { + if (i - lastPos > 0) { + if (i - lastPos > 63) { + self.hostname = hostname.slice(0, lastPos + 63); + return '/' + hostname.slice(lastPos + 63) + rest; + } + } + lastPos = i + 1; + continue; + } else if (code >= 48 /*0*/ && code <= 57 /*9*/ || code >= 97 /*a*/ && code <= 122 /*z*/ || code === 45 /*-*/ || code >= 65 /*A*/ && code <= 90 /*Z*/ || code === 43 /*+*/ || code === 95 /*_*/ || + /* BEGIN MONGO URI PATCH */ + code === 44 /*,*/ || code === 58 /*:*/ || + /* END MONGO URI PATCH */ + code > 127) { + continue; + } + // Invalid host character + self.hostname = hostname.slice(0, i); + if (i < hostname.length) return '/' + hostname.slice(i) + rest; + break; + } +} + +/* istanbul ignore next: improve coverage */ +function autoEscapeStr(rest) { + var newRest = ''; + var lastPos = 0; + for (var i = 0; i < rest.length; ++i) { + // Automatically escape all delimiters and unwise characters from RFC 2396 + // Also escape single quotes in case of an XSS attack + switch (rest.charCodeAt(i)) { + case 9: + // '\t' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%09'; + lastPos = i + 1; + break; + case 10: + // '\n' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%0A'; + lastPos = i + 1; + break; + case 13: + // '\r' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%0D'; + lastPos = i + 1; + break; + case 32: + // ' ' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%20'; + lastPos = i + 1; + break; + case 34: + // '"' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%22'; + lastPos = i + 1; + break; + case 39: + // '\'' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%27'; + lastPos = i + 1; + break; + case 60: + // '<' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%3C'; + lastPos = i + 1; + break; + case 62: + // '>' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%3E'; + lastPos = i + 1; + break; + case 92: + // '\\' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%5C'; + lastPos = i + 1; + break; + case 94: + // '^' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%5E'; + lastPos = i + 1; + break; + case 96: + // '`' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%60'; + lastPos = i + 1; + break; + case 123: + // '{' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%7B'; + lastPos = i + 1; + break; + case 124: + // '|' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%7C'; + lastPos = i + 1; + break; + case 125: + // '}' + if (i - lastPos > 0) newRest += rest.slice(lastPos, i); + newRest += '%7D'; + lastPos = i + 1; + break; + } + } + if (lastPos === 0) return; + if (lastPos < rest.length) return newRest + rest.slice(lastPos);else return newRest; +} + +// format a parsed object into a url string +/* istanbul ignore next: improve coverage */ +function urlFormat(obj) { + // ensure it's an object, and not a string url. + // If it's an obj, this is a no-op. + // this way, you can call url_format() on strings + // to clean up potentially wonky urls. + if (typeof obj === 'string') obj = urlParse(obj);else if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object' || obj === null) throw new TypeError('Parameter "urlObj" must be an object, not ' + obj === null ? 'null' : typeof obj === 'undefined' ? 'undefined' : _typeof(obj));else if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + + return obj.format(); +} + +/* istanbul ignore next: improve coverage */ +Url.prototype.format = function () { + var auth = this.auth || ''; + if (auth) { + auth = encodeAuth(auth); + auth += '@'; + } + + var protocol = this.protocol || ''; + var pathname = this.pathname || ''; + var hash = this.hash || ''; + var host = false; + var query = ''; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? this.hostname : '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; + } + } + + if (this.query !== null && _typeof(this.query) === 'object') query = querystring.stringify(this.query); + + var search = this.search || query && '?' + query || ''; + + if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /*:*/) protocol += ':'; + + var newPathname = ''; + var lastPos = 0; + for (var i = 0; i < pathname.length; ++i) { + switch (pathname.charCodeAt(i)) { + case 35: + // '#' + if (i - lastPos > 0) newPathname += pathname.slice(lastPos, i); + newPathname += '%23'; + lastPos = i + 1; + break; + case 63: + // '?' + if (i - lastPos > 0) newPathname += pathname.slice(lastPos, i); + newPathname += '%3F'; + lastPos = i + 1; + break; + } + } + if (lastPos > 0) { + if (lastPos !== pathname.length) pathname = newPathname + pathname.slice(lastPos);else pathname = newPathname; + } + + // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || (!protocol || slashedProtocol[protocol]) && host !== false) { + host = '//' + (host || ''); + if (pathname && pathname.charCodeAt(0) !== 47 /*/*/) pathname = '/' + pathname; + } else if (!host) { + host = ''; + } + + search = search.replace('#', '%23'); + + if (hash && hash.charCodeAt(0) !== 35 /*#*/) hash = '#' + hash; + if (search && search.charCodeAt(0) !== 63 /*?*/) search = '?' + search; + + return protocol + host + pathname + search + hash; +}; + +/* istanbul ignore next: improve coverage */ +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); +} + +/* istanbul ignore next: improve coverage */ +Url.prototype.resolve = function (relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; + +/* istanbul ignore next: improve coverage */ +function urlResolveObject(source, relative) { + if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); +} + +/* istanbul ignore next: improve coverage */ +Url.prototype.resolveObject = function (relative) { + if (typeof relative === 'string') { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } + + var result = new Url(); + var tkeys = Object.keys(this); + for (var tk = 0; tk < tkeys.length; tk++) { + var tkey = tkeys[tk]; + result[tkey] = this[tkey]; + } + + // hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; + + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === '') { + result.href = result.format(); + return result; + } + + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + var rkeys = Object.keys(relative); + for (var rk = 0; rk < rkeys.length; rk++) { + var rkey = rkeys[rk]; + if (rkey !== 'protocol') result[rkey] = relative[rkey]; + } + + //urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && result.hostname && !result.pathname) { + result.path = result.pathname = '/'; + } + + result.href = result.format(); + return result; + } + + if (relative.protocol && relative.protocol !== result.protocol) { + // if it's a known url protocol, then changing + // the protocol does weird things + // first, if it's not file:, then we MUST have a host, + // and if there was a path + // to begin with, then we MUST have a path. + // if it is file:, then the host is dropped, + // because that's known to be hostless. + // anything else is assumed to be absolute. + if (!slashedProtocol[relative.protocol]) { + var keys = Object.keys(relative); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + result[k] = relative[k]; + } + result.href = result.format(); + return result; + } + + result.protocol = relative.protocol; + if (!relative.host && !/^file:?$/.test(relative.protocol) && !hostlessProtocol[relative.protocol]) { + var _relPath = (relative.pathname || '').split('/'); + while (_relPath.length && !(relative.host = _relPath.shift())) {} + if (!relative.host) relative.host = ''; + if (!relative.hostname) relative.hostname = ''; + if (_relPath[0] !== '') _relPath.unshift(''); + if (_relPath.length < 2) _relPath.unshift(''); + result.pathname = _relPath.join('/'); + } else { + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; + } + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + var isSourceAbs = result.pathname && result.pathname.charAt(0) === '/'; + var isRelAbs = relative.host || relative.pathname && relative.pathname.charAt(0) === '/'; + var mustEndAbs = isRelAbs || isSourceAbs || result.host && relative.pathname; + var removeAllDots = mustEndAbs; + var srcPath = result.pathname && result.pathname.split('/') || []; + var relPath = relative.pathname && relative.pathname.split('/') || []; + var psychotic = result.protocol && !slashedProtocol[result.protocol]; + + // if the url is a non-slashed url, then relative + // links like ../.. should be able + // to crawl up to the hostname, as well. This is strange. + // result.protocol has already been set by now. + // Later on, put the first path part into the host field. + if (psychotic) { + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host;else srcPath.unshift(result.host); + } + result.host = ''; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === '') relPath[0] = relative.host;else relPath.unshift(relative.host); + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); + } + + if (isRelAbs) { + // it's absolute. + result.host = relative.host || relative.host === '' ? relative.host : result.host; + result.hostname = relative.hostname || relative.hostname === '' ? relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // fall through to the dot-handling below. + } else if (relPath.length) { + // it's relative + // throw away the existing file, and take the new path instead. + if (!srcPath) srcPath = []; + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (relative.search !== null && relative.search !== undefined) { + // just pull out the search. + // like href='?foo'. + // Put this after the other two cases because it simplifies the booleans + if (psychotic) { + result.hostname = result.host = srcPath.shift(); + //occasionally the auth can get stuck only in host + //this especially happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + result.search = relative.search; + result.query = relative.query; + //to support http.request + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : ''); + } + result.href = result.format(); + return result; + } + + if (!srcPath.length) { + // no path at all. easy. + // we've already handled the other stuff above. + result.pathname = null; + //to support http.request + if (result.search) { + result.path = '/' + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } + + // if a url ENDs in . or .., then it must get a trailing slash. + // however, if it ends in anything else non-slashy, + // then it must NOT get a trailing slash. + var last = srcPath.slice(-1)[0]; + var hasTrailingSlash = (result.host || relative.host || srcPath.length > 1) && (last === '.' || last === '..') || last === ''; + + // strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = srcPath.length; i >= 0; i--) { + last = srcPath[i]; + if (last === '.') { + spliceOne(srcPath, i); + } else if (last === '..') { + spliceOne(srcPath, i); + up++; + } else if (up) { + spliceOne(srcPath, i); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + for (; up--; up) { + srcPath.unshift('..'); + } + } + + if (mustEndAbs && srcPath[0] !== '' && (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { + srcPath.unshift(''); + } + + if (hasTrailingSlash && srcPath.join('/').substr(-1) !== '/') { + srcPath.push(''); + } + + var isAbsolute = srcPath[0] === '' || srcPath[0] && srcPath[0].charAt(0) === '/'; + + // put the host back + if (psychotic) { + result.hostname = result.host = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; + //occasionally the auth can get stuck only in host + //this especially happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var _authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; + if (_authInHost) { + result.auth = _authInHost.shift(); + result.host = result.hostname = _authInHost.shift(); + } + } + + mustEndAbs = mustEndAbs || result.host && srcPath.length; + + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(''); + } + + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); + } + + //to support request.http + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +/* istanbul ignore next: improve coverage */ +Url.prototype.parseHost = function () { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.slice(1); + } + host = host.slice(0, host.length - port.length); + } + if (host) this.hostname = host; +}; + +// About 1.5x faster than the two-arg version of Array#splice(). +/* istanbul ignore next: improve coverage */ +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) { + list[i] = list[k]; + }list.pop(); +} + +var hexTable = new Array(256); +for (var i = 0; i < 256; ++i) { + hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); +} /* istanbul ignore next: improve coverage */ +function encodeAuth(str) { + // faster encodeURIComponent alternative for encoding auth uri components + var out = ''; + var lastPos = 0; + for (var i = 0; i < str.length; ++i) { + var c = str.charCodeAt(i); + + // These characters do not need escaping: + // ! - . _ ~ + // ' ( ) * : + // digits + // alpha (uppercase) + // alpha (lowercase) + if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E || c >= 0x27 && c <= 0x2A || c >= 0x30 && c <= 0x3A || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A) { + continue; + } + + if (i - lastPos > 0) out += str.slice(lastPos, i); + + lastPos = i + 1; + + // Other ASCII characters + if (c < 0x80) { + out += hexTable[c]; + continue; + } + + // Multi-byte characters ... + if (c < 0x800) { + out += hexTable[0xC0 | c >> 6] + hexTable[0x80 | c & 0x3F]; + continue; + } + if (c < 0xD800 || c >= 0xE000) { + out += hexTable[0xE0 | c >> 12] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | c & 0x3F]; + continue; + } + // Surrogate pair + ++i; + var c2; + if (i < str.length) c2 = str.charCodeAt(i) & 0x3FF;else c2 = 0; + c = 0x10000 + ((c & 0x3FF) << 10 | c2); + out += hexTable[0xF0 | c >> 18] + hexTable[0x80 | c >> 12 & 0x3F] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | c & 0x3F]; + } + if (lastPos === 0) return str; + if (lastPos < str.length) return out + str.slice(lastPos); + return out; +} \ No newline at end of file From 4833c936f26242e1355a3aeff7b1fad11e0bbb22 Mon Sep 17 00:00:00 2001 From: benishak Date: Wed, 3 Aug 2016 19:39:09 +0200 Subject: [PATCH 4/5] ignore lib --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0953b0be61..95e1622a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ node_modules .vscode # Babel.js +lib # cache folder .cache From 364ac7b57c51539c9835ce732ea226fe1c47911c Mon Sep 17 00:00:00 2001 From: Wahb Ben Ishak Date: Wed, 3 Aug 2016 21:51:59 +0200 Subject: [PATCH 5/5] delete lib folder --- lib/Adapters/AdapterLoader.js | 43 - lib/Adapters/Analytics/AnalyticsAdapter.js | 31 - lib/Adapters/Cache/CacheAdapter.js | 56 - lib/Adapters/Cache/InMemoryCache.js | 88 -- lib/Adapters/Cache/InMemoryCacheAdapter.js | 58 - lib/Adapters/Email/MailAdapter.js | 43 - lib/Adapters/Files/FilesAdapter.js | 57 - lib/Adapters/Files/GridStoreAdapter.js | 101 -- lib/Adapters/Logger/FileLoggerAdapter.js | 157 --- lib/Adapters/Logger/LoggerAdapter.js | 40 - lib/Adapters/Push/PushAdapter.js | 44 - lib/Adapters/Storage/Mongo/MongoCollection.js | 141 --- .../Storage/Mongo/MongoSchemaCollection.js | 240 ---- .../Storage/Mongo/MongoStorageAdapter.js | 492 -------- lib/Adapters/Storage/Mongo/MongoTransform.js | 945 --------------- .../Postgres/PostgresStorageAdapter.js | 546 --------- lib/Auth.js | 221 ---- lib/ClientSDK.js | 42 - lib/Config.js | 224 ---- lib/Controllers/AdaptableController.js | 96 -- lib/Controllers/AnalyticsController.js | 61 - lib/Controllers/CacheController.js | 129 -- lib/Controllers/DatabaseController.js | 1061 ----------------- lib/Controllers/FilesController.js | 139 --- lib/Controllers/HooksController.js | 274 ----- lib/Controllers/LiveQueryController.js | 69 -- lib/Controllers/LoggerController.js | 117 -- lib/Controllers/PushController.js | 258 ---- lib/Controllers/SchemaCache.js | 96 -- lib/Controllers/SchemaController.js | 1011 ---------------- lib/Controllers/UserController.js | 261 ---- lib/DatabaseAdapter.js | 27 - lib/LiveQuery/Client.js | 162 --- lib/LiveQuery/EventEmitterPubSub.js | 92 -- lib/LiveQuery/Id.js | 34 - lib/LiveQuery/PLog.js | 7 - lib/LiveQuery/ParseCloudCodePublisher.js | 63 - lib/LiveQuery/ParseLiveQueryServer.js | 727 ----------- lib/LiveQuery/ParsePubSub.js | 35 - lib/LiveQuery/ParseWebSocketServer.js | 65 - lib/LiveQuery/QueryTools.js | 282 ----- lib/LiveQuery/RedisPubSub.js | 27 - lib/LiveQuery/RequestSchema.js | 106 -- lib/LiveQuery/SessionTokenCache.js | 67 -- lib/LiveQuery/Subscription.js | 70 -- lib/LiveQuery/equalObjects.js | 52 - lib/ParseServer.js | 453 ------- lib/PromiseRouter.js | 397 ------ lib/RestQuery.js | 892 -------------- lib/RestWrite.js | 944 --------------- lib/Routers/AnalyticsRouter.js | 51 - lib/Routers/ClassesRouter.js | 278 ----- lib/Routers/FeaturesRouter.js | 90 -- lib/Routers/FilesRouter.js | 126 -- lib/Routers/FunctionsRouter.js | 155 --- lib/Routers/GlobalConfigRouter.js | 79 -- lib/Routers/HooksRouter.js | 155 --- lib/Routers/IAPValidationRouter.js | 143 --- lib/Routers/InstallationsRouter.js | 117 -- lib/Routers/LogsRouter.js | 96 -- lib/Routers/PublicAPIRouter.js | 239 ---- lib/Routers/PurgeRouter.js | 64 - lib/Routers/PushRouter.js | 104 -- lib/Routers/RolesRouter.js | 97 -- lib/Routers/SchemasRouter.js | 126 -- lib/Routers/SessionsRouter.js | 120 -- lib/Routers/UsersRouter.js | 284 ----- lib/TestUtils.js | 21 - lib/authDataManager/OAuth1Client.js | 220 ---- lib/authDataManager/facebook.js | 52 - lib/authDataManager/github.js | 50 - lib/authDataManager/google.js | 43 - lib/authDataManager/index.js | 103 -- lib/authDataManager/instagram.js | 43 - lib/authDataManager/linkedin.js | 50 - lib/authDataManager/meetup.js | 49 - lib/authDataManager/spotify.js | 58 - lib/authDataManager/twitter.js | 55 - lib/batch.js | 91 -- lib/cache.js | 11 - lib/cli/cli-definitions.js | 214 ---- lib/cli/parse-server.js | 93 -- lib/cli/utils/commander.js | 148 --- lib/cloud-code/HTTPResponse.js | 65 - lib/cloud-code/Parse.Cloud.js | 62 - lib/cloud-code/httpRequest.js | 107 -- lib/cryptoUtils.js | 59 - lib/deprecated.js | 11 - lib/index.js | 53 - lib/logger.js | 112 -- lib/middlewares.js | 292 ----- lib/password.js | 41 - lib/pushStatusHandler.js | 130 -- lib/requiredParameter.js | 9 - lib/rest.js | 140 --- lib/testing-routes.js | 85 -- lib/triggers.js | 253 ---- lib/vendor/mongodbUrl.js | 928 -------------- 98 files changed, 17615 deletions(-) delete mode 100644 lib/Adapters/AdapterLoader.js delete mode 100644 lib/Adapters/Analytics/AnalyticsAdapter.js delete mode 100644 lib/Adapters/Cache/CacheAdapter.js delete mode 100644 lib/Adapters/Cache/InMemoryCache.js delete mode 100644 lib/Adapters/Cache/InMemoryCacheAdapter.js delete mode 100644 lib/Adapters/Email/MailAdapter.js delete mode 100644 lib/Adapters/Files/FilesAdapter.js delete mode 100644 lib/Adapters/Files/GridStoreAdapter.js delete mode 100644 lib/Adapters/Logger/FileLoggerAdapter.js delete mode 100644 lib/Adapters/Logger/LoggerAdapter.js delete mode 100644 lib/Adapters/Push/PushAdapter.js delete mode 100644 lib/Adapters/Storage/Mongo/MongoCollection.js delete mode 100644 lib/Adapters/Storage/Mongo/MongoSchemaCollection.js delete mode 100644 lib/Adapters/Storage/Mongo/MongoStorageAdapter.js delete mode 100644 lib/Adapters/Storage/Mongo/MongoTransform.js delete mode 100644 lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js delete mode 100644 lib/Auth.js delete mode 100644 lib/ClientSDK.js delete mode 100644 lib/Config.js delete mode 100644 lib/Controllers/AdaptableController.js delete mode 100644 lib/Controllers/AnalyticsController.js delete mode 100644 lib/Controllers/CacheController.js delete mode 100644 lib/Controllers/DatabaseController.js delete mode 100644 lib/Controllers/FilesController.js delete mode 100644 lib/Controllers/HooksController.js delete mode 100644 lib/Controllers/LiveQueryController.js delete mode 100644 lib/Controllers/LoggerController.js delete mode 100644 lib/Controllers/PushController.js delete mode 100644 lib/Controllers/SchemaCache.js delete mode 100644 lib/Controllers/SchemaController.js delete mode 100644 lib/Controllers/UserController.js delete mode 100644 lib/DatabaseAdapter.js delete mode 100644 lib/LiveQuery/Client.js delete mode 100644 lib/LiveQuery/EventEmitterPubSub.js delete mode 100644 lib/LiveQuery/Id.js delete mode 100644 lib/LiveQuery/PLog.js delete mode 100644 lib/LiveQuery/ParseCloudCodePublisher.js delete mode 100644 lib/LiveQuery/ParseLiveQueryServer.js delete mode 100644 lib/LiveQuery/ParsePubSub.js delete mode 100644 lib/LiveQuery/ParseWebSocketServer.js delete mode 100644 lib/LiveQuery/QueryTools.js delete mode 100644 lib/LiveQuery/RedisPubSub.js delete mode 100644 lib/LiveQuery/RequestSchema.js delete mode 100644 lib/LiveQuery/SessionTokenCache.js delete mode 100644 lib/LiveQuery/Subscription.js delete mode 100644 lib/LiveQuery/equalObjects.js delete mode 100644 lib/ParseServer.js delete mode 100644 lib/PromiseRouter.js delete mode 100644 lib/RestQuery.js delete mode 100644 lib/RestWrite.js delete mode 100644 lib/Routers/AnalyticsRouter.js delete mode 100644 lib/Routers/ClassesRouter.js delete mode 100644 lib/Routers/FeaturesRouter.js delete mode 100644 lib/Routers/FilesRouter.js delete mode 100644 lib/Routers/FunctionsRouter.js delete mode 100644 lib/Routers/GlobalConfigRouter.js delete mode 100644 lib/Routers/HooksRouter.js delete mode 100644 lib/Routers/IAPValidationRouter.js delete mode 100644 lib/Routers/InstallationsRouter.js delete mode 100644 lib/Routers/LogsRouter.js delete mode 100644 lib/Routers/PublicAPIRouter.js delete mode 100644 lib/Routers/PurgeRouter.js delete mode 100644 lib/Routers/PushRouter.js delete mode 100644 lib/Routers/RolesRouter.js delete mode 100644 lib/Routers/SchemasRouter.js delete mode 100644 lib/Routers/SessionsRouter.js delete mode 100644 lib/Routers/UsersRouter.js delete mode 100644 lib/TestUtils.js delete mode 100644 lib/authDataManager/OAuth1Client.js delete mode 100644 lib/authDataManager/facebook.js delete mode 100644 lib/authDataManager/github.js delete mode 100644 lib/authDataManager/google.js delete mode 100644 lib/authDataManager/index.js delete mode 100644 lib/authDataManager/instagram.js delete mode 100644 lib/authDataManager/linkedin.js delete mode 100644 lib/authDataManager/meetup.js delete mode 100644 lib/authDataManager/spotify.js delete mode 100644 lib/authDataManager/twitter.js delete mode 100644 lib/batch.js delete mode 100644 lib/cache.js delete mode 100644 lib/cli/cli-definitions.js delete mode 100644 lib/cli/parse-server.js delete mode 100644 lib/cli/utils/commander.js delete mode 100644 lib/cloud-code/HTTPResponse.js delete mode 100644 lib/cloud-code/Parse.Cloud.js delete mode 100644 lib/cloud-code/httpRequest.js delete mode 100644 lib/cryptoUtils.js delete mode 100644 lib/deprecated.js delete mode 100644 lib/index.js delete mode 100644 lib/logger.js delete mode 100644 lib/middlewares.js delete mode 100644 lib/password.js delete mode 100644 lib/pushStatusHandler.js delete mode 100644 lib/requiredParameter.js delete mode 100644 lib/rest.js delete mode 100644 lib/testing-routes.js delete mode 100644 lib/triggers.js delete mode 100644 lib/vendor/mongodbUrl.js diff --git a/lib/Adapters/AdapterLoader.js b/lib/Adapters/AdapterLoader.js deleted file mode 100644 index f2c5f0a12c..0000000000 --- a/lib/Adapters/AdapterLoader.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.loadAdapter = loadAdapter; -function loadAdapter(adapter, defaultAdapter, options) { - if (!adapter) { - if (!defaultAdapter) { - return options; - } - // Load from the default adapter when no adapter is set - return loadAdapter(defaultAdapter, undefined, options); - } else if (typeof adapter === "function") { - try { - return adapter(options); - } catch (e) { - if (e.name === 'TypeError') { - var Adapter = adapter; - return new Adapter(options); - } else { - throw e; - } - } - } else if (typeof adapter === "string") { - adapter = require(adapter); - // If it's define as a module, get the default - if (adapter.default) { - adapter = adapter.default; - } - return loadAdapter(adapter, undefined, options); - } else if (adapter.module) { - return loadAdapter(adapter.module, undefined, adapter.options); - } else if (adapter.class) { - return loadAdapter(adapter.class, undefined, adapter.options); - } else if (adapter.adapter) { - return loadAdapter(adapter.adapter, undefined, adapter.options); - } - // return the adapter as provided - return adapter; -} - -exports.default = loadAdapter; \ No newline at end of file diff --git a/lib/Adapters/Analytics/AnalyticsAdapter.js b/lib/Adapters/Analytics/AnalyticsAdapter.js deleted file mode 100644 index 3c81bfe4ea..0000000000 --- a/lib/Adapters/Analytics/AnalyticsAdapter.js +++ /dev/null @@ -1,31 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var AnalyticsAdapter = exports.AnalyticsAdapter = function () { - function AnalyticsAdapter() { - _classCallCheck(this, AnalyticsAdapter); - } - - _createClass(AnalyticsAdapter, [{ - key: "appOpened", - value: function appOpened(parameters, req) { - return Promise.resolve({}); - } - }, { - key: "trackEvent", - value: function trackEvent(eventName, parameters, req) { - return Promise.resolve({}); - } - }]); - - return AnalyticsAdapter; -}(); - -exports.default = AnalyticsAdapter; \ No newline at end of file diff --git a/lib/Adapters/Cache/CacheAdapter.js b/lib/Adapters/Cache/CacheAdapter.js deleted file mode 100644 index c0a8466a3e..0000000000 --- a/lib/Adapters/Cache/CacheAdapter.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var CacheAdapter = exports.CacheAdapter = function () { - function CacheAdapter() { - _classCallCheck(this, CacheAdapter); - } - - _createClass(CacheAdapter, [{ - key: "get", - - /** - * Get a value in the cache - * @param key Cache key to get - * @return Promise that will eventually resolve to the value in the cache. - */ - value: function get(key) {} - - /** - * Set a value in the cache - * @param key Cache key to set - * @param value Value to set the key - * @param ttl Optional TTL - */ - - }, { - key: "put", - value: function put(key, value, ttl) {} - - /** - * Remove a value from the cache. - * @param key Cache key to remove - */ - - }, { - key: "del", - value: function del(key) {} - - /** - * Empty a cache - */ - - }, { - key: "clear", - value: function clear() {} - }]); - - return CacheAdapter; -}(); \ No newline at end of file diff --git a/lib/Adapters/Cache/InMemoryCache.js b/lib/Adapters/Cache/InMemoryCache.js deleted file mode 100644 index 168510719e..0000000000 --- a/lib/Adapters/Cache/InMemoryCache.js +++ /dev/null @@ -1,88 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var DEFAULT_CACHE_TTL = 5 * 1000; - -var InMemoryCache = exports.InMemoryCache = function () { - function InMemoryCache(_ref) { - var _ref$ttl = _ref.ttl; - var ttl = _ref$ttl === undefined ? DEFAULT_CACHE_TTL : _ref$ttl; - - _classCallCheck(this, InMemoryCache); - - this.ttl = ttl; - this.cache = Object.create(null); - } - - _createClass(InMemoryCache, [{ - key: "get", - value: function get(key) { - var record = this.cache[key]; - if (record == null) { - return null; - } - - // Has Record and isnt expired - if (isNaN(record.expire) || record.expire >= Date.now()) { - return record.value; - } - - // Record has expired - delete this.cache[key]; - return null; - } - }, { - key: "put", - value: function put(key, value) { - var _this = this; - - var ttl = arguments.length <= 2 || arguments[2] === undefined ? this.ttl : arguments[2]; - - if (ttl < 0 || isNaN(ttl)) { - ttl = NaN; - } - - var record = { - value: value, - expire: ttl + Date.now() - }; - - if (!isNaN(record.expire)) { - record.timeout = setTimeout(function () { - _this.del(key); - }, ttl); - } - - this.cache[key] = record; - } - }, { - key: "del", - value: function del(key) { - var record = this.cache[key]; - if (record == null) { - return; - } - - if (record.timeout) { - clearTimeout(record.timeout); - } - delete this.cache[key]; - } - }, { - key: "clear", - value: function clear() { - this.cache = Object.create(null); - } - }]); - - return InMemoryCache; -}(); - -exports.default = InMemoryCache; \ No newline at end of file diff --git a/lib/Adapters/Cache/InMemoryCacheAdapter.js b/lib/Adapters/Cache/InMemoryCacheAdapter.js deleted file mode 100644 index fb9b00425d..0000000000 --- a/lib/Adapters/Cache/InMemoryCacheAdapter.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.InMemoryCacheAdapter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _InMemoryCache = require('./InMemoryCache'); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var InMemoryCacheAdapter = exports.InMemoryCacheAdapter = function () { - function InMemoryCacheAdapter(ctx) { - _classCallCheck(this, InMemoryCacheAdapter); - - this.cache = new _InMemoryCache.InMemoryCache(ctx); - } - - _createClass(InMemoryCacheAdapter, [{ - key: 'get', - value: function get(key) { - var _this = this; - - return new Promise(function (resolve, reject) { - var record = _this.cache.get(key); - if (record == null) { - return resolve(null); - } - - return resolve(JSON.parse(record)); - }); - } - }, { - key: 'put', - value: function put(key, value, ttl) { - this.cache.put(key, JSON.stringify(value), ttl); - return Promise.resolve(); - } - }, { - key: 'del', - value: function del(key) { - this.cache.del(key); - return Promise.resolve(); - } - }, { - key: 'clear', - value: function clear() { - this.cache.clear(); - return Promise.resolve(); - } - }]); - - return InMemoryCacheAdapter; -}(); - -exports.default = InMemoryCacheAdapter; \ No newline at end of file diff --git a/lib/Adapters/Email/MailAdapter.js b/lib/Adapters/Email/MailAdapter.js deleted file mode 100644 index 90bfc2647d..0000000000 --- a/lib/Adapters/Email/MailAdapter.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -/* - Mail Adapter prototype - A MailAdapter should implement at least sendMail() - */ -var MailAdapter = exports.MailAdapter = function () { - function MailAdapter() { - _classCallCheck(this, MailAdapter); - } - - _createClass(MailAdapter, [{ - key: "sendMail", - - /* - * A method for sending mail - * @param options would have the parameters - * - to: the recipient - * - text: the raw text of the message - * - subject: the subject of the email - */ - value: function sendMail(options) {} - - /* You can implement those methods if you want - * to provide HTML templates etc... - */ - // sendVerificationEmail({ link, appName, user }) {} - // sendPasswordResetEmail({ link, appName, user }) {} - - }]); - - return MailAdapter; -}(); - -exports.default = MailAdapter; \ No newline at end of file diff --git a/lib/Adapters/Files/FilesAdapter.js b/lib/Adapters/Files/FilesAdapter.js deleted file mode 100644 index dba1be21e6..0000000000 --- a/lib/Adapters/Files/FilesAdapter.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -// Files Adapter -// -// Allows you to change the file storage mechanism. -// -// Adapter classes must implement the following functions: -// * createFile(config, filename, data) -// * getFileData(config, filename) -// * getFileLocation(config, request, filename) -// -// Default is GridStoreAdapter, which requires mongo -// and for the API server to be using the DatabaseController with Mongo -// database adapter. - -var FilesAdapter = exports.FilesAdapter = function () { - function FilesAdapter() { - _classCallCheck(this, FilesAdapter); - } - - _createClass(FilesAdapter, [{ - key: "createFile", - - /* this method is responsible to store the file in order to be retrived later by it's file name - * - * @param filename the filename to save - * @param data the buffer of data from the file - * @param contentType the supposed contentType - * @discussion the contentType can be undefined if the controller was not able to determine it - * - * @return a promise that should fail if the storage didn't succeed - * - */ - value: function createFile(filename, data, contentType) {} - }, { - key: "deleteFile", - value: function deleteFile(filename) {} - }, { - key: "getFileData", - value: function getFileData(filename) {} - }, { - key: "getFileLocation", - value: function getFileLocation(config, filename) {} - }]); - - return FilesAdapter; -}(); - -exports.default = FilesAdapter; \ No newline at end of file diff --git a/lib/Adapters/Files/GridStoreAdapter.js b/lib/Adapters/Files/GridStoreAdapter.js deleted file mode 100644 index 6445fb72f6..0000000000 --- a/lib/Adapters/Files/GridStoreAdapter.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.GridStoreAdapter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _mongodb = require('mongodb'); - -var _FilesAdapter2 = require('./FilesAdapter'); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - GridStoreAdapter - Stores files in Mongo using GridStore - Requires the database adapter to be based on mongoclient - - weak - */ - -var DefaultMongoURI = 'mongodb://localhost:27017/parse'; - -var GridStoreAdapter = exports.GridStoreAdapter = function (_FilesAdapter) { - _inherits(GridStoreAdapter, _FilesAdapter); - - function GridStoreAdapter() { - var mongoDatabaseURI = arguments.length <= 0 || arguments[0] === undefined ? DefaultMongoURI : arguments[0]; - - _classCallCheck(this, GridStoreAdapter); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(GridStoreAdapter).call(this)); - - _this._databaseURI = mongoDatabaseURI; - _this._connect(); - return _this; - } - - _createClass(GridStoreAdapter, [{ - key: '_connect', - value: function _connect() { - if (!this._connectionPromise) { - this._connectionPromise = _mongodb.MongoClient.connect(this._databaseURI); - } - return this._connectionPromise; - } - - // For a given config object, filename, and data, store a file - // Returns a promise - - }, { - key: 'createFile', - value: function createFile(filename, data, contentType) { - return this._connect().then(function (database) { - var gridStore = new _mongodb.GridStore(database, filename, 'w'); - return gridStore.open(); - }).then(function (gridStore) { - return gridStore.write(data); - }).then(function (gridStore) { - return gridStore.close(); - }); - } - }, { - key: 'deleteFile', - value: function deleteFile(filename) { - return this._connect().then(function (database) { - var gridStore = new _mongodb.GridStore(database, filename, 'r'); - return gridStore.open(); - }).then(function (gridStore) { - return gridStore.unlink(); - }).then(function (gridStore) { - return gridStore.close(); - }); - } - }, { - key: 'getFileData', - value: function getFileData(filename) { - return this._connect().then(function (database) { - return _mongodb.GridStore.exist(database, filename).then(function () { - var gridStore = new _mongodb.GridStore(database, filename, 'r'); - return gridStore.open(); - }); - }).then(function (gridStore) { - return gridStore.read(); - }); - } - }, { - key: 'getFileLocation', - value: function getFileLocation(config, filename) { - return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename); - } - }]); - - return GridStoreAdapter; -}(_FilesAdapter2.FilesAdapter); - -exports.default = GridStoreAdapter; \ No newline at end of file diff --git a/lib/Adapters/Logger/FileLoggerAdapter.js b/lib/Adapters/Logger/FileLoggerAdapter.js deleted file mode 100644 index a4d035e9cf..0000000000 --- a/lib/Adapters/Logger/FileLoggerAdapter.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.FileLoggerAdapter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _LoggerAdapter2 = require('./LoggerAdapter'); - -var _winston = require('winston'); - -var _winston2 = _interopRequireDefault(_winston); - -var _fs = require('fs'); - -var _fs2 = _interopRequireDefault(_fs); - -var _node = require('parse/node'); - -var _logger = require('../../logger'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // Logger -// -// Wrapper around Winston logging library with custom query -// -// expected log entry to be in the shape of: -// {"level":"info","message":"Your Message","timestamp":"2016-02-04T05:59:27.412Z"} -// - - -var MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; -var CACHE_TIME = 1000 * 60; - -var LOGS_FOLDER = './logs/'; - -if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { - LOGS_FOLDER = './test_logs/'; -} - -var currentDate = new Date(); - -var simpleCache = { - timestamp: null, - from: null, - until: null, - order: null, - data: [], - level: 'info' -}; - -// returns Date object rounded to nearest day -var _getNearestDay = function _getNearestDay(date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); -}; - -// returns Date object of previous day -var _getPrevDay = function _getPrevDay(date) { - return new Date(date - MILLISECONDS_IN_A_DAY); -}; - -// returns the iso formatted file name -var _getFileName = function _getFileName() { - return _getNearestDay(currentDate).toISOString(); -}; - -// check for valid cache when both from and util match. -// cache valid for up to 1 minute -var _hasValidCache = function _hasValidCache(from, until, level) { - if (String(from) === String(simpleCache.from) && String(until) === String(simpleCache.until) && new Date() - simpleCache.timestamp < CACHE_TIME && level === simpleCache.level) { - return true; - } - return false; -}; - -// check that log entry has valid time stamp based on query -var _isValidLogEntry = function _isValidLogEntry(from, until, entry) { - var _entry = JSON.parse(entry), - timestamp = new Date(_entry.timestamp); - return timestamp >= from && timestamp <= until ? true : false; -}; - -var FileLoggerAdapter = exports.FileLoggerAdapter = function (_LoggerAdapter) { - _inherits(FileLoggerAdapter, _LoggerAdapter); - - function FileLoggerAdapter() { - _classCallCheck(this, FileLoggerAdapter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(FileLoggerAdapter).apply(this, arguments)); - } - - _createClass(FileLoggerAdapter, [{ - key: 'info', - value: function info() { - return _logger.logger.info.apply(undefined, arguments); - } - }, { - key: 'error', - value: function error() { - return _logger.logger.error.apply(undefined, arguments); - } - - // custom query as winston is currently limited - - }, { - key: 'query', - value: function query(options) { - var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; - - if (!options) { - options = {}; - } - // defaults to 7 days prior - var from = options.from || new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY); - var until = options.until || new Date(); - var limit = options.size || 10; - var order = options.order || 'desc'; - var level = options.level || 'info'; - var roundedUntil = _getNearestDay(until); - var roundedFrom = _getNearestDay(from); - - var options = { - from: from, - until: until, - limit: limit, - order: order - }; - - return new Promise(function (resolve, reject) { - _logger.logger.query(options, function (err, res) { - if (err) { - callback(err); - return reject(err); - } - if (level == 'error') { - callback(res['parse-server-error']); - resolve(res['parse-server-error']); - } else { - callback(res['parse-server']); - resolve(res['parse-server']); - } - }); - }); - } - }]); - - return FileLoggerAdapter; -}(_LoggerAdapter2.LoggerAdapter); - -exports.default = FileLoggerAdapter; \ No newline at end of file diff --git a/lib/Adapters/Logger/LoggerAdapter.js b/lib/Adapters/Logger/LoggerAdapter.js deleted file mode 100644 index 5def44f089..0000000000 --- a/lib/Adapters/Logger/LoggerAdapter.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -// Logger Adapter -// -// Allows you to change the logger mechanism -// -// Adapter classes must implement the following functions: -// * info(obj1 [, obj2, .., objN]) -// * error(obj1 [, obj2, .., objN]) -// * query(options, callback) -// Default is FileLoggerAdapter.js - -var LoggerAdapter = exports.LoggerAdapter = function () { - function LoggerAdapter() { - _classCallCheck(this, LoggerAdapter); - } - - _createClass(LoggerAdapter, [{ - key: "info", - value: function info() {} - }, { - key: "error", - value: function error() {} - }, { - key: "query", - value: function query(options, callback) {} - }]); - - return LoggerAdapter; -}(); - -exports.default = LoggerAdapter; \ No newline at end of file diff --git a/lib/Adapters/Push/PushAdapter.js b/lib/Adapters/Push/PushAdapter.js deleted file mode 100644 index 4ff7e54821..0000000000 --- a/lib/Adapters/Push/PushAdapter.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -// Push Adapter -// -// Allows you to change the push notification mechanism. -// -// Adapter classes must implement the following functions: -// * getValidPushTypes() -// * send(devices, installations, pushStatus) -// -// Default is ParsePushAdapter, which uses GCM for -// android push and APNS for ios push. - -var PushAdapter = exports.PushAdapter = function () { - function PushAdapter() { - _classCallCheck(this, PushAdapter); - } - - _createClass(PushAdapter, [{ - key: "send", - value: function send(devices, installations, pushStatus) {} - - /** - * Get an array of valid push types. - * @returns {Array} An array of valid push types - */ - - }, { - key: "getValidPushTypes", - value: function getValidPushTypes() {} - }]); - - return PushAdapter; -}(); - -exports.default = PushAdapter; \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoCollection.js b/lib/Adapters/Storage/Mongo/MongoCollection.js deleted file mode 100644 index b2a71c553e..0000000000 --- a/lib/Adapters/Storage/Mongo/MongoCollection.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var mongodb = require('mongodb'); -var Collection = mongodb.Collection; - -var MongoCollection = function () { - function MongoCollection(mongoCollection) { - _classCallCheck(this, MongoCollection); - - this._mongoCollection = mongoCollection; - } - - // Does a find with "smart indexing". - // Currently this just means, if it needs a geoindex and there is - // none, then build the geoindex. - // This could be improved a lot but it's not clear if that's a good - // idea. Or even if this behavior is a good idea. - - - _createClass(MongoCollection, [{ - key: 'find', - value: function find(query) { - var _this = this; - - var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - var skip = _ref.skip; - var limit = _ref.limit; - var sort = _ref.sort; - - return this._rawFind(query, { skip: skip, limit: limit, sort: sort }).catch(function (error) { - // Check for "no geoindex" error - if (error.code != 17007 && !error.message.match(/unable to find index for .geoNear/)) { - throw error; - } - // Figure out what key needs an index - var key = error.message.match(/field=([A-Za-z_0-9]+) /)[1]; - if (!key) { - throw error; - } - - var index = {}; - index[key] = '2d'; - return _this._mongoCollection.createIndex(index) - // Retry, but just once. - .then(function () { - return _this._rawFind(query, { skip: skip, limit: limit, sort: sort }); - }); - }); - } - }, { - key: '_rawFind', - value: function _rawFind(query) { - var _ref2 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - var skip = _ref2.skip; - var limit = _ref2.limit; - var sort = _ref2.sort; - - return this._mongoCollection.find(query, { skip: skip, limit: limit, sort: sort }).toArray(); - } - }, { - key: 'count', - value: function count(query) { - var _ref3 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - var skip = _ref3.skip; - var limit = _ref3.limit; - var sort = _ref3.sort; - - return this._mongoCollection.count(query, { skip: skip, limit: limit, sort: sort }); - } - }, { - key: 'insertOne', - value: function insertOne(object) { - return this._mongoCollection.insertOne(object); - } - - // Atomically updates data in the database for a single (first) object that matched the query - // If there is nothing that matches the query - does insert - // Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5. - - }, { - key: 'upsertOne', - value: function upsertOne(query, update) { - return this._mongoCollection.update(query, update, { upsert: true }); - } - }, { - key: 'updateOne', - value: function updateOne(query, update) { - return this._mongoCollection.updateOne(query, update); - } - }, { - key: 'updateMany', - value: function updateMany(query, update) { - return this._mongoCollection.updateMany(query, update); - } - }, { - key: 'deleteOne', - value: function deleteOne(query) { - return this._mongoCollection.deleteOne(query); - } - }, { - key: 'deleteMany', - value: function deleteMany(query) { - return this._mongoCollection.deleteMany(query); - } - }, { - key: '_ensureSparseUniqueIndexInBackground', - value: function _ensureSparseUniqueIndexInBackground(indexRequest) { - var _this2 = this; - - return new Promise(function (resolve, reject) { - _this2._mongoCollection.ensureIndex(indexRequest, { unique: true, background: true, sparse: true }, function (error, indexName) { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); - } - }, { - key: 'drop', - value: function drop() { - return this._mongoCollection.drop(); - } - }]); - - return MongoCollection; -}(); - -exports.default = MongoCollection; \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoSchemaCollection.js b/lib/Adapters/Storage/Mongo/MongoSchemaCollection.js deleted file mode 100644 index 48fb4192fe..0000000000 --- a/lib/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ /dev/null @@ -1,240 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _MongoCollection = require('./MongoCollection'); - -var _MongoCollection2 = _interopRequireDefault(_MongoCollection); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function mongoFieldToParseSchemaField(type) { - if (type[0] === '*') { - return { - type: 'Pointer', - targetClass: type.slice(1) - }; - } - if (type.startsWith('relation<')) { - return { - type: 'Relation', - targetClass: type.slice('relation<'.length, type.length - 1) - }; - } - switch (type) { - case 'number': - return { type: 'Number' }; - case 'string': - return { type: 'String' }; - case 'boolean': - return { type: 'Boolean' }; - case 'date': - return { type: 'Date' }; - case 'map': - case 'object': - return { type: 'Object' }; - case 'array': - return { type: 'Array' }; - case 'geopoint': - return { type: 'GeoPoint' }; - case 'file': - return { type: 'File' }; - case 'bytes': - return { type: 'Bytes' }; - } -} - -var nonFieldSchemaKeys = ['_id', '_metadata', '_client_permissions']; -function mongoSchemaFieldsToParseSchemaFields(schema) { - var fieldNames = Object.keys(schema).filter(function (key) { - return nonFieldSchemaKeys.indexOf(key) === -1; - }); - var response = fieldNames.reduce(function (obj, fieldName) { - obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]); - return obj; - }, {}); - response.ACL = { type: 'ACL' }; - response.createdAt = { type: 'Date' }; - response.updatedAt = { type: 'Date' }; - response.objectId = { type: 'String' }; - return response; -} - -var emptyCLPS = Object.freeze({ - find: {}, - get: {}, - create: {}, - update: {}, - delete: {}, - addField: {} -}); - -var defaultCLPS = Object.freeze({ - find: { '*': true }, - get: { '*': true }, - create: { '*': true }, - update: { '*': true }, - delete: { '*': true }, - addField: { '*': true } -}); - -function mongoSchemaToParseSchema(mongoSchema) { - var clps = defaultCLPS; - if (mongoSchema._metadata && mongoSchema._metadata.class_permissions) { - clps = _extends({}, emptyCLPS, mongoSchema._metadata.class_permissions); - } - return { - className: mongoSchema._id, - fields: mongoSchemaFieldsToParseSchemaFields(mongoSchema), - classLevelPermissions: clps - }; -} - -function _mongoSchemaQueryFromNameQuery(name, query) { - var object = { _id: name }; - if (query) { - Object.keys(query).forEach(function (key) { - object[key] = query[key]; - }); - } - return object; -} - -// Returns a type suitable for inserting into mongo _SCHEMA collection. -// Does no validation. That is expected to be done in Parse Server. -function parseFieldTypeToMongoFieldType(_ref) { - var type = _ref.type; - var targetClass = _ref.targetClass; - - switch (type) { - case 'Pointer': - return '*' + targetClass; - case 'Relation': - return 'relation<' + targetClass + '>'; - case 'Number': - return 'number'; - case 'String': - return 'string'; - case 'Boolean': - return 'boolean'; - case 'Date': - return 'date'; - case 'Object': - return 'object'; - case 'Array': - return 'array'; - case 'GeoPoint': - return 'geopoint'; - case 'File': - return 'file'; - } -} - -var MongoSchemaCollection = function () { - function MongoSchemaCollection(collection) { - _classCallCheck(this, MongoSchemaCollection); - - this._collection = collection; - } - - _createClass(MongoSchemaCollection, [{ - key: '_fetchAllSchemasFrom_SCHEMA', - value: function _fetchAllSchemasFrom_SCHEMA() { - return this._collection._rawFind({}).then(function (schemas) { - return schemas.map(mongoSchemaToParseSchema); - }); - } - }, { - key: '_fechOneSchemaFrom_SCHEMA', - value: function _fechOneSchemaFrom_SCHEMA(name) { - return this._collection._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 }).then(function (results) { - if (results.length === 1) { - return mongoSchemaToParseSchema(results[0]); - } else { - throw undefined; - } - }); - } - - // Atomically find and delete an object based on query. - - }, { - key: 'findAndDeleteSchema', - value: function findAndDeleteSchema(name) { - return this._collection._mongoCollection.findAndRemove(_mongoSchemaQueryFromNameQuery(name), []); - } - }, { - key: 'updateSchema', - value: function updateSchema(name, update) { - return this._collection.updateOne(_mongoSchemaQueryFromNameQuery(name), update); - } - }, { - key: 'upsertSchema', - value: function upsertSchema(name, query, update) { - return this._collection.upsertOne(_mongoSchemaQueryFromNameQuery(name, query), update); - } - - // Add a field to the schema. If database does not support the field - // type (e.g. mongo doesn't support more than one GeoPoint in a class) reject with an "Incorrect Type" - // Parse error with a desciptive message. If the field already exists, this function must - // not modify the schema, and must reject with DUPLICATE_VALUE error. - // If this is called for a class that doesn't exist, this function must create that class. - - // TODO: throw an error if an unsupported field type is passed. Deciding whether a type is supported - // should be the job of the adapter. Some adapters may not support GeoPoint at all. Others may - // Support additional types that Mongo doesn't, like Money, or something. - - // TODO: don't spend an extra query on finding the schema if the type we are trying to add isn't a GeoPoint. - - }, { - key: 'addFieldIfNotExists', - value: function addFieldIfNotExists(className, fieldName, type) { - var _this = this; - - return this._fechOneSchemaFrom_SCHEMA(className).then(function (schema) { - // The schema exists. Check for existing GeoPoints. - if (type.type === 'GeoPoint') { - // Make sure there are not other geopoint fields - if (Object.keys(schema.fields).some(function (existingField) { - return schema.fields[existingField].type === 'GeoPoint'; - })) { - throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.'); - } - } - return; - }, function (error) { - // If error is undefined, the schema doesn't exist, and we can create the schema with the field. - // If some other error, reject with it. - if (error === undefined) { - return; - } - throw error; - }).then(function () { - // We use $exists and $set to avoid overwriting the field type if it - // already exists. (it could have added inbetween the last query and the update) - return _this.upsertSchema(className, _defineProperty({}, fieldName, { '$exists': false }), { '$set': _defineProperty({}, fieldName, parseFieldTypeToMongoFieldType(type)) }); - }); - } - }]); - - return MongoSchemaCollection; -}(); - -// Exported for testing reasons and because we haven't moved all mongo schema format -// related logic into the database adapter yet. - - -MongoSchemaCollection._TESTmongoSchemaToParseSchema = mongoSchemaToParseSchema; -MongoSchemaCollection.parseFieldTypeToMongoFieldType = parseFieldTypeToMongoFieldType; - -exports.default = MongoSchemaCollection; \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoStorageAdapter.js b/lib/Adapters/Storage/Mongo/MongoStorageAdapter.js deleted file mode 100644 index 6f79243ca0..0000000000 --- a/lib/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ /dev/null @@ -1,492 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.MongoStorageAdapter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _MongoCollection = require('./MongoCollection'); - -var _MongoCollection2 = _interopRequireDefault(_MongoCollection); - -var _MongoSchemaCollection = require('./MongoSchemaCollection'); - -var _MongoSchemaCollection2 = _interopRequireDefault(_MongoSchemaCollection); - -var _mongodbUrl = require('../../../vendor/mongodbUrl'); - -var _MongoTransform = require('./MongoTransform'); - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var mongodb = require('mongodb'); -var MongoClient = mongodb.MongoClient; - -var MongoSchemaCollectionName = '_SCHEMA'; -var DefaultMongoURI = 'mongodb://localhost:27017/parse'; - -var storageAdapterAllCollections = function storageAdapterAllCollections(mongoAdapter) { - return mongoAdapter.connect().then(function () { - return mongoAdapter.database.collections(); - }).then(function (collections) { - return collections.filter(function (collection) { - if (collection.namespace.match(/\.system\./)) { - return false; - } - // TODO: If you have one app with a collection prefix that happens to be a prefix of another - // apps prefix, this will go very very badly. We should fix that somehow. - return collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0; - }); - }); -}; - -var convertParseSchemaToMongoSchema = function convertParseSchemaToMongoSchema(_ref) { - var schema = _objectWithoutProperties(_ref, []); - - delete schema.fields._rperm; - delete schema.fields._wperm; - - if (schema.className === '_User') { - // Legacy mongo adapter knows about the difference between password and _hashed_password. - // Future database adapters will only know about _hashed_password. - // Note: Parse Server will bring back password with injectDefaultSchema, so we don't need - // to add _hashed_password back ever. - delete schema.fields._hashed_password; - } - - return schema; -}; - -// Returns { code, error } if invalid, or { result }, an object -// suitable for inserting into _SCHEMA collection, otherwise. -var mongoSchemaFromFieldsAndClassNameAndCLP = function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions) { - var mongoObject = { - _id: className, - objectId: 'string', - updatedAt: 'string', - createdAt: 'string' - }; - - for (var fieldName in fields) { - mongoObject[fieldName] = _MongoSchemaCollection2.default.parseFieldTypeToMongoFieldType(fields[fieldName]); - } - - if (typeof classLevelPermissions !== 'undefined') { - mongoObject._metadata = mongoObject._metadata || {}; - if (!classLevelPermissions) { - delete mongoObject._metadata.class_permissions; - } else { - mongoObject._metadata.class_permissions = classLevelPermissions; - } - } - - return mongoObject; -}; - -var MongoStorageAdapter = exports.MongoStorageAdapter = function () { - // Public - function MongoStorageAdapter(_ref2) { - var _ref2$uri = _ref2.uri; - var uri = _ref2$uri === undefined ? DefaultMongoURI : _ref2$uri; - var _ref2$collectionPrefi = _ref2.collectionPrefix; - var collectionPrefix = _ref2$collectionPrefi === undefined ? '' : _ref2$collectionPrefi; - var _ref2$mongoOptions = _ref2.mongoOptions; - var mongoOptions = _ref2$mongoOptions === undefined ? {} : _ref2$mongoOptions; - - _classCallCheck(this, MongoStorageAdapter); - - this._uri = uri; - this._collectionPrefix = collectionPrefix; - this._mongoOptions = mongoOptions; - } - // Private - - - _createClass(MongoStorageAdapter, [{ - key: 'connect', - value: function connect() { - var _this = this; - - if (this.connectionPromise) { - return this.connectionPromise; - } - - // parsing and re-formatting causes the auth value (if there) to get URI - // encoded - var encodedUri = (0, _mongodbUrl.format)((0, _mongodbUrl.parse)(this._uri)); - - this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(function (database) { - if (!database) { - delete _this.connectionPromise; - return; - } - database.on('error', function (error) { - delete _this.connectionPromise; - }); - database.on('close', function (error) { - delete _this.connectionPromise; - }); - _this.database = database; - }).catch(function (err) { - delete _this.connectionPromise; - return Promise.reject(err); - }); - - return this.connectionPromise; - } - }, { - key: '_adaptiveCollection', - value: function _adaptiveCollection(name) { - var _this2 = this; - - return this.connect().then(function () { - return _this2.database.collection(_this2._collectionPrefix + name); - }).then(function (rawCollection) { - return new _MongoCollection2.default(rawCollection); - }); - } - }, { - key: '_schemaCollection', - value: function _schemaCollection() { - var _this3 = this; - - return this.connect().then(function () { - return _this3._adaptiveCollection(MongoSchemaCollectionName); - }).then(function (collection) { - return new _MongoSchemaCollection2.default(collection); - }); - } - }, { - key: 'classExists', - value: function classExists(name) { - var _this4 = this; - - return this.connect().then(function () { - return _this4.database.listCollections({ name: _this4._collectionPrefix + name }).toArray(); - }).then(function (collections) { - return collections.length > 0; - }); - } - }, { - key: 'setClassLevelPermissions', - value: function setClassLevelPermissions(className, CLPs) { - return this._schemaCollection().then(function (schemaCollection) { - return schemaCollection.updateSchema(className, { - $set: { _metadata: { class_permissions: CLPs } } - }); - }); - } - }, { - key: 'createClass', - value: function createClass(className, schema) { - schema = convertParseSchemaToMongoSchema(schema); - var mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions); - mongoObject._id = className; - return this._schemaCollection().then(function (schemaCollection) { - return schemaCollection._collection.insertOne(mongoObject); - }).then(function (result) { - return _MongoSchemaCollection2.default._TESTmongoSchemaToParseSchema(result.ops[0]); - }).catch(function (error) { - if (error.code === 11000) { - //Mongo's duplicate key error - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.'); - } else { - throw error; - } - }); - } - }, { - key: 'addFieldIfNotExists', - value: function addFieldIfNotExists(className, fieldName, type) { - return this._schemaCollection().then(function (schemaCollection) { - return schemaCollection.addFieldIfNotExists(className, fieldName, type); - }); - } - - // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) - // and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible. - - }, { - key: 'deleteClass', - value: function deleteClass(className) { - var _this5 = this; - - return this._adaptiveCollection(className).then(function (collection) { - return collection.drop(); - }).catch(function (error) { - // 'ns not found' means collection was already gone. Ignore deletion attempt. - if (error.message == 'ns not found') { - return; - } - throw error; - }) - // We've dropped the collection, now remove the _SCHEMA document - .then(function () { - return _this5._schemaCollection(); - }).then(function (schemaCollection) { - return schemaCollection.findAndDeleteSchema(className); - }); - } - - // Delete all data known to this adatper. Used for testing. - - }, { - key: 'deleteAllClasses', - value: function deleteAllClasses() { - return storageAdapterAllCollections(this).then(function (collections) { - return Promise.all(collections.map(function (collection) { - return collection.drop(); - })); - }); - } - - // Remove the column and all the data. For Relations, the _Join collection is handled - // specially, this function does not delete _Join columns. It should, however, indicate - // that the relation fields does not exist anymore. In mongo, this means removing it from - // the _SCHEMA collection. There should be no actual data in the collection under the same name - // as the relation column, so it's fine to attempt to delete it. If the fields listed to be - // deleted do not exist, this function should return successfully anyways. Checking for - // attempts to delete non-existent fields is the responsibility of Parse Server. - - // Pointer field names are passed for legacy reasons: the original mongo - // format stored pointer field names differently in the database, and therefore - // needed to know the type of the field before it could delete it. Future database - // adatpers should ignore the pointerFieldNames argument. All the field names are in - // fieldNames, they show up additionally in the pointerFieldNames database for use - // by the mongo adapter, which deals with the legacy mongo format. - - // This function is not obligated to delete fields atomically. It is given the field - // names in a list so that databases that are capable of deleting fields atomically - // may do so. - - // Returns a Promise. - - }, { - key: 'deleteFields', - value: function deleteFields(className, schema, fieldNames) { - var _this6 = this; - - var mongoFormatNames = fieldNames.map(function (fieldName) { - if (schema.fields[fieldName].type === 'Pointer') { - return '_p_' + fieldName; - } else { - return fieldName; - } - }); - var collectionUpdate = { '$unset': {} }; - mongoFormatNames.forEach(function (name) { - collectionUpdate['$unset'][name] = null; - }); - - var schemaUpdate = { '$unset': {} }; - fieldNames.forEach(function (name) { - schemaUpdate['$unset'][name] = null; - }); - - return this._adaptiveCollection(className).then(function (collection) { - return collection.updateMany({}, collectionUpdate); - }).then(function () { - return _this6._schemaCollection(); - }).then(function (schemaCollection) { - return schemaCollection.updateSchema(className, schemaUpdate); - }); - } - - // Return a promise for all schemas known to this adapter, in Parse format. In case the - // schemas cannot be retrieved, returns a promise that rejects. Requirements for the - // rejection reason are TBD. - - }, { - key: 'getAllClasses', - value: function getAllClasses() { - return this._schemaCollection().then(function (schemasCollection) { - return schemasCollection._fetchAllSchemasFrom_SCHEMA(); - }); - } - - // Return a promise for the schema with the given name, in Parse format. If - // this adapter doesn't know about the schema, return a promise that rejects with - // undefined as the reason. - - }, { - key: 'getClass', - value: function getClass(className) { - return this._schemaCollection().then(function (schemasCollection) { - return schemasCollection._fechOneSchemaFrom_SCHEMA(className); - }); - } - - // TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema, - // and should infer from the type. Or maybe does need the schema for validations. Or maybe needs - // the schem only for the legacy mongo format. We'll figure that out later. - - }, { - key: 'createObject', - value: function createObject(className, schema, object) { - schema = convertParseSchemaToMongoSchema(schema); - var mongoObject = (0, _MongoTransform.parseObjectToMongoObjectForCreate)(className, object, schema); - return this._adaptiveCollection(className).then(function (collection) { - return collection.insertOne(mongoObject); - }).catch(function (error) { - if (error.code === 11000) { - // Duplicate value - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided'); - } - throw error; - }); - } - - // Remove all objects that match the given Parse Query. - // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. - // If there is some other error, reject with INTERNAL_SERVER_ERROR. - - }, { - key: 'deleteObjectsByQuery', - value: function deleteObjectsByQuery(className, schema, query) { - schema = convertParseSchemaToMongoSchema(schema); - return this._adaptiveCollection(className).then(function (collection) { - var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); - return collection.deleteMany(mongoWhere); - }).then(function (_ref3) { - var result = _ref3.result; - - if (result.n === 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } - return Promise.resolve(); - }, function (error) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error'); - }); - } - - // Apply the update to all objects that match the given Parse Query. - - }, { - key: 'updateObjectsByQuery', - value: function updateObjectsByQuery(className, schema, query, update) { - schema = convertParseSchemaToMongoSchema(schema); - var mongoUpdate = (0, _MongoTransform.transformUpdate)(className, update, schema); - var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); - return this._adaptiveCollection(className).then(function (collection) { - return collection.updateMany(mongoWhere, mongoUpdate); - }); - } - - // Atomically finds and updates an object based on query. - // Return value not currently well specified. - - }, { - key: 'findOneAndUpdate', - value: function findOneAndUpdate(className, schema, query, update) { - schema = convertParseSchemaToMongoSchema(schema); - var mongoUpdate = (0, _MongoTransform.transformUpdate)(className, update, schema); - var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); - return this._adaptiveCollection(className).then(function (collection) { - return collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }); - }).then(function (result) { - return result.value; - }); - } - - // Hopefully we can get rid of this. It's only used for config and hooks. - - }, { - key: 'upsertOneObject', - value: function upsertOneObject(className, schema, query, update) { - schema = convertParseSchemaToMongoSchema(schema); - var mongoUpdate = (0, _MongoTransform.transformUpdate)(className, update, schema); - var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); - return this._adaptiveCollection(className).then(function (collection) { - return collection.upsertOne(mongoWhere, mongoUpdate); - }); - } - - // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. - - }, { - key: 'find', - value: function find(className, schema, query, _ref4) { - var skip = _ref4.skip; - var limit = _ref4.limit; - var sort = _ref4.sort; - - schema = convertParseSchemaToMongoSchema(schema); - var mongoWhere = (0, _MongoTransform.transformWhere)(className, query, schema); - var mongoSort = _lodash2.default.mapKeys(sort, function (value, fieldName) { - return (0, _MongoTransform.transformKey)(className, fieldName, schema); - }); - return this._adaptiveCollection(className).then(function (collection) { - return collection.find(mongoWhere, { skip: skip, limit: limit, sort: mongoSort }); - }).then(function (objects) { - return objects.map(function (object) { - return (0, _MongoTransform.mongoObjectToParseObject)(className, object, schema); - }); - }); - } - - // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't - // currently know which fields are nullable and which aren't, we ignore that criteria. - // As such, we shouldn't expose this function to users of parse until we have an out-of-band - // Way of determining if a field is nullable. Undefined doesn't count against uniqueness, - // which is why we use sparse indexes. - - }, { - key: 'ensureUniqueness', - value: function ensureUniqueness(className, schema, fieldNames) { - schema = convertParseSchemaToMongoSchema(schema); - var indexCreationRequest = {}; - var mongoFieldNames = fieldNames.map(function (fieldName) { - return (0, _MongoTransform.transformKey)(className, fieldName, schema); - }); - mongoFieldNames.forEach(function (fieldName) { - indexCreationRequest[fieldName] = 1; - }); - return this._adaptiveCollection(className).then(function (collection) { - return collection._ensureSparseUniqueIndexInBackground(indexCreationRequest); - }).catch(function (error) { - if (error.code === 11000) { - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.'); - } else { - throw error; - } - }); - } - - // Used in tests - - }, { - key: '_rawFind', - value: function _rawFind(className, query) { - return this._adaptiveCollection(className).then(function (collection) { - return collection.find(query); - }); - } - - // Executs a count. - - }, { - key: 'count', - value: function count(className, schema, query) { - schema = convertParseSchemaToMongoSchema(schema); - return this._adaptiveCollection(className).then(function (collection) { - return collection.count((0, _MongoTransform.transformWhere)(className, query, schema)); - }); - } - }]); - - return MongoStorageAdapter; -}(); - -exports.default = MongoStorageAdapter; - -module.exports = MongoStorageAdapter; // Required for tests \ No newline at end of file diff --git a/lib/Adapters/Storage/Mongo/MongoTransform.js b/lib/Adapters/Storage/Mongo/MongoTransform.js deleted file mode 100644 index 111bb49a8e..0000000000 --- a/lib/Adapters/Storage/Mongo/MongoTransform.js +++ /dev/null @@ -1,945 +0,0 @@ -'use strict'; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _logger = require('../../../logger'); - -var _logger2 = _interopRequireDefault(_logger); - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var mongodb = require('mongodb'); -var Parse = require('parse/node').Parse; - -var transformKey = function transformKey(className, fieldName, schema) { - // Check if the schema is known since it's a built-in field. - switch (fieldName) { - case 'objectId': - return '_id'; - case 'createdAt': - return '_created_at'; - case 'updatedAt': - return '_updated_at'; - case 'sessionToken': - return '_session_token'; - } - - if (schema.fields[fieldName] && schema.fields[fieldName].__type == 'Pointer') { - fieldName = '_p_' + fieldName; - } - - return fieldName; -}; - -var transformKeyValueForUpdate = function transformKeyValueForUpdate(className, restKey, restValue, parseFormatSchema) { - // Check if the schema is known since it's a built-in field. - var key = restKey; - var timeField = false; - switch (key) { - case 'objectId': - case '_id': - key = '_id'; - break; - case 'createdAt': - case '_created_at': - key = '_created_at'; - timeField = true; - break; - case 'updatedAt': - case '_updated_at': - key = '_updated_at'; - timeField = true; - break; - case 'sessionToken': - case '_session_token': - key = '_session_token'; - break; - case 'expiresAt': - case '_expiresAt': - key = 'expiresAt'; - timeField = true; - break; - case '_email_verify_token_expires_at': - key = '_email_verify_token_expires_at'; - timeField = true; - break; - case '_rperm': - case '_wperm': - return { key: key, value: restValue }; - break; - } - - if (parseFormatSchema.fields[key] && parseFormatSchema.fields[key].type === 'Pointer' || !parseFormatSchema.fields[key] && restValue && restValue.__type == 'Pointer') { - key = '_p_' + key; - } - - // Handle atomic values - var value = transformTopLevelAtom(restValue); - if (value !== CannotTransform) { - if (timeField && typeof value === 'string') { - value = new Date(value); - } - return { key: key, value: value }; - } - - // Handle arrays - if (restValue instanceof Array) { - value = restValue.map(transformInteriorValue); - return { key: key, value: value }; - } - - // Handle update operators - if ((typeof restValue === 'undefined' ? 'undefined' : _typeof(restValue)) === 'object' && '__op' in restValue) { - return { key: key, value: transformUpdateOperator(restValue, false) }; - } - - // Handle normal objects by recursing - value = _lodash2.default.mapValues(restValue, transformInteriorValue); - return { key: key, value: value }; -}; - -var transformInteriorValue = function transformInteriorValue(restValue) { - if (restValue !== null && (typeof restValue === 'undefined' ? 'undefined' : _typeof(restValue)) === 'object' && Object.keys(restValue).some(function (key) { - return key.includes('$') || key.includes('.'); - })) { - throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters"); - } - // Handle atomic values - var value = transformInteriorAtom(restValue); - if (value !== CannotTransform) { - return value; - } - - // Handle arrays - if (restValue instanceof Array) { - return restValue.map(transformInteriorValue); - } - - // Handle update operators - if ((typeof restValue === 'undefined' ? 'undefined' : _typeof(restValue)) === 'object' && '__op' in restValue) { - return transformUpdateOperator(restValue, true); - } - - // Handle normal objects by recursing - return _lodash2.default.mapValues(restValue, transformInteriorValue); -}; - -var valueAsDate = function valueAsDate(value) { - if (typeof value === 'string') { - return new Date(value); - } else if (value instanceof Date) { - return value; - } - return false; -}; - -function transformQueryKeyValue(className, key, value, schema) { - switch (key) { - case 'createdAt': - if (valueAsDate(value)) { - return { key: '_created_at', value: valueAsDate(value) }; - } - key = '_created_at'; - break; - case 'updatedAt': - if (valueAsDate(value)) { - return { key: '_updated_at', value: valueAsDate(value) }; - } - key = '_updated_at'; - break; - case 'expiresAt': - if (valueAsDate(value)) { - return { key: 'expiresAt', value: valueAsDate(value) }; - } - break; - case '_email_verify_token_expires_at': - if (valueAsDate(value)) { - return { key: '_email_verify_token_expires_at', value: valueAsDate(value) }; - } - break; - case 'objectId': - return { key: '_id', value: value }; - case 'sessionToken': - return { key: '_session_token', value: value }; - case '_rperm': - case '_wperm': - case '_perishable_token': - case '_email_verify_token': - return { key: key, value: value }; - case '$or': - return { key: '$or', value: value.map(function (subQuery) { - return transformWhere(className, subQuery, schema); - }) }; - case '$and': - return { key: '$and', value: value.map(function (subQuery) { - return transformWhere(className, subQuery, schema); - }) }; - default: - // Other auth data - var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); - if (authDataMatch) { - var provider = authDataMatch[1]; - // Special-case auth data. - return { key: '_auth_data_' + provider + '.id', value: value }; - } - } - - var expectedTypeIsArray = schema && schema.fields[key] && schema.fields[key].type === 'Array'; - - var expectedTypeIsPointer = schema && schema.fields[key] && schema.fields[key].type === 'Pointer'; - - if (expectedTypeIsPointer || !schema && value && value.__type === 'Pointer') { - key = '_p_' + key; - } - - // Handle query constraints - if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) { - return { key: key, value: transformConstraint(value, expectedTypeIsArray) }; - } - - if (expectedTypeIsArray && !(value instanceof Array)) { - return { key: key, value: { '$all': [value] } }; - } - - // Handle atomic values - if (transformTopLevelAtom(value) !== CannotTransform) { - return { key: key, value: transformTopLevelAtom(value) }; - } else { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'You cannot use ' + value + ' as a query parameter.'); - } -} - -// Main exposed method to help run queries. -// restWhere is the "where" clause in REST API form. -// Returns the mongo form of the query. -function transformWhere(className, restWhere, schema) { - var mongoWhere = {}; - for (var restKey in restWhere) { - var out = transformQueryKeyValue(className, restKey, restWhere[restKey], schema); - mongoWhere[out.key] = out.value; - } - return mongoWhere; -} - -var parseObjectKeyValueToMongoObjectKeyValue = function parseObjectKeyValueToMongoObjectKeyValue(restKey, restValue, schema) { - // Check if the schema is known since it's a built-in field. - var transformedValue = void 0; - var coercedToDate = void 0; - switch (restKey) { - case 'objectId': - return { key: '_id', value: restValue }; - case 'expiresAt': - transformedValue = transformTopLevelAtom(restValue); - coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue; - return { key: 'expiresAt', value: coercedToDate }; - case '_email_verify_token_expires_at': - transformedValue = transformTopLevelAtom(restValue); - coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue; - return { key: '_email_verify_token_expires_at', value: coercedToDate }; - case '_rperm': - case '_wperm': - case '_email_verify_token': - case '_hashed_password': - case '_perishable_token': - return { key: restKey, value: restValue }; - case 'sessionToken': - return { key: '_session_token', value: restValue }; - default: - // Auth data should have been transformed already - if (restKey.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + restKey); - } - // Trust that the auth data has been transformed and save it directly - if (restKey.match(/^_auth_data_[a-zA-Z0-9_]+$/)) { - return { key: restKey, value: restValue }; - } - } - //skip straight to transformTopLevelAtom for Bytes, they don't show up in the schema for some reason - if (restValue && restValue.__type !== 'Bytes') { - //Note: We may not know the type of a field here, as the user could be saving (null) to a field - //That never existed before, meaning we can't infer the type. - if (schema.fields[restKey] && schema.fields[restKey].type == 'Pointer' || restValue.__type == 'Pointer') { - restKey = '_p_' + restKey; - } - } - - // Handle atomic values - var value = transformTopLevelAtom(restValue); - if (value !== CannotTransform) { - return { key: restKey, value: value }; - } - - // ACLs are handled before this method is called - // If an ACL key still exists here, something is wrong. - if (restKey === 'ACL') { - throw 'There was a problem transforming an ACL.'; - } - - // Handle arrays - if (restValue instanceof Array) { - value = restValue.map(transformInteriorValue); - return { key: restKey, value: value }; - } - - // Handle normal objects by recursing - if (Object.keys(restValue).some(function (key) { - return key.includes('$') || key.includes('.'); - })) { - throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters"); - } - value = _lodash2.default.mapValues(restValue, transformInteriorValue); - return { key: restKey, value: value }; -}; - -var parseObjectToMongoObjectForCreate = function parseObjectToMongoObjectForCreate(className, restCreate, schema) { - restCreate = addLegacyACL(restCreate); - var mongoCreate = {}; - for (var restKey in restCreate) { - var _parseObjectKeyValueT = parseObjectKeyValueToMongoObjectKeyValue(restKey, restCreate[restKey], schema); - - var key = _parseObjectKeyValueT.key; - var value = _parseObjectKeyValueT.value; - - if (value !== undefined) { - mongoCreate[key] = value; - } - } - - // Use the legacy mongo format for createdAt and updatedAt - if (mongoCreate.createdAt) { - mongoCreate._created_at = new Date(mongoCreate.createdAt.iso || mongoCreate.createdAt); - delete mongoCreate.createdAt; - } - if (mongoCreate.updatedAt) { - mongoCreate._updated_at = new Date(mongoCreate.updatedAt.iso || mongoCreate.updatedAt); - delete mongoCreate.updatedAt; - } - - return mongoCreate; -}; - -// Main exposed method to help update old objects. -var transformUpdate = function transformUpdate(className, restUpdate, parseFormatSchema) { - var mongoUpdate = {}; - var acl = addLegacyACL(restUpdate)._acl; - if (acl) { - mongoUpdate.$set = {}; - if (acl._rperm) { - mongoUpdate.$set._rperm = acl._rperm; - } - if (acl._wperm) { - mongoUpdate.$set._wperm = acl._wperm; - } - if (acl._acl) { - mongoUpdate.$set._acl = acl._acl; - } - } - for (var restKey in restUpdate) { - var out = transformKeyValueForUpdate(className, restKey, restUpdate[restKey], parseFormatSchema); - - // If the output value is an object with any $ keys, it's an - // operator that needs to be lifted onto the top level update - // object. - if (_typeof(out.value) === 'object' && out.value !== null && out.value.__op) { - mongoUpdate[out.value.__op] = mongoUpdate[out.value.__op] || {}; - mongoUpdate[out.value.__op][out.key] = out.value.arg; - } else { - mongoUpdate['$set'] = mongoUpdate['$set'] || {}; - mongoUpdate['$set'][out.key] = out.value; - } - } - - return mongoUpdate; -}; - -// Add the legacy _acl format. -var addLegacyACL = function addLegacyACL(restObject) { - var restObjectCopy = _extends({}, restObject); - var _acl = {}; - - if (restObject._wperm) { - restObject._wperm.forEach(function (entry) { - _acl[entry] = { w: true }; - }); - } - - if (restObject._rperm) { - restObject._rperm.forEach(function (entry) { - if (!(entry in _acl)) { - _acl[entry] = { r: true }; - } else { - _acl[entry].r = true; - } - }); - } - - if (Object.keys(_acl).length > 0) { - restObjectCopy._acl = _acl; - } - - return restObjectCopy; -}; - -// A sentinel value that helper transformations return when they -// cannot perform a transformation -function CannotTransform() {} - -var transformInteriorAtom = function transformInteriorAtom(atom) { - // TODO: check validity harder for the __type-defined types - if ((typeof atom === 'undefined' ? 'undefined' : _typeof(atom)) === 'object' && atom && !(atom instanceof Date) && atom.__type === 'Pointer') { - return { - __type: 'Pointer', - className: atom.className, - objectId: atom.objectId - }; - } else if (typeof atom === 'function' || (typeof atom === 'undefined' ? 'undefined' : _typeof(atom)) === 'symbol') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot transform value: ' + atom); - } else if (DateCoder.isValidJSON(atom)) { - return DateCoder.JSONToDatabase(atom); - } else if (BytesCoder.isValidJSON(atom)) { - return BytesCoder.JSONToDatabase(atom); - } else { - return atom; - } -}; - -// Helper function to transform an atom from REST format to Mongo format. -// An atom is anything that can't contain other expressions. So it -// includes things where objects are used to represent other -// datatypes, like pointers and dates, but it does not include objects -// or arrays with generic stuff inside. -// Raises an error if this cannot possibly be valid REST format. -// Returns CannotTransform if it's just not an atom -function transformTopLevelAtom(atom) { - switch (typeof atom === 'undefined' ? 'undefined' : _typeof(atom)) { - case 'string': - case 'number': - case 'boolean': - return atom; - case 'undefined': - return atom; - case 'symbol': - case 'function': - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot transform value: ' + atom); - case 'object': - if (atom instanceof Date) { - // Technically dates are not rest format, but, it seems pretty - // clear what they should be transformed to, so let's just do it. - return atom; - } - - if (atom === null) { - return atom; - } - - // TODO: check validity harder for the __type-defined types - if (atom.__type == 'Pointer') { - return atom.className + '$' + atom.objectId; - } - if (DateCoder.isValidJSON(atom)) { - return DateCoder.JSONToDatabase(atom); - } - if (BytesCoder.isValidJSON(atom)) { - return BytesCoder.JSONToDatabase(atom); - } - if (GeoPointCoder.isValidJSON(atom)) { - return GeoPointCoder.JSONToDatabase(atom); - } - if (FileCoder.isValidJSON(atom)) { - return FileCoder.JSONToDatabase(atom); - } - return CannotTransform; - - default: - // I don't think typeof can ever let us get here - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'really did not expect value: ' + atom); - } -} - -// Transforms a query constraint from REST API format to Mongo format. -// A constraint is something with fields like $lt. -// If it is not a valid constraint but it could be a valid something -// else, return CannotTransform. -// inArray is whether this is an array field. -function transformConstraint(constraint, inArray) { - if ((typeof constraint === 'undefined' ? 'undefined' : _typeof(constraint)) !== 'object' || !constraint) { - return CannotTransform; - } - - // keys is the constraints in reverse alphabetical order. - // This is a hack so that: - // $regex is handled before $options - // $nearSphere is handled before $maxDistance - var keys = Object.keys(constraint).sort().reverse(); - var answer = {}; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var key = _step.value; - - switch (key) { - case '$lt': - case '$lte': - case '$gt': - case '$gte': - case '$exists': - case '$ne': - case '$eq': - answer[key] = inArray ? transformInteriorAtom(constraint[key]) : transformTopLevelAtom(constraint[key]); - if (answer[key] === CannotTransform) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad atom: ' + atom); - } - break; - - case '$in': - case '$nin': - var arr = constraint[key]; - if (!(arr instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value'); - } - answer[key] = arr.map(function (value) { - var result = inArray ? transformInteriorAtom(value) : transformTopLevelAtom(value); - if (result === CannotTransform) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad atom: ' + atom); - } - return result; - }); - break; - - case '$all': - var arr = constraint[key]; - if (!(arr instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value'); - } - answer[key] = arr.map(transformInteriorAtom); - break; - - case '$regex': - var s = constraint[key]; - if (typeof s !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad regex: ' + s); - } - answer[key] = s; - break; - - case '$options': - answer[key] = constraint[key]; - break; - - case '$nearSphere': - var point = constraint[key]; - answer[key] = [point.longitude, point.latitude]; - break; - - case '$maxDistance': - answer[key] = constraint[key]; - break; - - // The SDKs don't seem to use these but they are documented in the - // REST API docs. - case '$maxDistanceInRadians': - answer['$maxDistance'] = constraint[key]; - break; - case '$maxDistanceInMiles': - answer['$maxDistance'] = constraint[key] / 3959; - break; - case '$maxDistanceInKilometers': - answer['$maxDistance'] = constraint[key] / 6371; - break; - - case '$select': - case '$dontSelect': - throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, 'the ' + key + ' constraint is not supported yet'); - - case '$within': - var box = constraint[key]['$box']; - if (!box || box.length != 2) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'malformatted $within arg'); - } - answer[key] = { - '$box': [[box[0].longitude, box[0].latitude], [box[1].longitude, box[1].latitude]] - }; - break; - - default: - if (key.match(/^\$+/)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad constraint: ' + key); - } - return CannotTransform; - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - return answer; -} - -// Transforms an update operator from REST format to mongo format. -// To be transformed, the input should have an __op field. -// If flatten is true, this will flatten operators to their static -// data format. For example, an increment of 2 would simply become a -// 2. -// The output for a non-flattened operator is a hash with __op being -// the mongo op, and arg being the argument. -// The output for a flattened operator is just a value. -// Returns undefined if this should be a no-op. - -function transformUpdateOperator(_ref, flatten) { - var __op = _ref.__op; - var amount = _ref.amount; - var objects = _ref.objects; - - switch (__op) { - case 'Delete': - if (flatten) { - return undefined; - } else { - return { __op: '$unset', arg: '' }; - } - - case 'Increment': - if (typeof amount !== 'number') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'incrementing must provide a number'); - } - if (flatten) { - return amount; - } else { - return { __op: '$inc', arg: amount }; - } - - case 'Add': - case 'AddUnique': - if (!(objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); - } - var toAdd = objects.map(transformInteriorAtom); - if (flatten) { - return toAdd; - } else { - var mongoOp = { - Add: '$push', - AddUnique: '$addToSet' - }[__op]; - return { __op: mongoOp, arg: { '$each': toAdd } }; - } - - case 'Remove': - if (!(objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to remove must be an array'); - } - var toRemove = objects.map(transformInteriorAtom); - if (flatten) { - return []; - } else { - return { __op: '$pullAll', arg: toRemove }; - } - - default: - throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, 'The ' + __op + ' operator is not supported yet.'); - } -} - -var nestedMongoObjectToNestedParseObject = function nestedMongoObjectToNestedParseObject(mongoObject) { - switch (typeof mongoObject === 'undefined' ? 'undefined' : _typeof(mongoObject)) { - case 'string': - case 'number': - case 'boolean': - return mongoObject; - case 'undefined': - case 'symbol': - case 'function': - throw 'bad value in mongoObjectToParseObject'; - case 'object': - if (mongoObject === null) { - return null; - } - if (mongoObject instanceof Array) { - return mongoObject.map(nestedMongoObjectToNestedParseObject); - } - - if (mongoObject instanceof Date) { - return Parse._encode(mongoObject); - } - - if (mongoObject instanceof mongodb.Long) { - return mongoObject.toNumber(); - } - - if (mongoObject instanceof mongodb.Double) { - return mongoObject.value; - } - - if (BytesCoder.isValidDatabaseObject(mongoObject)) { - return BytesCoder.databaseToJSON(mongoObject); - } - - return _lodash2.default.mapValues(mongoObject, nestedMongoObjectToNestedParseObject); - default: - throw 'unknown js type'; - } -}; - -// Converts from a mongo-format object to a REST-format object. -// Does not strip out anything based on a lack of authentication. -var mongoObjectToParseObject = function mongoObjectToParseObject(className, mongoObject, schema) { - var key; - var authDataMatch; - var provider; - var newKey; - var objData; - var value; - - var _ret = function () { - switch (typeof mongoObject === 'undefined' ? 'undefined' : _typeof(mongoObject)) { - case 'string': - case 'number': - case 'boolean': - return { - v: mongoObject - }; - case 'undefined': - case 'symbol': - case 'function': - throw 'bad value in mongoObjectToParseObject'; - case 'object': - if (mongoObject === null) { - return { - v: null - }; - } - if (mongoObject instanceof Array) { - return { - v: mongoObject.map(nestedMongoObjectToNestedParseObject) - }; - } - - if (mongoObject instanceof Date) { - return { - v: Parse._encode(mongoObject) - }; - } - - if (mongoObject instanceof mongodb.Long) { - return { - v: mongoObject.toNumber() - }; - } - - if (mongoObject instanceof mongodb.Double) { - return { - v: mongoObject.value - }; - } - - if (BytesCoder.isValidDatabaseObject(mongoObject)) { - return { - v: BytesCoder.databaseToJSON(mongoObject) - }; - } - - var restObject = {}; - if (mongoObject._rperm || mongoObject._wperm) { - restObject._rperm = mongoObject._rperm || []; - restObject._wperm = mongoObject._wperm || []; - delete mongoObject._rperm; - delete mongoObject._wperm; - } - - for (key in mongoObject) { - switch (key) { - case '_id': - restObject['objectId'] = '' + mongoObject[key]; - break; - case '_hashed_password': - restObject._hashed_password = mongoObject[key]; - break; - case '_acl': - case '_email_verify_token': - case '_perishable_token': - case '_tombstone': - case '_email_verify_token_expires_at': - break; - case '_session_token': - restObject['sessionToken'] = mongoObject[key]; - break; - case 'updatedAt': - case '_updated_at': - restObject['updatedAt'] = Parse._encode(new Date(mongoObject[key])).iso; - break; - case 'createdAt': - case '_created_at': - restObject['createdAt'] = Parse._encode(new Date(mongoObject[key])).iso; - break; - case 'expiresAt': - case '_expiresAt': - restObject['expiresAt'] = Parse._encode(new Date(mongoObject[key])); - break; - default: - // Check other auth data keys - authDataMatch = key.match(/^_auth_data_([a-zA-Z0-9_]+)$/); - - if (authDataMatch) { - provider = authDataMatch[1]; - - restObject['authData'] = restObject['authData'] || {}; - restObject['authData'][provider] = mongoObject[key]; - break; - } - - if (key.indexOf('_p_') == 0) { - newKey = key.substring(3); - - if (!schema.fields[newKey]) { - _logger2.default.info('transform.js', 'Found a pointer column not in the schema, dropping it.', className, newKey); - break; - } - if (schema.fields[newKey].type !== 'Pointer') { - _logger2.default.info('transform.js', 'Found a pointer in a non-pointer column, dropping it.', className, key); - break; - } - if (mongoObject[key] === null) { - break; - } - objData = mongoObject[key].split('$'); - - if (objData[0] !== schema.fields[newKey].targetClass) { - throw 'pointer to incorrect className'; - } - restObject[newKey] = { - __type: 'Pointer', - className: objData[0], - objectId: objData[1] - }; - break; - } else if (key[0] == '_' && key != '__type') { - throw 'bad key in untransform: ' + key; - } else { - value = mongoObject[key]; - - if (schema.fields[key] && schema.fields[key].type === 'File' && FileCoder.isValidDatabaseObject(value)) { - restObject[key] = FileCoder.databaseToJSON(value); - break; - } - if (schema.fields[key] && schema.fields[key].type === 'GeoPoint' && GeoPointCoder.isValidDatabaseObject(value)) { - restObject[key] = GeoPointCoder.databaseToJSON(value); - break; - } - } - restObject[key] = nestedMongoObjectToNestedParseObject(mongoObject[key]); - } - } - - var relationFieldNames = Object.keys(schema.fields).filter(function (fieldName) { - return schema.fields[fieldName].type === 'Relation'; - }); - var relationFields = {}; - relationFieldNames.forEach(function (relationFieldName) { - relationFields[relationFieldName] = { - __type: 'Relation', - className: schema.fields[relationFieldName].targetClass - }; - }); - - return { - v: _extends({}, restObject, relationFields) - }; - default: - throw 'unknown js type'; - } - }(); - - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; -}; - -var DateCoder = { - JSONToDatabase: function JSONToDatabase(json) { - return new Date(json.iso); - }, - isValidJSON: function isValidJSON(value) { - return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'Date'; - } -}; - -var BytesCoder = { - databaseToJSON: function databaseToJSON(object) { - return { - __type: 'Bytes', - base64: object.buffer.toString('base64') - }; - }, - isValidDatabaseObject: function isValidDatabaseObject(object) { - return object instanceof mongodb.Binary; - }, - JSONToDatabase: function JSONToDatabase(json) { - return new mongodb.Binary(new Buffer(json.base64, 'base64')); - }, - isValidJSON: function isValidJSON(value) { - return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'Bytes'; - } -}; - -var GeoPointCoder = { - databaseToJSON: function databaseToJSON(object) { - return { - __type: 'GeoPoint', - latitude: object[1], - longitude: object[0] - }; - }, - isValidDatabaseObject: function isValidDatabaseObject(object) { - return object instanceof Array && object.length == 2; - }, - JSONToDatabase: function JSONToDatabase(json) { - return [json.longitude, json.latitude]; - }, - isValidJSON: function isValidJSON(value) { - return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'GeoPoint'; - } -}; - -var FileCoder = { - databaseToJSON: function databaseToJSON(object) { - return { - __type: 'File', - name: object - }; - }, - isValidDatabaseObject: function isValidDatabaseObject(object) { - return typeof object === 'string'; - }, - JSONToDatabase: function JSONToDatabase(json) { - return json.name; - }, - isValidJSON: function isValidJSON(value) { - return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && value.__type === 'File'; - } -}; - -module.exports = { - transformKey: transformKey, - parseObjectToMongoObjectForCreate: parseObjectToMongoObjectForCreate, - transformUpdate: transformUpdate, - transformWhere: transformWhere, - mongoObjectToParseObject: mongoObjectToParseObject -}; \ No newline at end of file diff --git a/lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js deleted file mode 100644 index 8c358621e5..0000000000 --- a/lib/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ /dev/null @@ -1,546 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } - -var pgp = require('pg-promise')(); - -var PostgresRelationDoesNotExistError = '42P01'; -var PostgresDuplicateRelationError = '42P07'; -var PostgresDuplicateColumnError = '42701'; -var PostgresUniqueIndexViolationError = '23505'; - -var parseTypeToPostgresType = function parseTypeToPostgresType(type) { - switch (type.type) { - case 'String': - return 'text'; - case 'Date': - return 'timestamp'; - case 'Object': - return 'jsonb'; - case 'Boolean': - return 'boolean'; - case 'Pointer': - return 'char(10)'; - case 'Number': - return 'double precision'; - case 'Array': - if (type.contents && type.contents.type === 'String') { - return 'text[]'; - } else { - return 'jsonb'; - } - default: - throw 'no type for ' + JSON.stringify(type) + ' yet'; - } -}; - -var buildWhereClause = function buildWhereClause(_ref) { - var schema = _ref.schema; - var query = _ref.query; - var index = _ref.index; - - var patterns = []; - var values = []; - for (var fieldName in query) { - var fieldValue = query[fieldName]; - if (typeof fieldValue === 'string') { - patterns.push('$' + index + ':name = $' + (index + 1)); - values.push(fieldName, fieldValue); - index += 2; - } else if (fieldValue.$ne) { - patterns.push('$' + index + ':name <> $' + (index + 1)); - values.push(fieldName, fieldValue.$ne); - index += 2; - } else if (fieldName === '$or') { - fieldValue.map(function (subQuery) { - return buildWhereClause({ schema: schema, query: subQuery, index: index }); - }).forEach(function (result) { - patterns.push(result.pattern); - values.push.apply(values, _toConsumableArray(result.values)); - }); - } else if (Array.isArray(fieldValue.$in) && schema.fields[fieldName].type === 'Array') { - (function () { - var inPatterns = []; - var allowNull = false; - values.push(fieldName); - fieldValue.$in.forEach(function (listElem, listIndex) { - if (listElem === null) { - allowNull = true; - } else { - values.push(listElem); - inPatterns.push('$' + (index + 1 + listIndex - (allowNull ? 1 : 0))); - } - }); - if (allowNull) { - patterns.push('($' + index + ':name IS NULL OR $' + index + ':name && ARRAY[' + inPatterns.join(',') + '])'); - } else { - patterns.push('$' + index + ':name && ARRAY[' + inPatterns.join(',') + ']'); - } - index = index + 1 + inPatterns.length; - })(); - } else if (Array.isArray(fieldValue.$in) && schema.fields[fieldName].type === 'String') { - (function () { - var inPatterns = []; - values.push(fieldName); - fieldValue.$in.forEach(function (listElem, listIndex) { - values.push(listElem); - inPatterns.push('$' + (index + 1 + listIndex)); - }); - patterns.push('$' + index + ':name IN (' + inPatterns.join(',') + ')'); - index = index + 1 + inPatterns.length; - })(); - } else if (fieldValue.__type === 'Pointer') { - patterns.push('$' + index + ':name = $' + (index + 1)); - values.push(fieldName, fieldValue.objectId); - index += 2; - } else { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres doesn\'t support this query type yet'); - } - } - return { pattern: patterns.join(' AND '), values: values }; -}; - -var PostgresStorageAdapter = exports.PostgresStorageAdapter = function () { - // Private - function PostgresStorageAdapter(_ref2) { - var uri = _ref2.uri; - var _ref2$collectionPrefi = _ref2.collectionPrefix; - var collectionPrefix = _ref2$collectionPrefi === undefined ? '' : _ref2$collectionPrefi; - - _classCallCheck(this, PostgresStorageAdapter); - - this._collectionPrefix = collectionPrefix; - this._client = pgp(uri); - } - - _createClass(PostgresStorageAdapter, [{ - key: '_ensureSchemaCollectionExists', - value: function _ensureSchemaCollectionExists() { - return this._client.query('CREATE TABLE "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )').catch(function (error) { - if (error.code === PostgresDuplicateRelationError) { - // Table already exists, must have been created by a different request. Ignore error. - } else { - throw error; - } - }); - } - }, { - key: 'classExists', - value: function classExists(name) { - return notImplemented(); - } - }, { - key: 'setClassLevelPermissions', - value: function setClassLevelPermissions(className, CLPs) { - return notImplemented(); - } - }, { - key: 'createClass', - value: function createClass(className, schema) { - var _this = this; - - var valuesArray = []; - var patternsArray = []; - Object.keys(schema.fields).forEach(function (fieldName, index) { - valuesArray.push(fieldName); - var parseType = schema.fields[fieldName]; - if (['_rperm', '_wperm'].includes(fieldName)) { - parseType.contents = { type: 'String' }; - } - valuesArray.push(parseTypeToPostgresType(parseType)); - patternsArray.push('$' + (index * 2 + 2) + ':name $' + (index * 2 + 3) + ':raw'); - }); - return this._ensureSchemaCollectionExists().then(function () { - return _this._client.query('CREATE TABLE $1:name (' + patternsArray.join(',') + ')', [className].concat(valuesArray)); - }).catch(function (error) { - if (error.code === PostgresDuplicateRelationError) { - // Table already exists, must have been created by a different request. Ignore error. - } else { - throw error; - } - }).then(function () { - return _this._client.query('INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($, $, true)', { className: className, schema: schema }); - }).then(function () { - return schema; - }); - } - }, { - key: 'addFieldIfNotExists', - value: function addFieldIfNotExists(className, fieldName, type) { - var _this2 = this; - - // TODO: Must be revised for invalid logic... - return this._client.tx("addFieldIfNotExists", function (t) { - return t.query('ALTER TABLE $ ADD COLUMN $ $', { - className: className, - fieldName: fieldName, - postgresType: parseTypeToPostgresType(type) - }).catch(function (error) { - if (error.code === PostgresRelationDoesNotExistError) { - return _this2.createClass(className, { fields: _defineProperty({}, fieldName, type) }); - } else if (error.code === PostgresDuplicateColumnError) { - // Column already exists, created by other request. Carry on to - // See if it's the right type. - } else { - throw error; - } - }).then(function () { - return t.query('SELECT "schema" FROM "_SCHEMA" WHERE "className" = $', { className: className }); - }).then(function (result) { - if (fieldName in result[0].schema) { - throw "Attempted to add a field that already exists"; - } else { - result[0].schema.fields[fieldName] = type; - return t.query('UPDATE "_SCHEMA" SET "schema"=$ WHERE "className"=$', { schema: result[0].schema, className: className }); - } - }); - }); - } - - // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) - // and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible. - - }, { - key: 'deleteClass', - value: function deleteClass(className) { - return notImplemented(); - } - - // Delete all data known to this adapter. Used for testing. - - }, { - key: 'deleteAllClasses', - value: function deleteAllClasses() { - var _this3 = this; - - return this._client.query('SELECT "className" FROM "_SCHEMA"').then(function (results) { - var classes = ['_SCHEMA'].concat(_toConsumableArray(results.map(function (result) { - return result.className; - }))); - return _this3._client.tx(function (t) { - return t.batch(classes.map(function (className) { - return t.none('DROP TABLE $', { className: className }); - })); - }); - }, function (error) { - if (error.code === PostgresRelationDoesNotExistError) { - // No _SCHEMA collection. Don't delete anything. - return; - } else { - throw error; - } - }); - } - - // Remove the column and all the data. For Relations, the _Join collection is handled - // specially, this function does not delete _Join columns. It should, however, indicate - // that the relation fields does not exist anymore. In mongo, this means removing it from - // the _SCHEMA collection. There should be no actual data in the collection under the same name - // as the relation column, so it's fine to attempt to delete it. If the fields listed to be - // deleted do not exist, this function should return successfully anyways. Checking for - // attempts to delete non-existent fields is the responsibility of Parse Server. - - // This function is not obligated to delete fields atomically. It is given the field - // names in a list so that databases that are capable of deleting fields atomically - // may do so. - - // Returns a Promise. - - }, { - key: 'deleteFields', - value: function deleteFields(className, schema, fieldNames) { - return notImplemented(); - } - - // Return a promise for all schemas known to this adapter, in Parse format. In case the - // schemas cannot be retrieved, returns a promise that rejects. Requirements for the - // rejection reason are TBD. - - }, { - key: 'getAllClasses', - value: function getAllClasses() { - var _this4 = this; - - return this._ensureSchemaCollectionExists().then(function () { - return _this4._client.map('SELECT * FROM "_SCHEMA"', null, function (row) { - return _extends({ className: row.className }, row.schema); - }); - }); - } - - // Return a promise for the schema with the given name, in Parse format. If - // this adapter doesn't know about the schema, return a promise that rejects with - // undefined as the reason. - - }, { - key: 'getClass', - value: function getClass(className) { - return this._client.query('SELECT * FROM "_SCHEMA" WHERE "className"=$', { className: className }).then(function (result) { - if (result.length === 1) { - return result[0].schema; - } else { - throw undefined; - } - }); - } - - // TODO: remove the mongo format dependency in the return value - - }, { - key: 'createObject', - value: function createObject(className, schema, object) { - var columnsArray = []; - var valuesArray = []; - Object.keys(object).forEach(function (fieldName) { - columnsArray.push(fieldName); - switch (schema.fields[fieldName].type) { - case 'Date': - valuesArray.push(object[fieldName].iso); - break; - case 'Pointer': - valuesArray.push(object[fieldName].objectId); - break; - case 'Array': - if (['_rperm', '_wperm'].includes(fieldName)) { - valuesArray.push(object[fieldName]); - } else { - valuesArray.push(JSON.stringify(object[fieldName])); - } - break; - case 'Object': - valuesArray.push(object[fieldName]); - break; - case 'String': - valuesArray.push(object[fieldName]); - break; - case 'Number': - valuesArray.push(object[fieldName]); - break; - case 'Boolean': - valuesArray.push(object[fieldName]); - break; - default: - throw 'Type ' + schema.fields[fieldName].type + ' not supported yet'; - break; - } - }); - var columnsPattern = columnsArray.map(function (col, index) { - return '$' + (index + 2) + ':name'; - }).join(','); - var valuesPattern = valuesArray.map(function (val, index) { - return '$' + (index + 2 + columnsArray.length) + (['_rperm', '_wperm'].includes(columnsArray[index]) ? '::text[]' : ''); - }).join(','); - var qs = 'INSERT INTO $1:name (' + columnsPattern + ') VALUES (' + valuesPattern + ')'; - var values = [className].concat(columnsArray, valuesArray); - return this._client.query(qs, values).then(function () { - return { ops: [object] }; - }).catch(function (error) { - if (error.code === PostgresUniqueIndexViolationError) { - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided'); - } else { - throw error; - } - }); - } - - // Remove all objects that match the given Parse Query. - // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. - // If there is some other error, reject with INTERNAL_SERVER_ERROR. - - }, { - key: 'deleteObjectsByQuery', - value: function deleteObjectsByQuery(className, schema, query) { - return this._client.one('WITH deleted AS (DELETE FROM $ RETURNING *) SELECT count(*) FROM deleted', { className: className }, function (res) { - return parseInt(res.count); - }).then(function (count) { - if (count === 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } else { - return count; - } - }); - } - - // Apply the update to all objects that match the given Parse Query. - - }, { - key: 'updateObjectsByQuery', - value: function updateObjectsByQuery(className, schema, query, update) { - return notImplemented(); - } - - // Return value not currently well specified. - - }, { - key: 'findOneAndUpdate', - value: function findOneAndUpdate(className, schema, query, update) { - var conditionPatterns = []; - var updatePatterns = []; - var values = [className]; - var index = 2; - - for (var fieldName in update) { - var fieldValue = update[fieldName]; - if (fieldValue.__op === 'Increment') { - updatePatterns.push('$' + index + ':name = COALESCE($' + index + ':name, 0) + $' + (index + 1)); - values.push(fieldName, fieldValue.amount); - index += 2; - } else if (fieldValue.__op === 'Add') { - updatePatterns.push('$' + index + ':name = COALESCE($' + index + ':name, \'[]\'::jsonb) || $' + (index + 1)); - values.push(fieldName, fieldValue.objects); - index += 2; - } else if (fieldValue.__op === 'Remove') { - return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres does not support Remove operator.')); - } else if (fieldValue.__op === 'AddUnique') { - return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres does not support AddUnique operator')); - } else if (fieldName === 'updatedAt') { - //TODO: stop special casing this. It should check for __type === 'Date' and use .iso - updatePatterns.push('$' + index + ':name = $' + (index + 1)); - values.push(fieldName, new Date(fieldValue)); - index += 2; - } else if (typeof fieldValue === 'string') { - updatePatterns.push('$' + index + ':name = $' + (index + 1)); - values.push(fieldName, fieldValue); - index += 2; - } else if (fieldValue.__type === 'Pointer') { - updatePatterns.push('$' + index + ':name = $' + (index + 1)); - values.push(fieldName, fieldValue.objectId); - index += 2; - } else { - return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Postgres doesn\'t support update ' + JSON.stringify(fieldValue) + ' yet')); - } - } - - var where = buildWhereClause({ schema: schema, index: index, query: query }); - values.push.apply(values, _toConsumableArray(where.values)); - - var qs = 'UPDATE $1:name SET ' + updatePatterns.join(',') + ' WHERE ' + where.pattern + ' RETURNING *'; - return this._client.query(qs, values).then(function (val) { - return val[0]; - }); // TODO: This is unsafe, verification is needed, or a different query method; - } - - // Hopefully, we can get rid of this. It's only used for config and hooks. - - }, { - key: 'upsertOneObject', - value: function upsertOneObject(className, schema, query, update) { - return notImplemented(); - } - }, { - key: 'find', - value: function find(className, schema, query, _ref3) { - var skip = _ref3.skip; - var limit = _ref3.limit; - var sort = _ref3.sort; - - var values = [className]; - var where = buildWhereClause({ schema: schema, query: query, index: 2 }); - values.push.apply(values, _toConsumableArray(where.values)); - - var wherePattern = where.pattern.length > 0 ? 'WHERE ' + where.pattern : ''; - var limitPattern = limit !== undefined ? 'LIMIT $' + (values.length + 1) : ''; - - var qs = 'SELECT * FROM $1:name ' + wherePattern + ' ' + limitPattern; - if (limit !== undefined) { - values.push(limit); - } - return this._client.query(qs, values).then(function (results) { - return results.map(function (object) { - Object.keys(schema.fields).filter(function (field) { - return schema.fields[field].type === 'Pointer'; - }).forEach(function (fieldName) { - object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass }; - }); - //TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field. - if (object.createdAt) { - object.createdAt = object.createdAt.toISOString(); - } - if (object.updatedAt) { - object.updatedAt = object.updatedAt.toISOString(); - } - if (object.expiresAt) { - object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() }; - } - if (object._email_verify_token_expires_at) { - object._email_verify_token_expires_at = { __type: 'Date', iso: object._email_verify_token_expires_at.toISOString() }; - } - - for (var fieldName in object) { - if (object[fieldName] === null) { - delete object[fieldName]; - } - if (object[fieldName] instanceof Date) { - object[fieldName] = { __type: 'Date', iso: object[fieldName].toISOString() }; - } - } - - return object; - }); - }); - } - - // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't - // currently know which fields are nullable and which aren't, we ignore that criteria. - // As such, we shouldn't expose this function to users of parse until we have an out-of-band - // Way of determining if a field is nullable. Undefined doesn't count against uniqueness, - // which is why we use sparse indexes. - - }, { - key: 'ensureUniqueness', - value: function ensureUniqueness(className, schema, fieldNames) { - // Use the same name for every ensureUniqueness attempt, because postgres - // Will happily create the same index with multiple names. - var constraintName = 'unique_' + fieldNames.sort().join('_'); - var constraintPatterns = fieldNames.map(function (fieldName, index) { - return '$' + (index + 3) + ':name'; - }); - var qs = 'ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (' + constraintPatterns.join(',') + ')'; - return this._client.query(qs, [className, constraintName].concat(_toConsumableArray(fieldNames))).catch(function (error) { - if (error.code === PostgresDuplicateRelationError && error.message.includes(constraintName)) { - // Index already exists. Ignore error. - } else { - throw error; - } - }); - } - - // Executes a count. - - }, { - key: 'count', - value: function count(className, schema, query) { - var values = [className]; - var where = buildWhereClause({ schema: schema, query: query, index: 2 }); - values.push.apply(values, _toConsumableArray(where.values)); - - var wherePattern = where.pattern.length > 0 ? 'WHERE ' + where.pattern : ''; - var qs = 'SELECT COUNT(*) FROM $1:name ' + wherePattern; - return this._client.query(qs, values).then(function (result) { - return parseInt(result[0].count); - }); - } - }]); - - return PostgresStorageAdapter; -}(); - -function notImplemented() { - return Promise.reject(new Error('Not implemented yet.')); -} - -exports.default = PostgresStorageAdapter; - -module.exports = PostgresStorageAdapter; // Required for tests \ No newline at end of file diff --git a/lib/Auth.js b/lib/Auth.js deleted file mode 100644 index 1176948a3e..0000000000 --- a/lib/Auth.js +++ /dev/null @@ -1,221 +0,0 @@ -'use strict'; - -function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } - -var deepcopy = require('deepcopy'); -var Parse = require('parse/node').Parse; -var RestQuery = require('./RestQuery'); - -// An Auth object tells you who is requesting something and whether -// the master key was used. -// userObject is a Parse.User and can be null if there's no user. -function Auth() { - var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var config = _ref.config; - var _ref$isMaster = _ref.isMaster; - var isMaster = _ref$isMaster === undefined ? false : _ref$isMaster; - var user = _ref.user; - var installationId = _ref.installationId; - - this.config = config; - this.installationId = installationId; - this.isMaster = isMaster; - this.user = user; - - // Assuming a users roles won't change during a single request, we'll - // only load them once. - this.userRoles = []; - this.fetchedRoles = false; - this.rolePromise = null; -} - -// Whether this auth could possibly modify the given user id. -// It still could be forbidden via ACLs even if this returns true. -Auth.prototype.couldUpdateUserId = function (userId) { - if (this.isMaster) { - return true; - } - if (this.user && this.user.id === userId) { - return true; - } - return false; -}; - -// A helper to get a master-level Auth object -function master(config) { - return new Auth({ config: config, isMaster: true }); -} - -// A helper to get a nobody-level Auth object -function nobody(config) { - return new Auth({ config: config, isMaster: false }); -} - -// Returns a promise that resolves to an Auth object -var getAuthForSessionToken = function getAuthForSessionToken() { - var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var config = _ref2.config; - var sessionToken = _ref2.sessionToken; - var installationId = _ref2.installationId; - - return config.cacheController.user.get(sessionToken).then(function (userJSON) { - if (userJSON) { - var cachedUser = Parse.Object.fromJSON(userJSON); - return Promise.resolve(new Auth({ config: config, isMaster: false, installationId: installationId, user: cachedUser })); - } - - var restOptions = { - limit: 1, - include: 'user' - }; - - var query = new RestQuery(config, master(config), '_Session', { sessionToken: sessionToken }, restOptions); - return query.execute().then(function (response) { - var results = response.results; - if (results.length !== 1 || !results[0]['user']) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); - } - - var now = new Date(), - expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined; - if (expiresAt < now) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.'); - } - var obj = results[0]['user']; - delete obj.password; - obj['className'] = '_User'; - obj['sessionToken'] = sessionToken; - config.cacheController.user.put(sessionToken, obj); - var userObject = Parse.Object.fromJSON(obj); - return new Auth({ config: config, isMaster: false, installationId: installationId, user: userObject }); - }); - }); -}; - -// Returns a promise that resolves to an array of role names -Auth.prototype.getUserRoles = function () { - if (this.isMaster || !this.user) { - return Promise.resolve([]); - } - if (this.fetchedRoles) { - return Promise.resolve(this.userRoles); - } - if (this.rolePromise) { - return this.rolePromise; - } - this.rolePromise = this._loadRoles(); - return this.rolePromise; -}; - -// Iterates through the role tree and compiles a users roles -Auth.prototype._loadRoles = function () { - var _this = this; - - var cacheAdapter = this.config.cacheController; - return cacheAdapter.role.get(this.user.id).then(function (cachedRoles) { - if (cachedRoles != null) { - _this.fetchedRoles = true; - _this.userRoles = cachedRoles; - return Promise.resolve(cachedRoles); - } - - var restWhere = { - 'users': { - __type: 'Pointer', - className: '_User', - objectId: _this.user.id - } - }; - // First get the role ids this user is directly a member of - var query = new RestQuery(_this.config, master(_this.config), '_Role', restWhere, {}); - return query.execute().then(function (response) { - var results = response.results; - if (!results.length) { - _this.userRoles = []; - _this.fetchedRoles = true; - _this.rolePromise = null; - - cacheAdapter.role.put(_this.user.id, _this.userRoles); - return Promise.resolve(_this.userRoles); - } - var rolesMap = results.reduce(function (m, r) { - m.names.push(r.name); - m.ids.push(r.objectId); - return m; - }, { ids: [], names: [] }); - - // run the recursive finding - return _this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names).then(function (roleNames) { - _this.userRoles = roleNames.map(function (r) { - return 'role:' + r; - }); - _this.fetchedRoles = true; - _this.rolePromise = null; - - cacheAdapter.role.put(_this.user.id, _this.userRoles); - return Promise.resolve(_this.userRoles); - }); - }); - }); -}; - -// Given a list of roleIds, find all the parent roles, returns a promise with all names -Auth.prototype._getAllRolesNamesForRoleIds = function (roleIDs) { - var _this2 = this; - - var names = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; - var queriedRoles = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - - var ins = roleIDs.filter(function (roleID) { - return queriedRoles[roleID] !== true; - }).map(function (roleID) { - // mark as queried - queriedRoles[roleID] = true; - return { - __type: 'Pointer', - className: '_Role', - objectId: roleID - }; - }); - - // all roles are accounted for, return the names - if (ins.length == 0) { - return Promise.resolve([].concat(_toConsumableArray(new Set(names)))); - } - // Build an OR query across all parentRoles - var restWhere = void 0; - if (ins.length == 1) { - restWhere = { 'roles': ins[0] }; - } else { - restWhere = { 'roles': { '$in': ins } }; - } - var query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {}); - return query.execute().then(function (response) { - var results = response.results; - // Nothing found - if (!results.length) { - return Promise.resolve(names); - } - // Map the results with all Ids and names - var resultMap = results.reduce(function (memo, role) { - memo.names.push(role.name); - memo.ids.push(role.objectId); - return memo; - }, { ids: [], names: [] }); - // store the new found names - names = names.concat(resultMap.names); - // find the next ones, circular roles will be cut - return _this2._getAllRolesNamesForRoleIds(resultMap.ids, names, queriedRoles); - }).then(function (names) { - return Promise.resolve([].concat(_toConsumableArray(new Set(names)))); - }); -}; - -module.exports = { - Auth: Auth, - master: master, - nobody: nobody, - getAuthForSessionToken: getAuthForSessionToken -}; \ No newline at end of file diff --git a/lib/ClientSDK.js b/lib/ClientSDK.js deleted file mode 100644 index ea73b24ccf..0000000000 --- a/lib/ClientSDK.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var semver = require('semver'); - -function compatible(compatibleSDK) { - return function (clientSDK) { - if (typeof clientSDK === 'string') { - clientSDK = fromString(clientSDK); - } - // REST API, or custom SDK - if (!clientSDK) { - return true; - } - var clientVersion = clientSDK.version; - var compatiblityVersion = compatibleSDK[clientSDK.sdk]; - return semver.satisfies(clientVersion, compatiblityVersion); - }; -} - -function supportsForwardDelete(clientSDK) { - return compatible({ - js: '>=1.9.0' - })(clientSDK); -} - -function fromString(version) { - var versionRE = /([-a-zA-Z]+)([0-9\.]+)/; - var match = version.toLowerCase().match(versionRE); - if (match && match.length === 3) { - return { - sdk: match[1], - version: match[2] - }; - } - return undefined; -} - -module.exports = { - compatible: compatible, - supportsForwardDelete: supportsForwardDelete, - fromString: fromString -}; \ No newline at end of file diff --git a/lib/Config.js b/lib/Config.js deleted file mode 100644 index 5fb2fffa36..0000000000 --- a/lib/Config.js +++ /dev/null @@ -1,224 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Config = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // A Config object provides information about how a specific app is -// configured. -// mount is the URL for the root of the API; includes http, domain, etc. - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -var _SchemaCache = require('./Controllers/SchemaCache'); - -var _SchemaCache2 = _interopRequireDefault(_SchemaCache); - -var _DatabaseController = require('./Controllers/DatabaseController'); - -var _DatabaseController2 = _interopRequireDefault(_DatabaseController); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function removeTrailingSlash(str) { - if (!str) { - return str; - } - if (str.endsWith("/")) { - str = str.substr(0, str.length - 1); - } - return str; -} - -var Config = exports.Config = function () { - function Config(applicationId, mount) { - _classCallCheck(this, Config); - - var cacheInfo = _cache2.default.get(applicationId); - if (!cacheInfo) { - return; - } - - this.applicationId = applicationId; - this.jsonLogs = cacheInfo.jsonLogs; - this.masterKey = cacheInfo.masterKey; - this.clientKey = cacheInfo.clientKey; - this.javascriptKey = cacheInfo.javascriptKey; - this.dotNetKey = cacheInfo.dotNetKey; - this.restAPIKey = cacheInfo.restAPIKey; - this.webhookKey = cacheInfo.webhookKey; - this.fileKey = cacheInfo.fileKey; - this.facebookAppIds = cacheInfo.facebookAppIds; - this.allowClientClassCreation = cacheInfo.allowClientClassCreation; - - // Create a new DatabaseController per request - if (cacheInfo.databaseController) { - var schemaCache = new _SchemaCache2.default(cacheInfo.cacheController, cacheInfo.schemaCacheTTL); - this.database = new _DatabaseController2.default(cacheInfo.databaseController.adapter, schemaCache); - } - - this.schemaCacheTTL = cacheInfo.schemaCacheTTL; - - this.serverURL = cacheInfo.serverURL; - this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL); - this.verifyUserEmails = cacheInfo.verifyUserEmails; - this.preventLoginWithUnverifiedEmail = cacheInfo.preventLoginWithUnverifiedEmail; - this.emailVerifyTokenValidityDuration = cacheInfo.emailVerifyTokenValidityDuration; - this.appName = cacheInfo.appName; - - this.analyticsController = cacheInfo.analyticsController; - this.cacheController = cacheInfo.cacheController; - this.hooksController = cacheInfo.hooksController; - this.filesController = cacheInfo.filesController; - this.pushController = cacheInfo.pushController; - this.loggerController = cacheInfo.loggerController; - this.userController = cacheInfo.userController; - this.authDataManager = cacheInfo.authDataManager; - this.customPages = cacheInfo.customPages || {}; - this.mount = removeTrailingSlash(mount); - this.liveQueryController = cacheInfo.liveQueryController; - this.sessionLength = cacheInfo.sessionLength; - this.expireInactiveSessions = cacheInfo.expireInactiveSessions; - this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this); - this.generateEmailVerifyTokenExpiresAt = this.generateEmailVerifyTokenExpiresAt.bind(this); - this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset; - } - - _createClass(Config, [{ - key: 'generateEmailVerifyTokenExpiresAt', - value: function generateEmailVerifyTokenExpiresAt() { - if (!this.verifyUserEmails || !this.emailVerifyTokenValidityDuration) { - return undefined; - } - var now = new Date(); - return new Date(now.getTime() + this.emailVerifyTokenValidityDuration * 1000); - } - }, { - key: 'generateSessionExpiresAt', - value: function generateSessionExpiresAt() { - if (!this.expireInactiveSessions) { - return undefined; - } - var now = new Date(); - return new Date(now.getTime() + this.sessionLength * 1000); - } - }, { - key: 'mount', - get: function get() { - var mount = this._mount; - if (this.publicServerURL) { - mount = this.publicServerURL; - } - return mount; - }, - set: function set(newValue) { - this._mount = newValue; - } - }, { - key: 'invalidLinkURL', - get: function get() { - return this.customPages.invalidLink || this.publicServerURL + '/apps/invalid_link.html'; - } - }, { - key: 'verifyEmailSuccessURL', - get: function get() { - return this.customPages.verifyEmailSuccess || this.publicServerURL + '/apps/verify_email_success.html'; - } - }, { - key: 'choosePasswordURL', - get: function get() { - return this.customPages.choosePassword || this.publicServerURL + '/apps/choose_password'; - } - }, { - key: 'requestResetPasswordURL', - get: function get() { - return this.publicServerURL + '/apps/' + this.applicationId + '/request_password_reset'; - } - }, { - key: 'passwordResetSuccessURL', - get: function get() { - return this.customPages.passwordResetSuccess || this.publicServerURL + '/apps/password_reset_success.html'; - } - }, { - key: 'verifyEmailURL', - get: function get() { - return this.publicServerURL + '/apps/' + this.applicationId + '/verify_email'; - } - }], [{ - key: 'validate', - value: function validate(_ref) { - var verifyUserEmails = _ref.verifyUserEmails; - var userController = _ref.userController; - var appName = _ref.appName; - var publicServerURL = _ref.publicServerURL; - var revokeSessionOnPasswordReset = _ref.revokeSessionOnPasswordReset; - var expireInactiveSessions = _ref.expireInactiveSessions; - var sessionLength = _ref.sessionLength; - var emailVerifyTokenValidityDuration = _ref.emailVerifyTokenValidityDuration; - - var emailAdapter = userController.adapter; - if (verifyUserEmails) { - this.validateEmailConfiguration({ emailAdapter: emailAdapter, appName: appName, publicServerURL: publicServerURL, emailVerifyTokenValidityDuration: emailVerifyTokenValidityDuration }); - } - - if (typeof revokeSessionOnPasswordReset !== 'boolean') { - throw 'revokeSessionOnPasswordReset must be a boolean value'; - } - - if (publicServerURL) { - if (!publicServerURL.startsWith("http://") && !publicServerURL.startsWith("https://")) { - throw "publicServerURL should be a valid HTTPS URL starting with https://"; - } - } - - this.validateSessionConfiguration(sessionLength, expireInactiveSessions); - } - }, { - key: 'validateEmailConfiguration', - value: function validateEmailConfiguration(_ref2) { - var emailAdapter = _ref2.emailAdapter; - var appName = _ref2.appName; - var publicServerURL = _ref2.publicServerURL; - var emailVerifyTokenValidityDuration = _ref2.emailVerifyTokenValidityDuration; - - if (!emailAdapter) { - throw 'An emailAdapter is required for e-mail verification and password resets.'; - } - if (typeof appName !== 'string') { - throw 'An app name is required for e-mail verification and password resets.'; - } - if (typeof publicServerURL !== 'string') { - throw 'A public server url is required for e-mail verification and password resets.'; - } - if (emailVerifyTokenValidityDuration) { - if (isNaN(emailVerifyTokenValidityDuration)) { - throw 'Email verify token validity duration must be a valid number.'; - } else if (emailVerifyTokenValidityDuration <= 0) { - throw 'Email verify token validity duration must be a value greater than 0.'; - } - } - } - }, { - key: 'validateSessionConfiguration', - value: function validateSessionConfiguration(sessionLength, expireInactiveSessions) { - if (expireInactiveSessions) { - if (isNaN(sessionLength)) { - throw 'Session length must be a valid number.'; - } else if (sessionLength <= 0) { - throw 'Session length must be a value greater than 0.'; - } - } - } - }]); - - return Config; -}(); - -exports.default = Config; - -module.exports = Config; \ No newline at end of file diff --git a/lib/Controllers/AdaptableController.js b/lib/Controllers/AdaptableController.js deleted file mode 100644 index 2cfa4f7212..0000000000 --- a/lib/Controllers/AdaptableController.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AdaptableController = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _Config = require("../Config"); - -var _Config2 = _interopRequireDefault(_Config); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -/* -AdaptableController.js - -AdaptableController is the base class for all controllers -that support adapter, -The super class takes care of creating the right instance for the adapter -based on the parameters passed - - */ - -// _adapter is private, use Symbol -var _adapter = Symbol(); - -var AdaptableController = exports.AdaptableController = function () { - function AdaptableController(adapter, appId, options) { - _classCallCheck(this, AdaptableController); - - this.options = options; - this.appId = appId; - this.adapter = adapter; - } - - _createClass(AdaptableController, [{ - key: "expectedAdapterType", - value: function expectedAdapterType() { - throw new Error("Subclasses should implement expectedAdapterType()"); - } - }, { - key: "validateAdapter", - value: function validateAdapter(adapter) { - if (!adapter) { - throw new Error(this.constructor.name + " requires an adapter"); - } - - var Type = this.expectedAdapterType(); - // Allow skipping for testing - if (!Type) { - return; - } - - // Makes sure the prototype matches - var mismatches = Object.getOwnPropertyNames(Type.prototype).reduce(function (obj, key) { - var adapterType = _typeof(adapter[key]); - var expectedType = _typeof(Type.prototype[key]); - if (adapterType !== expectedType) { - obj[key] = { - expected: expectedType, - actual: adapterType - }; - } - return obj; - }, {}); - - if (Object.keys(mismatches).length > 0) { - throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches); - } - } - }, { - key: "adapter", - set: function set(adapter) { - this.validateAdapter(adapter); - this[_adapter] = adapter; - }, - get: function get() { - return this[_adapter]; - } - }, { - key: "config", - get: function get() { - return new _Config2.default(this.appId); - } - }]); - - return AdaptableController; -}(); - -exports.default = AdaptableController; \ No newline at end of file diff --git a/lib/Controllers/AnalyticsController.js b/lib/Controllers/AnalyticsController.js deleted file mode 100644 index 304d132d2b..0000000000 --- a/lib/Controllers/AnalyticsController.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AnalyticsController = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _AdaptableController2 = require('./AdaptableController'); - -var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); - -var _AnalyticsAdapter = require('../Adapters/Analytics/AnalyticsAdapter'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var AnalyticsController = exports.AnalyticsController = function (_AdaptableController) { - _inherits(AnalyticsController, _AdaptableController); - - function AnalyticsController() { - _classCallCheck(this, AnalyticsController); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(AnalyticsController).apply(this, arguments)); - } - - _createClass(AnalyticsController, [{ - key: 'appOpened', - value: function appOpened(req) { - return this.adapter.appOpened(req.body, req).then(function (response) { - return { response: response }; - }).catch(function (err) { - return { response: {} }; - }); - } - }, { - key: 'trackEvent', - value: function trackEvent(req) { - return this.adapter.trackEvent(req.params.eventName, req.body, req).then(function (response) { - return { response: response }; - }).catch(function (err) { - return { response: {} }; - }); - } - }, { - key: 'expectedAdapterType', - value: function expectedAdapterType() { - return _AnalyticsAdapter.AnalyticsAdapter; - } - }]); - - return AnalyticsController; -}(_AdaptableController3.default); - -exports.default = AnalyticsController; \ No newline at end of file diff --git a/lib/Controllers/CacheController.js b/lib/Controllers/CacheController.js deleted file mode 100644 index 628a5cbeec..0000000000 --- a/lib/Controllers/CacheController.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.CacheController = exports.SubCache = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _AdaptableController2 = require('./AdaptableController'); - -var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); - -var _CacheAdapter = require('../Adapters/Cache/CacheAdapter'); - -var _CacheAdapter2 = _interopRequireDefault(_CacheAdapter); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var KEY_SEPARATOR_CHAR = ':'; - -function joinKeys() { - for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) { - keys[_key] = arguments[_key]; - } - - return keys.join(KEY_SEPARATOR_CHAR); -} - -/** - * Prefix all calls to the cache via a prefix string, useful when grouping Cache by object type. - * - * eg "Role" or "Session" - */ - -var SubCache = exports.SubCache = function () { - function SubCache(prefix, cacheController, ttl) { - _classCallCheck(this, SubCache); - - this.prefix = prefix; - this.cache = cacheController; - this.ttl = ttl; - } - - _createClass(SubCache, [{ - key: 'get', - value: function get(key) { - var cacheKey = joinKeys(this.prefix, key); - return this.cache.get(cacheKey); - } - }, { - key: 'put', - value: function put(key, value, ttl) { - var cacheKey = joinKeys(this.prefix, key); - return this.cache.put(cacheKey, value, ttl); - } - }, { - key: 'del', - value: function del(key) { - var cacheKey = joinKeys(this.prefix, key); - return this.cache.del(cacheKey); - } - }, { - key: 'clear', - value: function clear() { - return this.cache.clear(); - } - }]); - - return SubCache; -}(); - -var CacheController = exports.CacheController = function (_AdaptableController) { - _inherits(CacheController, _AdaptableController); - - function CacheController(adapter, appId) { - var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - - _classCallCheck(this, CacheController); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(CacheController).call(this, adapter, appId, options)); - - _this.role = new SubCache('role', _this); - _this.user = new SubCache('user', _this); - return _this; - } - - _createClass(CacheController, [{ - key: 'get', - value: function get(key) { - var cacheKey = joinKeys(this.appId, key); - return this.adapter.get(cacheKey).then(null, function () { - return Promise.resolve(null); - }); - } - }, { - key: 'put', - value: function put(key, value, ttl) { - var cacheKey = joinKeys(this.appId, key); - return this.adapter.put(cacheKey, value, ttl); - } - }, { - key: 'del', - value: function del(key) { - var cacheKey = joinKeys(this.appId, key); - return this.adapter.del(cacheKey); - } - }, { - key: 'clear', - value: function clear() { - return this.adapter.clear(); - } - }, { - key: 'expectedAdapterType', - value: function expectedAdapterType() { - return _CacheAdapter2.default; - } - }]); - - return CacheController; -}(_AdaptableController3.default); - -exports.default = CacheController; \ No newline at end of file diff --git a/lib/Controllers/DatabaseController.js b/lib/Controllers/DatabaseController.js deleted file mode 100644 index e58197f475..0000000000 --- a/lib/Controllers/DatabaseController.js +++ /dev/null @@ -1,1061 +0,0 @@ -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _intersect = require('intersect'); - -var _intersect2 = _interopRequireDefault(_intersect); - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // A database adapter that works with data exported from the hosted -// Parse database. - -var mongodb = require('mongodb'); -var Parse = require('parse/node').Parse; - -var SchemaController = require('./SchemaController'); - -var deepcopy = require('deepcopy'); - -function addWriteACL(query, acl) { - var newQuery = _lodash2.default.cloneDeep(query); - //Can't be any existing '_wperm' query, we don't allow client queries on that, no need to $and - newQuery._wperm = { "$in": [null].concat(_toConsumableArray(acl)) }; - return newQuery; -} - -function addReadACL(query, acl) { - var newQuery = _lodash2.default.cloneDeep(query); - //Can't be any existing '_rperm' query, we don't allow client queries on that, no need to $and - newQuery._rperm = { "$in": [null, "*"].concat(_toConsumableArray(acl)) }; - return newQuery; -} - -// Transforms a REST API formatted ACL object to our two-field mongo format. -var transformObjectACL = function transformObjectACL(_ref) { - var ACL = _ref.ACL; - - var result = _objectWithoutProperties(_ref, ['ACL']); - - if (!ACL) { - return result; - } - - result._wperm = []; - result._rperm = []; - - for (var entry in ACL) { - if (ACL[entry].read) { - result._rperm.push(entry); - } - if (ACL[entry].write) { - result._wperm.push(entry); - } - } - return result; -}; - -var specialQuerykeys = ['$and', '$or', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at']; -var validateQuery = function validateQuery(query) { - if (query.ACL) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); - } - - if (query.$or) { - if (query.$or instanceof Array) { - query.$or.forEach(validateQuery); - } else { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.'); - } - } - - if (query.$and) { - if (query.$and instanceof Array) { - query.$and.forEach(validateQuery); - } else { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $and format - use an array value.'); - } - } - - Object.keys(query).forEach(function (key) { - if (query && query[key] && query[key].$regex) { - if (typeof query[key].$options === 'string') { - if (!query[key].$options.match(/^[imxs]+$/)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $options value for query: ' + query[key].$options); - } - } - } - if (!specialQuerykeys.includes(key) && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid key name: ' + key); - } - }); -}; - -function DatabaseController(adapter, schemaCache) { - this.adapter = adapter; - this.schemaCache = schemaCache; - // We don't want a mutable this.schema, because then you could have - // one request that uses different schemas for different parts of - // it. Instead, use loadSchema to get a schema. - this.schemaPromise = null; -} - -DatabaseController.prototype.collectionExists = function (className) { - return this.adapter.classExists(className); -}; - -DatabaseController.prototype.purgeCollection = function (className) { - var _this = this; - - return this.loadSchema().then(function (schemaController) { - return schemaController.getOneSchema(className); - }).then(function (schema) { - return _this.adapter.deleteObjectsByQuery(className, schema, {}); - }); -}; - -DatabaseController.prototype.validateClassName = function (className) { - if (!SchemaController.classNameIsValid(className)) { - return Promise.reject(new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className)); - } - return Promise.resolve(); -}; - -// Returns a promise for a schemaController. -DatabaseController.prototype.loadSchema = function () { - var _this2 = this; - - var options = arguments.length <= 0 || arguments[0] === undefined ? { clearCache: false } : arguments[0]; - - if (!this.schemaPromise) { - this.schemaPromise = SchemaController.load(this.adapter, this.schemaCache, options); - this.schemaPromise.then(function () { - return delete _this2.schemaPromise; - }, function () { - return delete _this2.schemaPromise; - }); - } - return this.schemaPromise; -}; - -// Returns a promise for the classname that is related to the given -// classname through the key. -// TODO: make this not in the DatabaseController interface -DatabaseController.prototype.redirectClassNameForKey = function (className, key) { - return this.loadSchema().then(function (schema) { - var t = schema.getExpectedType(className, key); - if (t && t.type == 'Relation') { - return t.targetClass; - } else { - return className; - } - }); -}; - -// Uses the schema to validate the object (REST API format). -// Returns a promise that resolves to the new schema. -// This does not update this.schema, because in a situation like a -// batch request, that could confuse other users of the schema. -DatabaseController.prototype.validateObject = function (className, object, query, _ref2) { - var _this3 = this; - - var acl = _ref2.acl; - - var schema = void 0; - var isMaster = acl === undefined; - var aclGroup = acl || []; - return this.loadSchema().then(function (s) { - schema = s; - if (isMaster) { - return Promise.resolve(); - } - return _this3.canAddField(schema, className, object, aclGroup); - }).then(function () { - return schema.validateObject(className, object, query); - }); -}; - -// Filters out any data that shouldn't be on this REST-formatted object. -var filterSensitiveData = function filterSensitiveData(isMaster, aclGroup, className, object) { - if (className !== '_User') { - return object; - } - - object.password = object._hashed_password; - delete object._hashed_password; - - delete object.sessionToken; - - if (isMaster || aclGroup.indexOf(object.objectId) > -1) { - return object; - } - - delete object.authData; - - return object; -}; - -// Runs an update on the database. -// Returns a promise for an object with the new values for field -// modifications that don't know their results ahead of time, like -// 'increment'. -// Options: -// acl: a list of strings. If the object to be updated has an ACL, -// one of the provided strings must provide the caller with -// write permissions. -var specialKeysForUpdate = ['_hashed_password', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at']; -DatabaseController.prototype.update = function (className, query, update) { - var _this4 = this; - - var _ref3 = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; - - var acl = _ref3.acl; - var many = _ref3.many; - var upsert = _ref3.upsert; - var skipSanitization = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; - - var originalUpdate = update; - // Make a copy of the object, so we don't mutate the incoming data. - update = deepcopy(update); - - var isMaster = acl === undefined; - var aclGroup = acl || []; - var mongoUpdate; - return this.loadSchema().then(function (schemaController) { - return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update')).then(function () { - return _this4.handleRelationUpdates(className, query.objectId, update); - }).then(function () { - if (!isMaster) { - query = _this4.addPointerPermissions(schemaController, className, 'update', query, aclGroup); - } - if (!query) { - return Promise.resolve(); - } - if (acl) { - query = addWriteACL(query, acl); - } - validateQuery(query); - return schemaController.getOneSchema(className).catch(function (error) { - // If the schema doesn't exist, pretend it exists with no fields. This behaviour - // will likely need revisiting. - if (error === undefined) { - return { fields: {} }; - } - throw error; - }).then(function (schema) { - Object.keys(update).forEach(function (fieldName) { - if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name for update: ' + fieldName); - } - fieldName = fieldName.split('.')[0]; - if (!SchemaController.fieldNameIsValid(fieldName) && !specialKeysForUpdate.includes(fieldName)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name for update: ' + fieldName); - } - }); - for (var updateOperation in update) { - if (Object.keys(updateOperation).some(function (innerKey) { - return innerKey.includes('$') || innerKey.includes('.'); - })) { - throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters"); - } - } - update = transformObjectACL(update); - transformAuthData(className, update, schema); - if (many) { - return _this4.adapter.updateObjectsByQuery(className, schema, query, update); - } else if (upsert) { - return _this4.adapter.upsertOneObject(className, schema, query, update); - } else { - return _this4.adapter.findOneAndUpdate(className, schema, query, update); - } - }); - }).then(function (result) { - if (!result) { - return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); - } - if (skipSanitization) { - return Promise.resolve(result); - } - return sanitizeDatabaseResult(originalUpdate, result); - }); - }); -}; - -function sanitizeDatabaseResult(originalObject, result) { - var response = {}; - if (!result) { - return Promise.resolve(response); - } - Object.keys(originalObject).forEach(function (key) { - var keyUpdate = originalObject[key]; - // determine if that was an op - if (keyUpdate && (typeof keyUpdate === 'undefined' ? 'undefined' : _typeof(keyUpdate)) === 'object' && keyUpdate.__op && ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) { - // only valid ops that produce an actionable result - response[key] = result[key]; - } - }); - return Promise.resolve(response); -} - -// Processes relation-updating operations from a REST-format update. -// Returns a promise that resolves successfully when these are -// processed. -// This mutates update. -DatabaseController.prototype.handleRelationUpdates = function (className, objectId, update) { - var _this5 = this; - - var pending = []; - var deleteMe = []; - objectId = update.objectId || objectId; - - var process = function process(op, key) { - if (!op) { - return; - } - if (op.__op == 'AddRelation') { - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = op.objects[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var object = _step.value; - - pending.push(_this5.addRelation(key, className, objectId, object.objectId)); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - deleteMe.push(key); - } - - if (op.__op == 'RemoveRelation') { - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = op.objects[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var object = _step2.value; - - pending.push(_this5.removeRelation(key, className, objectId, object.objectId)); - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - - deleteMe.push(key); - } - - if (op.__op == 'Batch') { - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = op.ops[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var x = _step3.value; - - process(x, key); - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - } - }; - - for (var key in update) { - process(update[key], key); - } - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = deleteMe[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var key = _step4.value; - - delete update[key]; - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - return Promise.all(pending); -}; - -// Adds a relation. -// Returns a promise that resolves successfully iff the add was successful. -var relationSchema = { fields: { relatedId: { type: 'String' }, owningId: { type: 'String' } } }; -DatabaseController.prototype.addRelation = function (key, fromClassName, fromId, toId) { - var doc = { - relatedId: toId, - owningId: fromId - }; - return this.adapter.upsertOneObject('_Join:' + key + ':' + fromClassName, relationSchema, doc, doc); -}; - -// Removes a relation. -// Returns a promise that resolves successfully iff the remove was -// successful. -DatabaseController.prototype.removeRelation = function (key, fromClassName, fromId, toId) { - var doc = { - relatedId: toId, - owningId: fromId - }; - return this.adapter.deleteObjectsByQuery('_Join:' + key + ':' + fromClassName, relationSchema, doc).catch(function (error) { - // We don't care if they try to delete a non-existent relation. - if (error.code == Parse.Error.OBJECT_NOT_FOUND) { - return; - } - throw error; - }); -}; - -// Removes objects matches this query from the database. -// Returns a promise that resolves successfully iff the object was -// deleted. -// Options: -// acl: a list of strings. If the object to be updated has an ACL, -// one of the provided strings must provide the caller with -// write permissions. -DatabaseController.prototype.destroy = function (className, query) { - var _this6 = this; - - var _ref4 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - - var acl = _ref4.acl; - - var isMaster = acl === undefined; - var aclGroup = acl || []; - - return this.loadSchema().then(function (schemaController) { - return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete')).then(function () { - if (!isMaster) { - query = _this6.addPointerPermissions(schemaController, className, 'delete', query, aclGroup); - if (!query) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } - } - // delete by query - if (acl) { - query = addWriteACL(query, acl); - } - validateQuery(query); - return schemaController.getOneSchema(className).catch(function (error) { - // If the schema doesn't exist, pretend it exists with no fields. This behaviour - // will likely need revisiting. - if (error === undefined) { - return { fields: {} }; - } - throw error; - }).then(function (parseFormatSchema) { - return _this6.adapter.deleteObjectsByQuery(className, parseFormatSchema, query); - }).catch(function (error) { - // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions. - if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) { - return Promise.resolve({}); - } - throw error; - }); - }); - }); -}; - -var flattenUpdateOperatorsForCreate = function flattenUpdateOperatorsForCreate(object) { - for (var key in object) { - if (object[key] && object[key].__op) { - switch (object[key].__op) { - case 'Increment': - if (typeof object[key].amount !== 'number') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); - } - object[key] = object[key].amount; - break; - case 'Add': - if (!(object[key].objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); - } - object[key] = object[key].objects; - break; - case 'AddUnique': - if (!(object[key].objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); - } - object[key] = object[key].objects; - break; - case 'Remove': - if (!(object[key].objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); - } - object[key] = []; - break; - case 'Delete': - delete object[key]; - break; - default: - throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, 'The ' + object[key].__op + ' operator is not supported yet.'); - } - } - } -}; - -var transformAuthData = function transformAuthData(className, object, schema) { - if (object.authData && className === '_User') { - Object.keys(object.authData).forEach(function (provider) { - var providerData = object.authData[provider]; - var fieldName = '_auth_data_' + provider; - if (providerData == null) { - object[fieldName] = { - __op: 'Delete' - }; - } else { - object[fieldName] = providerData; - schema.fields[fieldName] = { type: 'Object' }; - } - }); - delete object.authData; - } -}; - -// Inserts an object into the database. -// Returns a promise that resolves successfully iff the object saved. -DatabaseController.prototype.create = function (className, object) { - var _this7 = this; - - var _ref5 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - - var acl = _ref5.acl; - - // Make a copy of the object, so we don't mutate the incoming data. - var originalObject = object; - object = transformObjectACL(object); - - object.createdAt = { iso: object.createdAt, __type: 'Date' }; - object.updatedAt = { iso: object.updatedAt, __type: 'Date' }; - - var isMaster = acl === undefined; - var aclGroup = acl || []; - - return this.validateClassName(className).then(function () { - return _this7.loadSchema(); - }).then(function (schemaController) { - return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create')).then(function () { - return _this7.handleRelationUpdates(className, null, object); - }).then(function () { - return schemaController.enforceClassExists(className); - }).then(function () { - return schemaController.reloadData(); - }).then(function () { - return schemaController.getOneSchema(className, true); - }).then(function (schema) { - transformAuthData(className, object, schema); - flattenUpdateOperatorsForCreate(object); - return _this7.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object); - }).then(function (result) { - return sanitizeDatabaseResult(originalObject, result.ops[0]); - }); - }); -}; - -DatabaseController.prototype.canAddField = function (schema, className, object, aclGroup) { - var classSchema = schema.data[className]; - if (!classSchema) { - return Promise.resolve(); - } - var fields = Object.keys(object); - var schemaFields = Object.keys(classSchema); - var newKeys = fields.filter(function (field) { - return schemaFields.indexOf(field) < 0; - }); - if (newKeys.length > 0) { - return schema.validatePermission(className, aclGroup, 'addField'); - } - return Promise.resolve(); -}; - -// Won't delete collections in the system namespace -// Returns a promise. -DatabaseController.prototype.deleteEverything = function () { - this.schemaPromise = null; - return this.adapter.deleteAllClasses(); -}; - -// Finds the keys in a query. Returns a Set. REST format only -function keysForQuery(query) { - var sublist = query['$and'] || query['$or']; - if (sublist) { - var answer = sublist.reduce(function (memo, subquery) { - return memo.concat(keysForQuery(subquery)); - }, []); - - return new Set(answer); - } - - return new Set(Object.keys(query)); -} - -// Returns a promise for a list of related ids given an owning id. -// className here is the owning className. -DatabaseController.prototype.relatedIds = function (className, key, owningId) { - return this.adapter.find(joinTableName(className, key), relationSchema, { owningId: owningId }, {}).then(function (results) { - return results.map(function (result) { - return result.relatedId; - }); - }); -}; - -// Returns a promise for a list of owning ids given some related ids. -// className here is the owning className. -DatabaseController.prototype.owningIds = function (className, key, relatedIds) { - return this.adapter.find(joinTableName(className, key), relationSchema, { relatedId: { '$in': relatedIds } }, {}).then(function (results) { - return results.map(function (result) { - return result.owningId; - }); - }); -}; - -// Modifies query so that it no longer has $in on relation fields, or -// equal-to-pointer constraints on relation fields. -// Returns a promise that resolves when query is mutated -DatabaseController.prototype.reduceInRelation = function (className, query, schema) { - var _this8 = this; - - // Search for an in-relation or equal-to-relation - // Make it sequential for now, not sure of paralleization side effects - if (query['$or']) { - var ors = query['$or']; - return Promise.all(ors.map(function (aQuery, index) { - return _this8.reduceInRelation(className, aQuery, schema).then(function (aQuery) { - query['$or'][index] = aQuery; - }); - })).then(function () { - return Promise.resolve(query); - }); - } - - var promises = Object.keys(query).map(function (key) { - if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) { - var t = schema.getExpectedType(className, key); - if (!t || t.type !== 'Relation') { - return Promise.resolve(query); - } - var relatedClassName = t.targetClass; - // Build the list of queries - var queries = Object.keys(query[key]).map(function (constraintKey) { - var relatedIds = void 0; - var isNegation = false; - if (constraintKey === 'objectId') { - relatedIds = [query[key].objectId]; - } else if (constraintKey == '$in') { - relatedIds = query[key]['$in'].map(function (r) { - return r.objectId; - }); - } else if (constraintKey == '$nin') { - isNegation = true; - relatedIds = query[key]['$nin'].map(function (r) { - return r.objectId; - }); - } else if (constraintKey == '$ne') { - isNegation = true; - relatedIds = [query[key]['$ne'].objectId]; - } else { - return; - } - return { - isNegation: isNegation, - relatedIds: relatedIds - }; - }); - - // remove the current queryKey as we don,t need it anymore - delete query[key]; - // execute each query independnently to build the list of - // $in / $nin - var _promises = queries.map(function (q) { - if (!q) { - return Promise.resolve(); - } - return _this8.owningIds(className, key, q.relatedIds).then(function (ids) { - if (q.isNegation) { - _this8.addNotInObjectIdsIds(ids, query); - } else { - _this8.addInObjectIdsIds(ids, query); - } - return Promise.resolve(); - }); - }); - - return Promise.all(_promises).then(function () { - return Promise.resolve(); - }); - } - return Promise.resolve(); - }); - - return Promise.all(promises).then(function () { - return Promise.resolve(query); - }); -}; - -// Modifies query so that it no longer has $relatedTo -// Returns a promise that resolves when query is mutated -DatabaseController.prototype.reduceRelationKeys = function (className, query) { - var _this9 = this; - - if (query['$or']) { - return Promise.all(query['$or'].map(function (aQuery) { - return _this9.reduceRelationKeys(className, aQuery); - })); - } - - var relatedTo = query['$relatedTo']; - if (relatedTo) { - return this.relatedIds(relatedTo.object.className, relatedTo.key, relatedTo.object.objectId).then(function (ids) { - delete query['$relatedTo']; - _this9.addInObjectIdsIds(ids, query); - return _this9.reduceRelationKeys(className, query); - }); - } -}; - -DatabaseController.prototype.addInObjectIdsIds = function () { - var ids = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; - var query = arguments[1]; - - var idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null; - var idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null; - var idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null; - - var allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(function (list) { - return list !== null; - }); - var totalLength = allIds.reduce(function (memo, list) { - return memo + list.length; - }, 0); - - var idsIntersection = []; - if (totalLength > 125) { - idsIntersection = _intersect2.default.big(allIds); - } else { - idsIntersection = (0, _intersect2.default)(allIds); - } - - // Need to make sure we don't clobber existing $lt or other constraints on objectId. - // Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints - // is expected though. - if (!('objectId' in query) || typeof query.objectId === 'string') { - query.objectId = {}; - } - query.objectId['$in'] = idsIntersection; - - return query; -}; - -DatabaseController.prototype.addNotInObjectIdsIds = function () { - var ids = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; - var query = arguments[1]; - - var idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null; - var allIds = [idsFromNin, ids].filter(function (list) { - return list !== null; - }); - var totalLength = allIds.reduce(function (memo, list) { - return memo + list.length; - }, 0); - - var idsIntersection = []; - if (totalLength > 125) { - idsIntersection = _intersect2.default.big(allIds); - } else { - idsIntersection = (0, _intersect2.default)(allIds); - } - - // Need to make sure we don't clobber existing $lt or other constraints on objectId. - // Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints - // is expected though. - if (!('objectId' in query) || typeof query.objectId === 'string') { - query.objectId = {}; - } - query.objectId['$nin'] = idsIntersection; - - return query; -}; - -// Runs a query on the database. -// Returns a promise that resolves to a list of items. -// Options: -// skip number of results to skip. -// limit limit to this number of results. -// sort an object where keys are the fields to sort by. -// the value is +1 for ascending, -1 for descending. -// count run a count instead of returning results. -// acl restrict this operation with an ACL for the provided array -// of user objectIds and roles. acl: null means no user. -// when this field is not present, don't do anything regarding ACLs. -// TODO: make userIds not needed here. The db adapter shouldn't know -// anything about users, ideally. Then, improve the format of the ACL -// arg to work like the others. -DatabaseController.prototype.find = function (className, query) { - var _this10 = this; - - var _ref6 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - - var skip = _ref6.skip; - var limit = _ref6.limit; - var acl = _ref6.acl; - var _ref6$sort = _ref6.sort; - var sort = _ref6$sort === undefined ? {} : _ref6$sort; - var count = _ref6.count; - - var isMaster = acl === undefined; - var aclGroup = acl || []; - var op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find'; - var classExists = true; - return this.loadSchema().then(function (schemaController) { - //Allow volatile classes if querying with Master (for _PushStatus) - //TODO: Move volatile classes concept into mongo adatper, postgres adapter shouldn't care - //that api.parse.com breaks when _PushStatus exists in mongo. - return schemaController.getOneSchema(className, isMaster).catch(function (error) { - // Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much. - // For now, pretend the class exists but has no objects, - if (error === undefined) { - classExists = false; - return { fields: {} }; - } - throw error; - }).then(function (schema) { - // Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt, - // so duplicate that behaviour here. If both are specified, the corrent behaviour to match Parse.com is to - // use the one that appears first in the sort list. - if (sort._created_at) { - sort.createdAt = sort._created_at; - delete sort._created_at; - } - if (sort._updated_at) { - sort.updatedAt = sort._updated_at; - delete sort._updated_at; - } - Object.keys(sort).forEach(function (fieldName) { - if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot sort by ' + fieldName); - } - if (!SchemaController.fieldNameIsValid(fieldName)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name: ' + fieldName + '.'); - } - }); - return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op)).then(function () { - return _this10.reduceRelationKeys(className, query); - }).then(function () { - return _this10.reduceInRelation(className, query, schemaController); - }).then(function () { - if (!isMaster) { - query = _this10.addPointerPermissions(schemaController, className, op, query, aclGroup); - } - if (!query) { - if (op == 'get') { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } else { - return []; - } - } - if (!isMaster) { - query = addReadACL(query, aclGroup); - } - validateQuery(query); - if (count) { - if (!classExists) { - return 0; - } else { - return _this10.adapter.count(className, schema, query); - } - } else { - if (!classExists) { - return []; - } else { - return _this10.adapter.find(className, schema, query, { skip: skip, limit: limit, sort: sort }).then(function (objects) { - return objects.map(function (object) { - object = untransformObjectACL(object); - return filterSensitiveData(isMaster, aclGroup, className, object); - }); - }); - } - } - }); - }); - }); -}; - -// Transforms a Database format ACL to a REST API format ACL -var untransformObjectACL = function untransformObjectACL(_ref7) { - var _rperm = _ref7._rperm; - var _wperm = _ref7._wperm; - - var output = _objectWithoutProperties(_ref7, ['_rperm', '_wperm']); - - if (_rperm || _wperm) { - output.ACL = {}; - - (_rperm || []).forEach(function (entry) { - if (!output.ACL[entry]) { - output.ACL[entry] = { read: true }; - } else { - output.ACL[entry]['read'] = true; - } - }); - - (_wperm || []).forEach(function (entry) { - if (!output.ACL[entry]) { - output.ACL[entry] = { write: true }; - } else { - output.ACL[entry]['write'] = true; - } - }); - } - return output; -}; - -DatabaseController.prototype.deleteSchema = function (className) { - var _this11 = this; - - return this.loadSchema(true).then(function (schemaController) { - return schemaController.getOneSchema(className, true); - }).catch(function (error) { - if (error === undefined) { - return { fields: {} }; - } else { - throw error; - } - }).then(function (schema) { - return _this11.collectionExists(className).then(function (exist) { - return _this11.adapter.count(className, { fields: {} }); - }).then(function (count) { - if (count > 0) { - throw new Parse.Error(255, 'Class ' + className + ' is not empty, contains ' + count + ' objects, cannot drop schema.'); - } - return _this11.adapter.deleteClass(className); - }).then(function (wasParseCollection) { - if (wasParseCollection) { - var relationFieldNames = Object.keys(schema.fields).filter(function (fieldName) { - return schema.fields[fieldName].type === 'Relation'; - }); - return Promise.all(relationFieldNames.map(function (name) { - return _this11.adapter.deleteClass(joinTableName(className, name)); - })); - } else { - return Promise.resolve(); - } - }); - }); -}; - -DatabaseController.prototype.addPointerPermissions = function (schema, className, operation, query) { - var aclGroup = arguments.length <= 4 || arguments[4] === undefined ? [] : arguments[4]; - - // Check if class has public permission for operation - // If the BaseCLP pass, let go through - if (schema.testBaseCLP(className, aclGroup, operation)) { - return query; - } - var perms = schema.perms[className]; - var field = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields'; - var userACL = aclGroup.filter(function (acl) { - return acl.indexOf('role:') != 0 && acl != '*'; - }); - // the ACL should have exactly 1 user - if (perms && perms[field] && perms[field].length > 0) { - var _ret = function () { - // No user set return undefined - if (userACL.length != 1) { - return { - v: void 0 - }; - } - var userId = userACL[0]; - var userPointer = { - "__type": "Pointer", - "className": "_User", - "objectId": userId - }; - - var constraints = {}; - var permFields = perms[field]; - var ors = permFields.map(function (key) { - var q = _defineProperty({}, key, userPointer); - return { '$and': [q, query] }; - }); - if (ors.length > 1) { - return { - v: { '$or': ors } - }; - } - return { - v: ors[0] - }; - }(); - - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; - } else { - return query; - } -}; - -function joinTableName(className, key) { - return '_Join:' + key + ':' + className; -} - -module.exports = DatabaseController; \ No newline at end of file diff --git a/lib/Controllers/FilesController.js b/lib/Controllers/FilesController.js deleted file mode 100644 index af653b83e7..0000000000 --- a/lib/Controllers/FilesController.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.FilesController = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _node = require('parse/node'); - -var _cryptoUtils = require('../cryptoUtils'); - -var _AdaptableController2 = require('./AdaptableController'); - -var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); - -var _FilesAdapter = require('../Adapters/Files/FilesAdapter'); - -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -var _mime = require('mime'); - -var _mime2 = _interopRequireDefault(_mime); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // FilesController.js - - -var legacyFilesRegex = new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*"); - -var FilesController = exports.FilesController = function (_AdaptableController) { - _inherits(FilesController, _AdaptableController); - - function FilesController() { - _classCallCheck(this, FilesController); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(FilesController).apply(this, arguments)); - } - - _createClass(FilesController, [{ - key: 'getFileData', - value: function getFileData(config, filename) { - return this.adapter.getFileData(filename); - } - }, { - key: 'createFile', - value: function createFile(config, filename, data, contentType) { - - var extname = _path2.default.extname(filename); - - var hasExtension = extname.length > 0; - - if (!hasExtension && contentType && _mime2.default.extension(contentType)) { - filename = filename + '.' + _mime2.default.extension(contentType); - } else if (hasExtension && !contentType) { - contentType = _mime2.default.lookup(filename); - } - - filename = (0, _cryptoUtils.randomHexString)(32) + '_' + filename; - - var location = this.adapter.getFileLocation(config, filename); - return this.adapter.createFile(filename, data, contentType).then(function () { - return Promise.resolve({ - url: location, - name: filename - }); - }); - } - }, { - key: 'deleteFile', - value: function deleteFile(config, filename) { - return this.adapter.deleteFile(filename); - } - - /** - * Find file references in REST-format object and adds the url key - * with the current mount point and app id. - * Object may be a single object or list of REST-format objects. - */ - - }, { - key: 'expandFilesInObject', - value: function expandFilesInObject(config, object) { - var _this2 = this; - - if (object instanceof Array) { - object.map(function (obj) { - return _this2.expandFilesInObject(config, obj); - }); - return; - } - if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object') { - return; - } - for (var key in object) { - var fileObject = object[key]; - if (fileObject && fileObject['__type'] === 'File') { - if (fileObject['url']) { - continue; - } - var filename = fileObject['name']; - // all filenames starting with "tfss-" should be from files.parsetfss.com - // all filenames starting with a "-" seperated UUID should be from files.parse.com - // all other filenames have been migrated or created from Parse Server - if (config.fileKey === undefined) { - fileObject['url'] = this.adapter.getFileLocation(config, filename); - } else { - if (filename.indexOf('tfss-') === 0) { - fileObject['url'] = 'http://files.parsetfss.com/' + config.fileKey + '/' + encodeURIComponent(filename); - } else if (legacyFilesRegex.test(filename)) { - fileObject['url'] = 'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename); - } else { - fileObject['url'] = this.adapter.getFileLocation(config, filename); - } - } - } - } - } - }, { - key: 'expectedAdapterType', - value: function expectedAdapterType() { - return _FilesAdapter.FilesAdapter; - } - }]); - - return FilesController; -}(_AdaptableController3.default); - -exports.default = FilesController; \ No newline at end of file diff --git a/lib/Controllers/HooksController.js b/lib/Controllers/HooksController.js deleted file mode 100644 index 64034d0ec3..0000000000 --- a/lib/Controllers/HooksController.js +++ /dev/null @@ -1,274 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HooksController = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** weak */ - -var _DatabaseAdapter = require("../DatabaseAdapter"); - -var DatabaseAdapter = _interopRequireWildcard(_DatabaseAdapter); - -var _triggers = require("../triggers"); - -var triggers = _interopRequireWildcard(_triggers); - -var _node = require("parse/node"); - -var Parse = _interopRequireWildcard(_node); - -var _request = require("request"); - -var request = _interopRequireWildcard(_request); - -var _logger = require("../logger"); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var DefaultHooksCollectionName = "_Hooks"; - -var HooksController = exports.HooksController = function () { - function HooksController(applicationId, databaseController, webhookKey) { - _classCallCheck(this, HooksController); - - this._applicationId = applicationId; - this._webhookKey = webhookKey; - this.database = databaseController; - } - - _createClass(HooksController, [{ - key: "load", - value: function load() { - var _this = this; - - return this._getHooks().then(function (hooks) { - hooks = hooks || []; - hooks.forEach(function (hook) { - _this.addHookToTriggers(hook); - }); - }); - } - }, { - key: "getFunction", - value: function getFunction(functionName) { - return this._getHooks({ functionName: functionName }, 1).then(function (results) { - return results[0]; - }); - } - }, { - key: "getFunctions", - value: function getFunctions() { - return this._getHooks({ functionName: { $exists: true } }); - } - }, { - key: "getTrigger", - value: function getTrigger(className, triggerName) { - return this._getHooks({ className: className, triggerName: triggerName }, 1).then(function (results) { - return results[0]; - }); - } - }, { - key: "getTriggers", - value: function getTriggers() { - return this._getHooks({ className: { $exists: true }, triggerName: { $exists: true } }); - } - }, { - key: "deleteFunction", - value: function deleteFunction(functionName) { - triggers.removeFunction(functionName, this._applicationId); - return this._removeHooks({ functionName: functionName }); - } - }, { - key: "deleteTrigger", - value: function deleteTrigger(className, triggerName) { - triggers.removeTrigger(triggerName, className, this._applicationId); - return this._removeHooks({ className: className, triggerName: triggerName }); - } - }, { - key: "_getHooks", - value: function _getHooks() { - var query = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var limit = arguments[1]; - - var options = limit ? { limit: limit } : undefined; - return this.database.find(DefaultHooksCollectionName, query).then(function (results) { - return results.map(function (result) { - delete result.objectId; - return result; - }); - }); - } - }, { - key: "_removeHooks", - value: function _removeHooks(query) { - return this.database.destroy(DefaultHooksCollectionName, query).then(function () { - return Promise.resolve({}); - }); - } - }, { - key: "saveHook", - value: function saveHook(hook) { - var query; - if (hook.functionName && hook.url) { - query = { functionName: hook.functionName }; - } else if (hook.triggerName && hook.className && hook.url) { - query = { className: hook.className, triggerName: hook.triggerName }; - } else { - throw new Parse.Error(143, "invalid hook declaration"); - } - return this.database.update(DefaultHooksCollectionName, query, hook, { upsert: true }).then(function () { - return Promise.resolve(hook); - }); - } - }, { - key: "addHookToTriggers", - value: function addHookToTriggers(hook) { - var wrappedFunction = wrapToHTTPRequest(hook, this._webhookKey); - wrappedFunction.url = hook.url; - if (hook.className) { - triggers.addTrigger(hook.triggerName, hook.className, wrappedFunction, this._applicationId); - } else { - triggers.addFunction(hook.functionName, wrappedFunction, null, this._applicationId); - } - } - }, { - key: "addHook", - value: function addHook(hook) { - this.addHookToTriggers(hook); - return this.saveHook(hook); - } - }, { - key: "createOrUpdateHook", - value: function createOrUpdateHook(aHook) { - var hook; - if (aHook && aHook.functionName && aHook.url) { - hook = {}; - hook.functionName = aHook.functionName; - hook.url = aHook.url; - } else if (aHook && aHook.className && aHook.url && aHook.triggerName && triggers.Types[aHook.triggerName]) { - hook = {}; - hook.className = aHook.className; - hook.url = aHook.url; - hook.triggerName = aHook.triggerName; - } else { - throw new Parse.Error(143, "invalid hook declaration"); - } - - return this.addHook(hook); - } - }, { - key: "createHook", - value: function createHook(aHook) { - var _this2 = this; - - if (aHook.functionName) { - return this.getFunction(aHook.functionName).then(function (result) { - if (result) { - throw new Parse.Error(143, "function name: " + aHook.functionName + " already exits"); - } else { - return _this2.createOrUpdateHook(aHook); - } - }); - } else if (aHook.className && aHook.triggerName) { - return this.getTrigger(aHook.className, aHook.triggerName).then(function (result) { - if (result) { - throw new Parse.Error(143, "class " + aHook.className + " already has trigger " + aHook.triggerName); - } - return _this2.createOrUpdateHook(aHook); - }); - } - - throw new Parse.Error(143, "invalid hook declaration"); - } - }, { - key: "updateHook", - value: function updateHook(aHook) { - var _this3 = this; - - if (aHook.functionName) { - return this.getFunction(aHook.functionName).then(function (result) { - if (result) { - return _this3.createOrUpdateHook(aHook); - } - throw new Parse.Error(143, "no function named: " + aHook.functionName + " is defined"); - }); - } else if (aHook.className && aHook.triggerName) { - return this.getTrigger(aHook.className, aHook.triggerName).then(function (result) { - if (result) { - return _this3.createOrUpdateHook(aHook); - } - throw new Parse.Error(143, "class " + aHook.className + " does not exist"); - }); - } - throw new Parse.Error(143, "invalid hook declaration"); - } - }]); - - return HooksController; -}(); - -function wrapToHTTPRequest(hook, key) { - return function (req, res) { - var jsonBody = {}; - for (var i in req) { - jsonBody[i] = req[i]; - } - if (req.object) { - jsonBody.object = req.object.toJSON(); - jsonBody.object.className = req.object.className; - } - if (req.original) { - jsonBody.original = req.original.toJSON(); - jsonBody.original.className = req.original.className; - } - var jsonRequest = { - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(jsonBody) - }; - - if (key) { - jsonRequest.headers['X-Parse-Webhook-Key'] = key; - } else { - _logger.logger.warn('Making outgoing webhook request without webhookKey being set!'); - } - - request.post(hook.url, jsonRequest, function (err, httpResponse, body) { - var result; - if (body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } catch (e) { - err = { error: "Malformed response", code: -1 }; - } - } - if (!err) { - result = body.success; - err = body.error; - } - } - - if (err) { - return res.error(err); - } else if (hook.triggerName === 'beforeSave') { - if ((typeof result === "undefined" ? "undefined" : _typeof(result)) === 'object') { - delete result.createdAt; - delete result.updatedAt; - } - return res.success({ object: result }); - } else { - return res.success(result); - } - }); - }; -} - -exports.default = HooksController; \ No newline at end of file diff --git a/lib/Controllers/LiveQueryController.js b/lib/Controllers/LiveQueryController.js deleted file mode 100644 index 7ecb1c6113..0000000000 --- a/lib/Controllers/LiveQueryController.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.LiveQueryController = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _ParseCloudCodePublisher = require('../LiveQuery/ParseCloudCodePublisher'); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var LiveQueryController = exports.LiveQueryController = function () { - function LiveQueryController(config) { - _classCallCheck(this, LiveQueryController); - - var classNames = void 0; - // If config is empty, we just assume no classs needs to be registered as LiveQuery - if (!config || !config.classNames) { - this.classNames = new Set(); - } else if (config.classNames instanceof Array) { - this.classNames = new Set(config.classNames); - } else { - throw 'liveQuery.classes should be an array of string'; - } - this.liveQueryPublisher = new _ParseCloudCodePublisher.ParseCloudCodePublisher(config); - } - - _createClass(LiveQueryController, [{ - key: 'onAfterSave', - value: function onAfterSave(className, currentObject, originalObject) { - if (!this.hasLiveQuery(className)) { - return; - } - var req = this._makePublisherRequest(currentObject, originalObject); - this.liveQueryPublisher.onCloudCodeAfterSave(req); - } - }, { - key: 'onAfterDelete', - value: function onAfterDelete(className, currentObject, originalObject) { - if (!this.hasLiveQuery(className)) { - return; - } - var req = this._makePublisherRequest(currentObject, originalObject); - this.liveQueryPublisher.onCloudCodeAfterDelete(req); - } - }, { - key: 'hasLiveQuery', - value: function hasLiveQuery(className) { - return this.classNames.has(className); - } - }, { - key: '_makePublisherRequest', - value: function _makePublisherRequest(currentObject, originalObject) { - var req = { - object: currentObject - }; - if (currentObject) { - req.original = originalObject; - } - return req; - } - }]); - - return LiveQueryController; -}(); - -exports.default = LiveQueryController; \ No newline at end of file diff --git a/lib/Controllers/LoggerController.js b/lib/Controllers/LoggerController.js deleted file mode 100644 index 37508e4d61..0000000000 --- a/lib/Controllers/LoggerController.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.LoggerController = exports.LogOrder = exports.LogLevel = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _node = require('parse/node'); - -var _PromiseRouter = require('../PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _AdaptableController2 = require('./AdaptableController'); - -var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); - -var _LoggerAdapter = require('../Adapters/Logger/LoggerAdapter'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; - -var LogLevel = exports.LogLevel = { - INFO: 'info', - ERROR: 'error' -}; - -var LogOrder = exports.LogOrder = { - DESCENDING: 'desc', - ASCENDING: 'asc' -}; - -var LoggerController = exports.LoggerController = function (_AdaptableController) { - _inherits(LoggerController, _AdaptableController); - - function LoggerController() { - _classCallCheck(this, LoggerController); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(LoggerController).apply(this, arguments)); - } - - _createClass(LoggerController, [{ - key: 'getLogs', - - - // Returns a promise for a {response} object. - // query params: - // level (optional) Level of logging you want to query for (info || error) - // from (optional) Start time for the search. Defaults to 1 week ago. - // until (optional) End time for the search. Defaults to current time. - // order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”. - // size (optional) Number of rows returned by search. Defaults to 10 - value: function getLogs() { - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - if (!this.adapter) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not availabe'); - } - options = LoggerController.parseOptions(options); - return this.adapter.query(options); - } - }, { - key: 'expectedAdapterType', - value: function expectedAdapterType() { - return _LoggerAdapter.LoggerAdapter; - } - }], [{ - key: 'validDateTime', - - - // check that date input is valid - value: function validDateTime(date) { - if (!date) { - return null; - } - date = new Date(date); - - if (!isNaN(date.getTime())) { - return date; - } - - return null; - } - }, { - key: 'parseOptions', - value: function parseOptions() { - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var from = LoggerController.validDateTime(options.from) || new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY); - var until = LoggerController.validDateTime(options.until) || new Date(); - var size = Number(options.size) || 10; - var order = options.order || LogOrder.DESCENDING; - var level = options.level || LogLevel.INFO; - - return { - from: from, - until: until, - size: size, - order: order, - level: level - }; - } - }]); - - return LoggerController; -}(_AdaptableController3.default); - -exports.default = LoggerController; \ No newline at end of file diff --git a/lib/Controllers/PushController.js b/lib/Controllers/PushController.js deleted file mode 100644 index a84c0cc60f..0000000000 --- a/lib/Controllers/PushController.js +++ /dev/null @@ -1,258 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PushController = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _node = require('parse/node'); - -var _PromiseRouter = require('../PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -var _AdaptableController2 = require('./AdaptableController'); - -var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); - -var _PushAdapter = require('../Adapters/Push/PushAdapter'); - -var _deepcopy = require('deepcopy'); - -var _deepcopy2 = _interopRequireDefault(_deepcopy); - -var _RestQuery = require('../RestQuery'); - -var _RestQuery2 = _interopRequireDefault(_RestQuery); - -var _RestWrite = require('../RestWrite'); - -var _RestWrite2 = _interopRequireDefault(_RestWrite); - -var _Auth = require('../Auth'); - -var _pushStatusHandler = require('../pushStatusHandler'); - -var _pushStatusHandler2 = _interopRequireDefault(_pushStatusHandler); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var FEATURE_NAME = 'push'; -var UNSUPPORTED_BADGE_KEY = "unsupported"; - -var PushController = exports.PushController = function (_AdaptableController) { - _inherits(PushController, _AdaptableController); - - function PushController() { - _classCallCheck(this, PushController); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(PushController).apply(this, arguments)); - } - - _createClass(PushController, [{ - key: 'sendPush', - value: function sendPush() { - var body = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var where = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - var config = arguments[2]; - - var _this2 = this; - - var auth = arguments[3]; - var onPushStatusSaved = arguments.length <= 4 || arguments[4] === undefined ? function () {} : arguments[4]; - - var pushAdapter = this.adapter; - if (!this.pushIsAvailable) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Push adapter is not available'); - } - if (!this.options) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Missing push configuration'); - } - PushController.validatePushType(where, pushAdapter.getValidPushTypes()); - // Replace the expiration_time with a valid Unix epoch milliseconds time - body['expiration_time'] = PushController.getExpirationTime(body); - // TODO: If the req can pass the checking, we return immediately instead of waiting - // pushes to be sent. We probably change this behaviour in the future. - var badgeUpdate = function badgeUpdate() { - return Promise.resolve(); - }; - if (body.data && body.data.badge) { - (function () { - var badge = body.data.badge; - var restUpdate = {}; - if (typeof badge == 'string' && badge.toLowerCase() === 'increment') { - restUpdate = { badge: { __op: 'Increment', amount: 1 } }; - } else if (Number(badge)) { - restUpdate = { badge: badge }; - } else { - throw "Invalid value for badge, expected number or 'Increment'"; - } - var updateWhere = (0, _deepcopy2.default)(where); - - badgeUpdate = function badgeUpdate() { - if (updateWhere.arn) { - return; - } - updateWhere.deviceType = 'ios'; - // Build a real RestQuery so we can use it in RestWrite - var restQuery = new _RestQuery2.default(config, (0, _Auth.master)(config), '_Installation', updateWhere); - return restQuery.buildRestWhere().then(function () { - var write = new _RestWrite2.default(config, (0, _Auth.master)(config), '_Installation', restQuery.restWhere, restUpdate); - write.runOptions.many = true; - return write.execute(); - }); - }; - })(); - } - var pushStatus = (0, _pushStatusHandler2.default)(config); - return Promise.resolve().then(function () { - return pushStatus.setInitial(body, where); - }).then(function () { - onPushStatusSaved(pushStatus.objectId); - return badgeUpdate(); - }).then(function () { - if (_this2.adapter.snsConfig && where.arn) { - body.data.badge = null; // skip badge increment by bulk push send - return { - results: where - }; - } - return _rest2.default.find(config, auth, '_Installation', where); - }).then(function (response) { - if (!response.results) { - return Promise.reject({ error: 'PushController: no results in query' }); - } - pushStatus.setRunning(response.results); - return _this2.sendToAdapter(body, response.results, pushStatus, config); - }).then(function (results) { - return pushStatus.complete(results); - }).catch(function (err) { - pushStatus.fail(err); - return Promise.reject(err); - }); - } - }, { - key: 'sendToAdapter', - value: function sendToAdapter(body, installations, pushStatus, config) { - var _this3 = this; - - if (body.data && body.data.badge && typeof body.data.badge == 'string' && body.data.badge.toLowerCase() == "increment") { - var _ret2 = function () { - // Collect the badges to reduce the # of calls - var badgeInstallationsMap = installations.reduce(function (map, installation) { - var badge = installation.badge; - if (installation.deviceType != "ios") { - badge = UNSUPPORTED_BADGE_KEY; - } - map[badge + ''] = map[badge + ''] || []; - map[badge + ''].push(installation); - return map; - }, {}); - - // Map the on the badges count and return the send result - var promises = Object.keys(badgeInstallationsMap).map(function (badge) { - var payload = (0, _deepcopy2.default)(body); - if (badge == UNSUPPORTED_BADGE_KEY) { - delete payload.data.badge; - } else { - payload.data.badge = parseInt(badge); - } - return _this3.adapter.send(payload, badgeInstallationsMap[badge], pushStatus.objectId); - }); - return { - v: Promise.all(promises) - }; - }(); - - if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; - } - return this.adapter.send(body, installations, pushStatus.objectId); - } - - /** - * Get expiration time from the request body. - * @param {Object} request A request object - * @returns {Number|undefined} The expiration time if it exists in the request - */ - - }, { - key: 'expectedAdapterType', - value: function expectedAdapterType() { - return _PushAdapter.PushAdapter; - } - }, { - key: 'pushIsAvailable', - get: function get() { - return !!this.adapter; - } - }], [{ - key: 'validatePushType', - - - /** - * Check whether the deviceType parameter in qury condition is valid or not. - * @param {Object} where A query condition - * @param {Array} validPushTypes An array of valid push types(string) - */ - value: function validatePushType() { - var where = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var validPushTypes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; - - var deviceTypeField = where.deviceType || {}; - var deviceTypes = []; - if (typeof deviceTypeField === 'string') { - deviceTypes.push(deviceTypeField); - } else if (typeof deviceTypeField['$in'] === 'array') { - deviceTypes.concat(deviceTypeField['$in']); - } - for (var i = 0; i < deviceTypes.length; i++) { - var deviceType = deviceTypes[i]; - if (validPushTypes.indexOf(deviceType) < 0) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, deviceType + ' is not supported push type.'); - } - } - } - }, { - key: 'getExpirationTime', - value: function getExpirationTime() { - var body = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var hasExpirationTime = !!body['expiration_time']; - if (!hasExpirationTime) { - return; - } - var expirationTimeParam = body['expiration_time']; - var expirationTime; - if (typeof expirationTimeParam === 'number') { - expirationTime = new Date(expirationTimeParam * 1000); - } else if (typeof expirationTimeParam === 'string') { - expirationTime = new Date(expirationTimeParam); - } else { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, body['expiration_time'] + ' is not valid time.'); - } - // Check expirationTime is valid or not, if it is not valid, expirationTime is NaN - if (!isFinite(expirationTime)) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, body['expiration_time'] + ' is not valid time.'); - } - return expirationTime.valueOf(); - } - }]); - - return PushController; -}(_AdaptableController3.default); - -exports.default = PushController; \ No newline at end of file diff --git a/lib/Controllers/SchemaCache.js b/lib/Controllers/SchemaCache.js deleted file mode 100644 index 6607f3d258..0000000000 --- a/lib/Controllers/SchemaCache.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _cryptoUtils = require("../cryptoUtils"); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var MAIN_SCHEMA = "__MAIN_SCHEMA"; -var SCHEMA_CACHE_PREFIX = "__SCHEMA"; -var ALL_KEYS = "__ALL_KEYS"; - -var SchemaCache = function () { - function SchemaCache(cacheController) { - var ttl = arguments.length <= 1 || arguments[1] === undefined ? 30 : arguments[1]; - - _classCallCheck(this, SchemaCache); - - this.ttl = ttl; - if (typeof ttl == 'string') { - this.ttl = parseInt(ttl); - } - this.cache = cacheController; - this.prefix = SCHEMA_CACHE_PREFIX + (0, _cryptoUtils.randomString)(20); - } - - _createClass(SchemaCache, [{ - key: "put", - value: function put(key, value) { - var _this = this; - - return this.cache.get(this.prefix + ALL_KEYS).then(function (allKeys) { - allKeys = allKeys || {}; - allKeys[key] = true; - return Promise.all([_this.cache.put(_this.prefix + ALL_KEYS, allKeys, _this.ttl), _this.cache.put(key, value, _this.ttl)]); - }); - } - }, { - key: "getAllClasses", - value: function getAllClasses() { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.cache.get(this.prefix + MAIN_SCHEMA); - } - }, { - key: "setAllClasses", - value: function setAllClasses(schema) { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.put(this.prefix + MAIN_SCHEMA, schema); - } - }, { - key: "setOneSchema", - value: function setOneSchema(className, schema) { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.put(this.prefix + className, schema); - } - }, { - key: "getOneSchema", - value: function getOneSchema(className) { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.cache.get(this.prefix + className); - } - }, { - key: "clear", - value: function clear() { - var _this2 = this; - - // That clears all caches... - var promise = Promise.resolve(); - return this.cache.get(this.prefix + ALL_KEYS).then(function (allKeys) { - if (!allKeys) { - return; - } - var promises = Object.keys(allKeys).map(function (key) { - return _this2.cache.del(key); - }); - return Promise.all(promises); - }); - } - }]); - - return SchemaCache; -}(); - -exports.default = SchemaCache; \ No newline at end of file diff --git a/lib/Controllers/SchemaController.js b/lib/Controllers/SchemaController.js deleted file mode 100644 index 529daef191..0000000000 --- a/lib/Controllers/SchemaController.js +++ /dev/null @@ -1,1011 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.convertSchemaToAdapterSchema = exports.defaultColumns = exports.systemClasses = exports.buildMergedSchemaObject = exports.invalidClassNameMessage = exports.fieldNameIsValid = exports.classNameIsValid = exports.load = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -// This class handles schema validation, persistence, and modification. -// -// Each individual Schema object should be immutable. The helpers to -// do things with the Schema just return a new schema when the schema -// is changed. -// -// The canonical place to store this Schema is in the database itself, -// in a _SCHEMA collection. This is not the right way to do it for an -// open source framework, but it's backward compatible, so we're -// keeping it this way for now. -// -// In API-handling code, you should only use the Schema class via the -// DatabaseController. This will let us replace the schema logic for -// different databases. -// TODO: hide all schema logic inside the database adapter. - -var Parse = require('parse/node').Parse; - - -var defaultColumns = Object.freeze({ - // Contain the default columns for every parse object type (except _Join collection) - _Default: { - "objectId": { type: 'String' }, - "createdAt": { type: 'Date' }, - "updatedAt": { type: 'Date' }, - "ACL": { type: 'ACL' } - }, - // The additional default columns for the _User collection (in addition to DefaultCols) - _User: { - "username": { type: 'String' }, - "password": { type: 'String' }, - "email": { type: 'String' }, - "emailVerified": { type: 'Boolean' } - }, - // The additional default columns for the _Installation collection (in addition to DefaultCols) - _Installation: { - "installationId": { type: 'String' }, - "deviceToken": { type: 'String' }, - "channels": { type: 'Array' }, - "deviceType": { type: 'String' }, - "pushType": { type: 'String' }, - "GCMSenderId": { type: 'String' }, - "timeZone": { type: 'String' }, - "localeIdentifier": { type: 'String' }, - "badge": { type: 'Number' }, - "appVersion": { type: 'String' }, - "appName": { type: 'String' }, - "appIdentifier": { type: 'String' }, - "parseVersion": { type: 'String' } - }, - // The additional default columns for the _Role collection (in addition to DefaultCols) - _Role: { - "name": { type: 'String' }, - "users": { type: 'Relation', targetClass: '_User' }, - "roles": { type: 'Relation', targetClass: '_Role' } - }, - // The additional default columns for the _Session collection (in addition to DefaultCols) - _Session: { - "restricted": { type: 'Boolean' }, - "user": { type: 'Pointer', targetClass: '_User' }, - "installationId": { type: 'String' }, - "sessionToken": { type: 'String' }, - "expiresAt": { type: 'Date' }, - "createdWith": { type: 'Object' } - }, - _Product: { - "productIdentifier": { type: 'String' }, - "download": { type: 'File' }, - "downloadName": { type: 'String' }, - "icon": { type: 'File' }, - "order": { type: 'Number' }, - "title": { type: 'String' }, - "subtitle": { type: 'String' } - }, - _PushStatus: { - "pushTime": { type: 'String' }, - "source": { type: 'String' }, // rest or webui - "query": { type: 'String' }, // the stringified JSON query - "payload": { type: 'Object' }, // the JSON payload, - "title": { type: 'String' }, - "expiry": { type: 'Number' }, - "status": { type: 'String' }, - "numSent": { type: 'Number' }, - "numFailed": { type: 'Number' }, - "pushHash": { type: 'String' }, - "errorMessage": { type: 'Object' }, - "sentPerType": { type: 'Object' }, - "failedPerType": { type: 'Object' } - } -}); - -var requiredColumns = Object.freeze({ - _Product: ["productIdentifier", "icon", "order", "title", "subtitle"], - _Role: ["name", "ACL"] -}); - -var systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus']); - -var volatileClasses = Object.freeze(['_PushStatus', '_Hooks', '_GlobalConfig']); - -// 10 alpha numberic chars + uppercase -var userIdRegex = /^[a-zA-Z0-9]{10}$/; -// Anything that start with role -var roleRegex = /^role:.*/; -// * permission -var publicRegex = /^\*$/; - -var permissionKeyRegex = Object.freeze([userIdRegex, roleRegex, publicRegex]); - -function verifyPermissionKey(key) { - var result = permissionKeyRegex.reduce(function (isGood, regEx) { - isGood = isGood || key.match(regEx) != null; - return isGood; - }, false); - if (!result) { - throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + key + '\' is not a valid key for class level permissions'); - } -} - -var CLPValidKeys = Object.freeze(['find', 'get', 'create', 'update', 'delete', 'addField', 'readUserFields', 'writeUserFields']); -function validateCLP(perms, fields) { - if (!perms) { - return; - } - Object.keys(perms).forEach(function (operation) { - if (CLPValidKeys.indexOf(operation) == -1) { - throw new Parse.Error(Parse.Error.INVALID_JSON, operation + ' is not a valid operation for class level permissions'); - } - - if (operation === 'readUserFields' || operation === 'writeUserFields') { - if (!Array.isArray(perms[operation])) { - throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + perms[operation] + '\' is not a valid value for class level permissions ' + operation); - } else { - perms[operation].forEach(function (key) { - if (!fields[key] || fields[key].type != 'Pointer' || fields[key].targetClass != '_User') { - throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + key + '\' is not a valid column for class level pointer permissions ' + operation); - } - }); - } - return; - } - - Object.keys(perms[operation]).forEach(function (key) { - verifyPermissionKey(key); - var perm = perms[operation][key]; - if (perm !== true) { - throw new Parse.Error(Parse.Error.INVALID_JSON, '\'' + perm + '\' is not a valid value for class level permissions ' + operation + ':' + key + ':' + perm); - } - }); - }); -} -var joinClassRegex = /^_Join:[A-Za-z0-9_]+:[A-Za-z0-9_]+/; -var classAndFieldRegex = /^[A-Za-z][A-Za-z0-9_]*$/; -function classNameIsValid(className) { - // Valid classes must: - return ( - // Be one of _User, _Installation, _Role, _Session OR - systemClasses.indexOf(className) > -1 || - // Be a join table OR - joinClassRegex.test(className) || - // Include only alpha-numeric and underscores, and not start with an underscore or number - fieldNameIsValid(className) - ); -} - -// Valid fields must be alpha-numeric, and not start with an underscore or number -function fieldNameIsValid(fieldName) { - return classAndFieldRegex.test(fieldName); -} - -// Checks that it's not trying to clobber one of the default fields of the class. -function fieldNameIsValidForClass(fieldName, className) { - if (!fieldNameIsValid(fieldName)) { - return false; - } - if (defaultColumns._Default[fieldName]) { - return false; - } - if (defaultColumns[className] && defaultColumns[className][fieldName]) { - return false; - } - return true; -} - -function invalidClassNameMessage(className) { - return 'Invalid classname: ' + className + ', classnames can only have alphanumeric characters and _, and must start with an alpha character '; -} - -var invalidJsonError = new Parse.Error(Parse.Error.INVALID_JSON, "invalid JSON"); -var validNonRelationOrPointerTypes = ['Number', 'String', 'Boolean', 'Date', 'Object', 'Array', 'GeoPoint', 'File']; -// Returns an error suitable for throwing if the type is invalid -var fieldTypeIsInvalid = function fieldTypeIsInvalid(_ref) { - var type = _ref.type; - var targetClass = _ref.targetClass; - - if (['Pointer', 'Relation'].includes(type)) { - if (!targetClass) { - return new Parse.Error(135, 'type ' + type + ' needs a class name'); - } else if (typeof targetClass !== 'string') { - return invalidJsonError; - } else if (!classNameIsValid(targetClass)) { - return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass)); - } else { - return undefined; - } - } - if (typeof type !== 'string') { - return invalidJsonError; - } - if (!validNonRelationOrPointerTypes.includes(type)) { - return new Parse.Error(Parse.Error.INCORRECT_TYPE, 'invalid field type: ' + type); - } - return undefined; -}; - -var convertSchemaToAdapterSchema = function convertSchemaToAdapterSchema(schema) { - schema = injectDefaultSchema(schema); - delete schema.fields.ACL; - schema.fields._rperm = { type: 'Array' }; - schema.fields._wperm = { type: 'Array' }; - - if (schema.className === '_User') { - delete schema.fields.password; - schema.fields._hashed_password = { type: 'String' }; - } - - return schema; -}; - -var convertAdapterSchemaToParseSchema = function convertAdapterSchemaToParseSchema(_ref2) { - var schema = _objectWithoutProperties(_ref2, []); - - delete schema.fields._rperm; - delete schema.fields._wperm; - - schema.fields.ACL = { type: 'ACL' }; - - if (schema.className === '_User') { - delete schema.fields.authData; //Auth data is implicit - delete schema.fields._hashed_password; - schema.fields.password = { type: 'String' }; - } - - return schema; -}; - -var injectDefaultSchema = function injectDefaultSchema(_ref3) { - var className = _ref3.className; - var fields = _ref3.fields; - var classLevelPermissions = _ref3.classLevelPermissions; - return { - className: className, - fields: _extends({}, defaultColumns._Default, defaultColumns[className] || {}, fields), - classLevelPermissions: classLevelPermissions - }; -}; - -var dbTypeMatchesObjectType = function dbTypeMatchesObjectType(dbType, objectType) { - if (dbType.type !== objectType.type) return false; - if (dbType.targetClass !== objectType.targetClass) return false; - if (dbType === objectType.type) return true; - if (dbType.type === objectType.type) return true; - return false; -}; - -// Stores the entire schema of the app in a weird hybrid format somewhere between -// the mongo format and the Parse format. Soon, this will all be Parse format. - -var SchemaController = function () { - function SchemaController(databaseAdapter, schemaCache) { - _classCallCheck(this, SchemaController); - - this._dbAdapter = databaseAdapter; - this._cache = schemaCache; - // this.data[className][fieldName] tells you the type of that field, in mongo format - this.data = {}; - // this.perms[className][operation] tells you the acl-style permissions - this.perms = {}; - } - - _createClass(SchemaController, [{ - key: 'reloadData', - value: function reloadData() { - var _this = this; - - var options = arguments.length <= 0 || arguments[0] === undefined ? { clearCache: false } : arguments[0]; - - if (options.clearCache) { - this._cache.clear(); - } - if (this.reloadDataPromise && !options.clearCache) { - return this.reloadDataPromise; - } - this.data = {}; - this.perms = {}; - this.reloadDataPromise = this.getAllClasses(options).then(function (allSchemas) { - allSchemas.forEach(function (schema) { - _this.data[schema.className] = injectDefaultSchema(schema).fields; - _this.perms[schema.className] = schema.classLevelPermissions; - }); - - // Inject the in-memory classes - volatileClasses.forEach(function (className) { - _this.data[className] = injectDefaultSchema({ - className: className, - fields: {}, - classLevelPermissions: {} - }); - }); - delete _this.reloadDataPromise; - }, function (err) { - delete _this.reloadDataPromise; - throw err; - }); - return this.reloadDataPromise; - } - }, { - key: 'getAllClasses', - value: function getAllClasses() { - var _this2 = this; - - var options = arguments.length <= 0 || arguments[0] === undefined ? { clearCache: false } : arguments[0]; - - if (options.clearCache) { - this._cache.clear(); - } - return this._cache.getAllClasses().then(function (allClasses) { - if (allClasses && allClasses.length && !options.clearCache) { - return Promise.resolve(allClasses); - } - return _this2._dbAdapter.getAllClasses().then(function (allSchemas) { - return allSchemas.map(injectDefaultSchema); - }).then(function (allSchemas) { - return _this2._cache.setAllClasses(allSchemas).then(function () { - return allSchemas; - }); - }); - }); - } - }, { - key: 'getOneSchema', - value: function getOneSchema(className) { - var _this3 = this; - - var allowVolatileClasses = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; - var options = arguments.length <= 2 || arguments[2] === undefined ? { clearCache: false } : arguments[2]; - - if (options.clearCache) { - this._cache.clear(); - } - if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) { - return Promise.resolve(this.data[className]); - } - return this._cache.getOneSchema(className).then(function (cached) { - if (cached && !options.clearCache) { - return Promise.resolve(cached); - } - return _this3._dbAdapter.getClass(className).then(injectDefaultSchema).then(function (result) { - return _this3._cache.setOneSchema(className, result).then(function () { - return result; - }); - }); - }); - } - - // Create a new class that includes the three default fields. - // ACL is an implicit column that does not get an entry in the - // _SCHEMAS database. Returns a promise that resolves with the - // created schema, in mongo format. - // on success, and rejects with an error on fail. Ensure you - // have authorization (master key, or client class creation - // enabled) before calling this function. - - }, { - key: 'addClassIfNotExists', - value: function addClassIfNotExists(className) { - var _this4 = this; - - var fields = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - var classLevelPermissions = arguments[2]; - - var validationError = this.validateNewClass(className, fields, classLevelPermissions); - if (validationError) { - return Promise.reject(validationError); - } - - return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields: fields, classLevelPermissions: classLevelPermissions, className: className })).then(convertAdapterSchemaToParseSchema).then(function (res) { - _this4._cache.clear(); - return res; - }).catch(function (error) { - if (error && error.code === Parse.Error.DUPLICATE_VALUE) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' already exists.'); - } else { - throw error; - } - }); - } - }, { - key: 'updateClass', - value: function updateClass(className, submittedFields, classLevelPermissions, database) { - var _this5 = this; - - return this.getOneSchema(className).then(function (schema) { - var existingFields = schema.fields; - Object.keys(submittedFields).forEach(function (name) { - var field = submittedFields[name]; - if (existingFields[name] && field.__op !== 'Delete') { - throw new Parse.Error(255, 'Field ' + name + ' exists, cannot update.'); - } - if (!existingFields[name] && field.__op === 'Delete') { - throw new Parse.Error(255, 'Field ' + name + ' does not exist, cannot delete.'); - } - }); - - delete existingFields._rperm; - delete existingFields._wperm; - var newSchema = buildMergedSchemaObject(existingFields, submittedFields); - var validationError = _this5.validateSchemaData(className, newSchema, classLevelPermissions, Object.keys(existingFields)); - if (validationError) { - throw new Parse.Error(validationError.code, validationError.error); - } - - // Finally we have checked to make sure the request is valid and we can start deleting fields. - // Do all deletions first, then a single save to _SCHEMA collection to handle all additions. - var deletePromises = []; - var insertedFields = []; - Object.keys(submittedFields).forEach(function (fieldName) { - if (submittedFields[fieldName].__op === 'Delete') { - var promise = _this5.deleteField(fieldName, className, database); - deletePromises.push(promise); - } else { - insertedFields.push(fieldName); - } - }); - - return Promise.all(deletePromises) // Delete Everything - .then(function () { - return _this5.reloadData({ clearCache: true }); - }) // Reload our Schema, so we have all the new values - .then(function () { - var promises = insertedFields.map(function (fieldName) { - var type = submittedFields[fieldName]; - return _this5.enforceFieldExists(className, fieldName, type); - }); - return Promise.all(promises); - }).then(function () { - return _this5.setPermissions(className, classLevelPermissions, newSchema); - }) - //TODO: Move this logic into the database adapter - .then(function () { - return { - className: className, - fields: _this5.data[className], - classLevelPermissions: _this5.perms[className] - }; - }); - }).catch(function (error) { - if (error === undefined) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' does not exist.'); - } else { - throw error; - } - }); - } - - // Returns a promise that resolves successfully to the new schema - // object or fails with a reason. - - }, { - key: 'enforceClassExists', - value: function enforceClassExists(className) { - var _this6 = this; - - if (this.data[className]) { - return Promise.resolve(this); - } - // We don't have this class. Update the schema - return this.addClassIfNotExists(className) - // The schema update succeeded. Reload the schema - .then(function () { - return _this6.reloadData({ clearCache: true }); - }).catch(function (error) { - // The schema update failed. This can be okay - it might - // have failed because there's a race condition and a different - // client is making the exact same schema update that we want. - // So just reload the schema. - return _this6.reloadData({ clearCache: true }); - }).then(function () { - // Ensure that the schema now validates - if (_this6.data[className]) { - return _this6; - } else { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'Failed to add ' + className); - } - }).catch(function (error) { - // The schema still doesn't validate. Give up - throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate'); - }); - } - }, { - key: 'validateNewClass', - value: function validateNewClass(className) { - var fields = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - var classLevelPermissions = arguments[2]; - - if (this.data[className]) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' already exists.'); - } - if (!classNameIsValid(className)) { - return { - code: Parse.Error.INVALID_CLASS_NAME, - error: invalidClassNameMessage(className) - }; - } - return this.validateSchemaData(className, fields, classLevelPermissions, []); - } - }, { - key: 'validateSchemaData', - value: function validateSchemaData(className, fields, classLevelPermissions, existingFieldNames) { - for (var fieldName in fields) { - if (!existingFieldNames.includes(fieldName)) { - if (!fieldNameIsValid(fieldName)) { - return { - code: Parse.Error.INVALID_KEY_NAME, - error: 'invalid field name: ' + fieldName - }; - } - if (!fieldNameIsValidForClass(fieldName, className)) { - return { - code: 136, - error: 'field ' + fieldName + ' cannot be added' - }; - } - var error = fieldTypeIsInvalid(fields[fieldName]); - if (error) return { code: error.code, error: error.message }; - } - } - - for (var _fieldName in defaultColumns[className]) { - fields[_fieldName] = defaultColumns[className][_fieldName]; - } - - var geoPoints = Object.keys(fields).filter(function (key) { - return fields[key] && fields[key].type === 'GeoPoint'; - }); - if (geoPoints.length > 1) { - return { - code: Parse.Error.INCORRECT_TYPE, - error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.' - }; - } - validateCLP(classLevelPermissions, fields); - } - - // Sets the Class-level permissions for a given className, which must exist. - - }, { - key: 'setPermissions', - value: function setPermissions(className, perms, newSchema) { - var _this7 = this; - - if (typeof perms === 'undefined') { - return Promise.resolve(); - } - validateCLP(perms, newSchema); - return this._dbAdapter.setClassLevelPermissions(className, perms).then(function () { - return _this7.reloadData({ clearCache: true }); - }); - } - - // Returns a promise that resolves successfully to the new schema - // object if the provided className-fieldName-type tuple is valid. - // The className must already be validated. - // If 'freeze' is true, refuse to update the schema for this field. - - }, { - key: 'enforceFieldExists', - value: function enforceFieldExists(className, fieldName, type, freeze) { - var _this8 = this; - - if (fieldName.indexOf(".") > 0) { - // subdocument key (x.y) => ok if x is of type 'object' - fieldName = fieldName.split(".")[0]; - type = 'Object'; - } - if (!fieldNameIsValid(fieldName)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name: ' + fieldName + '.'); - } - - // If someone tries to create a new field with null/undefined as the value, return; - if (!type) { - return Promise.resolve(this); - } - - return this.reloadData().then(function () { - var expectedType = _this8.getExpectedType(className, fieldName); - if (typeof type === 'string') { - type = { type: type }; - } - - if (expectedType) { - if (!dbTypeMatchesObjectType(expectedType, type)) { - throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'schema mismatch for ' + className + '.' + fieldName + '; expected ' + (expectedType.type || expectedType) + ' but got ' + type.type); - } - return _this8; - } - - return _this8._dbAdapter.addFieldIfNotExists(className, fieldName, type).then(function () { - // The update succeeded. Reload the schema - return _this8.reloadData({ clearCache: true }); - }, function (error) { - //TODO: introspect the error and only reload if the error is one for which is makes sense to reload - - // The update failed. This can be okay - it might have been a race - // condition where another client updated the schema in the same - // way that we wanted to. So, just reload the schema - return _this8.reloadData({ clearCache: true }); - }).then(function (error) { - // Ensure that the schema now validates - if (!dbTypeMatchesObjectType(_this8.getExpectedType(className, fieldName), type)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'Could not add field ' + fieldName); - } - // Remove the cached schema - _this8._cache.clear(); - return _this8; - }); - }); - } - - // Delete a field, and remove that data from all objects. This is intended - // to remove unused fields, if other writers are writing objects that include - // this field, the field may reappear. Returns a Promise that resolves with - // no object on success, or rejects with { code, error } on failure. - // Passing the database and prefix is necessary in order to drop relation collections - // and remove fields from objects. Ideally the database would belong to - // a database adapter and this function would close over it or access it via member. - - }, { - key: 'deleteField', - value: function deleteField(fieldName, className, database) { - var _this9 = this; - - if (!classNameIsValid(className)) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(className)); - } - if (!fieldNameIsValid(fieldName)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid field name: ' + fieldName); - } - //Don't allow deleting the default fields. - if (!fieldNameIsValidForClass(fieldName, className)) { - throw new Parse.Error(136, 'field ' + fieldName + ' cannot be changed'); - } - - return this.getOneSchema(className, false, { clearCache: true }).catch(function (error) { - if (error === undefined) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' does not exist.'); - } else { - throw error; - } - }).then(function (schema) { - if (!schema.fields[fieldName]) { - throw new Parse.Error(255, 'Field ' + fieldName + ' does not exist, cannot delete.'); - } - if (schema.fields[fieldName].type == 'Relation') { - //For relations, drop the _Join table - return database.adapter.deleteFields(className, schema, [fieldName]).then(function () { - return database.adapter.deleteClass('_Join:' + fieldName + ':' + className); - }); - } - return database.adapter.deleteFields(className, schema, [fieldName]); - }).then(function () { - _this9._cache.clear(); - }); - } - - // Validates an object provided in REST format. - // Returns a promise that resolves to the new schema if this object is - // valid. - - }, { - key: 'validateObject', - value: function validateObject(className, object, query) { - var geocount = 0; - var promise = this.enforceClassExists(className); - - var _loop = function _loop(fieldName) { - if (object[fieldName] === undefined) { - return 'continue'; - } - var expected = getType(object[fieldName]); - if (expected === 'GeoPoint') { - geocount++; - } - if (geocount > 1) { - // Make sure all field validation operations run before we return. - // If not - we are continuing to run logic, but already provided response from the server. - return { - v: promise.then(function () { - return Promise.reject(new Parse.Error(Parse.Error.INCORRECT_TYPE, 'there can only be one geopoint field in a class')); - }) - }; - } - if (!expected) { - return 'continue'; - } - if (fieldName === 'ACL') { - // Every object has ACL implicitly. - return 'continue'; - } - - promise = promise.then(function (schema) { - return schema.enforceFieldExists(className, fieldName, expected); - }); - }; - - for (var fieldName in object) { - var _ret = _loop(fieldName); - - switch (_ret) { - case 'continue': - continue; - - default: - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; - } - } - promise = thenValidateRequiredColumns(promise, className, object, query); - return promise; - } - - // Validates that all the properties are set for the object - - }, { - key: 'validateRequiredColumns', - value: function validateRequiredColumns(className, object, query) { - var columns = requiredColumns[className]; - if (!columns || columns.length == 0) { - return Promise.resolve(this); - } - - var missingColumns = columns.filter(function (column) { - if (query && query.objectId) { - if (object[column] && _typeof(object[column]) === "object") { - // Trying to delete a required column - return object[column].__op == 'Delete'; - } - // Not trying to do anything there - return false; - } - return !object[column]; - }); - - if (missingColumns.length > 0) { - throw new Parse.Error(Parse.Error.INCORRECT_TYPE, missingColumns[0] + ' is required.'); - } - return Promise.resolve(this); - } - - // Validates the base CLP for an operation - - }, { - key: 'testBaseCLP', - value: function testBaseCLP(className, aclGroup, operation) { - if (!this.perms[className] || !this.perms[className][operation]) { - return true; - } - var classPerms = this.perms[className]; - var perms = classPerms[operation]; - // Handle the public scenario quickly - if (perms['*']) { - return true; - } - // Check permissions against the aclGroup provided (array of userId/roles) - if (aclGroup.some(function (acl) { - return perms[acl] === true; - })) { - return true; - } - return false; - } - - // Validates an operation passes class-level-permissions set in the schema - - }, { - key: 'validatePermission', - value: function validatePermission(className, aclGroup, operation) { - if (this.testBaseCLP(className, aclGroup, operation)) { - return Promise.resolve(); - } - - if (!this.perms[className] || !this.perms[className][operation]) { - return true; - } - var classPerms = this.perms[className]; - var perms = classPerms[operation]; - // No matching CLP, let's check the Pointer permissions - // And handle those later - var permissionField = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields'; - - // Reject create when write lockdown - if (permissionField == 'writeUserFields' && operation == 'create') { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied for action ' + operation + ' on class ' + className + '.'); - } - - // Process the readUserFields later - if (Array.isArray(classPerms[permissionField]) && classPerms[permissionField].length > 0) { - return Promise.resolve(); - } - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied for action ' + operation + ' on class ' + className + '.'); - } - }, { - key: 'getExpectedType', - - - // Returns the expected type for a className+key combination - // or undefined if the schema is not set - value: function getExpectedType(className, fieldName) { - if (this.data && this.data[className]) { - var expectedType = this.data[className][fieldName]; - return expectedType === 'map' ? 'Object' : expectedType; - } - return undefined; - } - }, { - key: 'hasClass', - - - // Checks if a given class is in the schema. - value: function hasClass(className) { - var _this10 = this; - - return this.reloadData().then(function () { - return !!_this10.data[className]; - }); - } - }]); - - return SchemaController; -}(); - -// Returns a promise for a new Schema. - - -var load = function load(dbAdapter, schemaCache, options) { - var schema = new SchemaController(dbAdapter, schemaCache); - return schema.reloadData(options).then(function () { - return schema; - }); -}; - -// Builds a new schema (in schema API response format) out of an -// existing mongo schema + a schemas API put request. This response -// does not include the default fields, as it is intended to be passed -// to mongoSchemaFromFieldsAndClassName. No validation is done here, it -// is done in mongoSchemaFromFieldsAndClassName. -function buildMergedSchemaObject(existingFields, putRequest) { - var newSchema = {}; - var sysSchemaField = Object.keys(defaultColumns).indexOf(existingFields._id) === -1 ? [] : Object.keys(defaultColumns[existingFields._id]); - for (var oldField in existingFields) { - if (oldField !== '_id' && oldField !== 'ACL' && oldField !== 'updatedAt' && oldField !== 'createdAt' && oldField !== 'objectId') { - if (sysSchemaField.length > 0 && sysSchemaField.indexOf(oldField) !== -1) { - continue; - } - var fieldIsDeleted = putRequest[oldField] && putRequest[oldField].__op === 'Delete'; - if (!fieldIsDeleted) { - newSchema[oldField] = existingFields[oldField]; - } - } - } - for (var newField in putRequest) { - if (newField !== 'objectId' && putRequest[newField].__op !== 'Delete') { - if (sysSchemaField.length > 0 && sysSchemaField.indexOf(newField) !== -1) { - continue; - } - newSchema[newField] = putRequest[newField]; - } - } - return newSchema; -} - -// Given a schema promise, construct another schema promise that -// validates this field once the schema loads. -function thenValidateRequiredColumns(schemaPromise, className, object, query) { - return schemaPromise.then(function (schema) { - return schema.validateRequiredColumns(className, object, query); - }); -} - -// Gets the type from a REST API formatted object, where 'type' is -// extended past javascript types to include the rest of the Parse -// type system. -// The output should be a valid schema value. -// TODO: ensure that this is compatible with the format used in Open DB -function getType(obj) { - var type = typeof obj === 'undefined' ? 'undefined' : _typeof(obj); - switch (type) { - case 'boolean': - return 'Boolean'; - case 'string': - return 'String'; - case 'number': - return 'Number'; - case 'map': - case 'object': - if (!obj) { - return undefined; - } - return getObjectType(obj); - case 'function': - case 'symbol': - case 'undefined': - default: - throw 'bad obj: ' + obj; - } -} - -// This gets the type for non-JSON types like pointers and files, but -// also gets the appropriate type for $ operators. -// Returns null if the type is unknown. -function getObjectType(obj) { - if (obj instanceof Array) { - return 'Array'; - } - if (obj.__type) { - switch (obj.__type) { - case 'Pointer': - if (obj.className) { - return { - type: 'Pointer', - targetClass: obj.className - }; - } - case 'File': - if (obj.name) { - return 'File'; - } - case 'Date': - if (obj.iso) { - return 'Date'; - } - case 'GeoPoint': - if (obj.latitude != null && obj.longitude != null) { - return 'GeoPoint'; - } - case 'Bytes': - if (obj.base64) { - return; - } - default: - throw new Parse.Error(Parse.Error.INCORRECT_TYPE, "This is not a valid " + obj.__type); - } - } - if (obj['$ne']) { - return getObjectType(obj['$ne']); - } - if (obj.__op) { - switch (obj.__op) { - case 'Increment': - return 'Number'; - case 'Delete': - return null; - case 'Add': - case 'AddUnique': - case 'Remove': - return 'Array'; - case 'AddRelation': - case 'RemoveRelation': - return { - type: 'Relation', - targetClass: obj.objects[0].className - }; - case 'Batch': - return getObjectType(obj.ops[0]); - default: - throw 'unexpected op: ' + obj.__op; - } - } - return 'Object'; -} - -exports.load = load; -exports.classNameIsValid = classNameIsValid; -exports.fieldNameIsValid = fieldNameIsValid; -exports.invalidClassNameMessage = invalidClassNameMessage; -exports.buildMergedSchemaObject = buildMergedSchemaObject; -exports.systemClasses = systemClasses; -exports.defaultColumns = defaultColumns; -exports.convertSchemaToAdapterSchema = convertSchemaToAdapterSchema; \ No newline at end of file diff --git a/lib/Controllers/UserController.js b/lib/Controllers/UserController.js deleted file mode 100644 index 859dd66fc6..0000000000 --- a/lib/Controllers/UserController.js +++ /dev/null @@ -1,261 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.UserController = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - -var _cryptoUtils = require('../cryptoUtils'); - -var _triggers = require('../triggers'); - -var _AdaptableController2 = require('./AdaptableController'); - -var _AdaptableController3 = _interopRequireDefault(_AdaptableController2); - -var _MailAdapter = require('../Adapters/Email/MailAdapter'); - -var _MailAdapter2 = _interopRequireDefault(_MailAdapter); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var DatabaseAdapter = require('../DatabaseAdapter'); -var RestWrite = require('../RestWrite'); -var RestQuery = require('../RestQuery'); -var hash = require('../password').hash; -var Auth = require('../Auth'); - -var UserController = exports.UserController = function (_AdaptableController) { - _inherits(UserController, _AdaptableController); - - function UserController(adapter, appId) { - var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - - _classCallCheck(this, UserController); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(UserController).call(this, adapter, appId, options)); - } - - _createClass(UserController, [{ - key: 'validateAdapter', - value: function validateAdapter(adapter) { - // Allow no adapter - if (!adapter && !this.shouldVerifyEmails) { - return; - } - _get(Object.getPrototypeOf(UserController.prototype), 'validateAdapter', this).call(this, adapter); - } - }, { - key: 'expectedAdapterType', - value: function expectedAdapterType() { - return _MailAdapter2.default; - } - }, { - key: 'setEmailVerifyToken', - value: function setEmailVerifyToken(user) { - if (this.shouldVerifyEmails) { - user._email_verify_token = (0, _cryptoUtils.randomString)(25); - user.emailVerified = false; - - if (this.config.emailVerifyTokenValidityDuration) { - user._email_verify_token_expires_at = Parse._encode(this.config.generateEmailVerifyTokenExpiresAt()); - } - } - } - }, { - key: 'verifyEmail', - value: function verifyEmail(username, token) { - if (!this.shouldVerifyEmails) { - // Trying to verify email when not enabled - // TODO: Better error here. - throw undefined; - } - - var query = { username: username, _email_verify_token: token }; - var updateFields = { emailVerified: true, _email_verify_token: { __op: 'Delete' } }; - - // if the email verify token needs to be validated then - // add additional query params and additional fields that need to be updated - if (this.config.emailVerifyTokenValidityDuration) { - query.emailVerified = false; - query._email_verify_token_expires_at = { $gt: Parse._encode(new Date()) }; - - updateFields._email_verify_token_expires_at = { __op: 'Delete' }; - } - - return this.config.database.update('_User', query, updateFields).then(function (document) { - if (!document) { - throw undefined; - } - return Promise.resolve(document); - }); - } - }, { - key: 'checkResetTokenValidity', - value: function checkResetTokenValidity(username, token) { - return this.config.database.find('_User', { - username: username, - _perishable_token: token - }, { limit: 1 }).then(function (results) { - if (results.length != 1) { - throw undefined; - } - return results[0]; - }); - } - }, { - key: 'getUserIfNeeded', - value: function getUserIfNeeded(user) { - if (user.username && user.email) { - return Promise.resolve(user); - } - var where = {}; - if (user.username) { - where.username = user.username; - } - if (user.email) { - where.email = user.email; - } - - var query = new RestQuery(this.config, Auth.master(this.config), '_User', where); - return query.execute().then(function (result) { - if (result.results.length != 1) { - throw undefined; - } - return result.results[0]; - }); - } - }, { - key: 'sendVerificationEmail', - value: function sendVerificationEmail(user) { - var _this2 = this; - - if (!this.shouldVerifyEmails) { - return; - } - var token = encodeURIComponent(user._email_verify_token); - // We may need to fetch the user in case of update email - this.getUserIfNeeded(user).then(function (user) { - var username = encodeURIComponent(user.username); - var link = _this2.config.verifyEmailURL + '?token=' + token + '&username=' + username; - var options = { - appName: _this2.config.appName, - link: link, - user: (0, _triggers.inflate)('_User', user) - }; - if (_this2.adapter.sendVerificationEmail) { - _this2.adapter.sendVerificationEmail(options); - } else { - _this2.adapter.sendMail(_this2.defaultVerificationEmail(options)); - } - }); - } - }, { - key: 'setPasswordResetToken', - value: function setPasswordResetToken(email) { - return this.config.database.update('_User', { email: email }, { _perishable_token: (0, _cryptoUtils.randomString)(25) }, {}, true); - } - }, { - key: 'sendPasswordResetEmail', - value: function sendPasswordResetEmail(email) { - var _this3 = this; - - if (!this.adapter) { - throw "Trying to send a reset password but no adapter is set"; - // TODO: No adapter? - return; - } - - return this.setPasswordResetToken(email).then(function (user) { - var token = encodeURIComponent(user._perishable_token); - var username = encodeURIComponent(user.username); - var link = _this3.config.requestResetPasswordURL + '?token=' + token + '&username=' + username; - - var options = { - appName: _this3.config.appName, - link: link, - user: (0, _triggers.inflate)('_User', user) - }; - - if (_this3.adapter.sendPasswordResetEmail) { - _this3.adapter.sendPasswordResetEmail(options); - } else { - _this3.adapter.sendMail(_this3.defaultResetPasswordEmail(options)); - } - - return Promise.resolve(user); - }); - } - }, { - key: 'updatePassword', - value: function updatePassword(username, token, password, config) { - var _this4 = this; - - return this.checkResetTokenValidity(username, token).then(function (user) { - return updateUserPassword(user.objectId, password, _this4.config); - }) - // clear reset password token - .then(function () { - return _this4.config.database.update('_User', { username: username }, { - _perishable_token: { __op: 'Delete' } - }); - }); - } - }, { - key: 'defaultVerificationEmail', - value: function defaultVerificationEmail(_ref) { - var link = _ref.link; - var user = _ref.user; - var appName = _ref.appName; - - var text = "Hi,\n\n" + "You are being asked to confirm the e-mail address " + user.get("email") + " with " + appName + "\n\n" + "" + "Click here to confirm it:\n" + link; - var to = user.get("email"); - var subject = 'Please verify your e-mail for ' + appName; - return { text: text, to: to, subject: subject }; - } - }, { - key: 'defaultResetPasswordEmail', - value: function defaultResetPasswordEmail(_ref2) { - var link = _ref2.link; - var user = _ref2.user; - var appName = _ref2.appName; - - var text = "Hi,\n\n" + "You requested to reset your password for " + appName + ".\n\n" + "" + "Click here to reset it:\n" + link; - var to = user.get("email"); - var subject = 'Password Reset for ' + appName; - return { text: text, to: to, subject: subject }; - } - }, { - key: 'shouldVerifyEmails', - get: function get() { - return this.options.verifyUserEmails; - } - }]); - - return UserController; -}(_AdaptableController3.default); - -// Mark this private - - -function updateUserPassword(userId, password, config) { - return _rest2.default.update(config, Auth.master(config), '_User', userId, { - password: password - }); -} - -exports.default = UserController; \ No newline at end of file diff --git a/lib/DatabaseAdapter.js b/lib/DatabaseAdapter.js deleted file mode 100644 index 54fc6e6a0f..0000000000 --- a/lib/DatabaseAdapter.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -//Used by tests -function destroyAllDataPermanently() { - if (process.env.TESTING) { - // This is super janky, but destroyAllDataPermanently is - // a janky interface, so we need to have some jankyness - // to support it - return Promise.all(Object.keys(_cache2.default.cache).map(function (appId) { - var app = _cache2.default.get(appId); - if (app.databaseController) { - return app.databaseController.deleteEverything(); - } else { - return Promise.resolve(); - } - })); - } - throw 'Only supported in test environment'; -} - -module.exports = { destroyAllDataPermanently: destroyAllDataPermanently }; \ No newline at end of file diff --git a/lib/LiveQuery/Client.js b/lib/LiveQuery/Client.js deleted file mode 100644 index 8b1289124a..0000000000 --- a/lib/LiveQuery/Client.js +++ /dev/null @@ -1,162 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Client = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PLog = require('./PLog'); - -var _PLog2 = _interopRequireDefault(_PLog); - -var _node = require('parse/node'); - -var _node2 = _interopRequireDefault(_node); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var dafaultFields = ['className', 'objectId', 'updatedAt', 'createdAt', 'ACL']; - -var Client = function () { - function Client(id, parseWebSocket) { - _classCallCheck(this, Client); - - this.id = id; - this.parseWebSocket = parseWebSocket; - this.roles = []; - this.subscriptionInfos = new Map(); - this.pushConnect = this._pushEvent('connected'); - this.pushSubscribe = this._pushEvent('subscribed'); - this.pushUnsubscribe = this._pushEvent('unsubscribed'); - this.pushCreate = this._pushEvent('create'); - this.pushEnter = this._pushEvent('enter'); - this.pushUpdate = this._pushEvent('update'); - this.pushDelete = this._pushEvent('delete'); - this.pushLeave = this._pushEvent('leave'); - } - - _createClass(Client, [{ - key: 'addSubscriptionInfo', - value: function addSubscriptionInfo(requestId, subscriptionInfo) { - this.subscriptionInfos.set(requestId, subscriptionInfo); - } - }, { - key: 'getSubscriptionInfo', - value: function getSubscriptionInfo(requestId) { - return this.subscriptionInfos.get(requestId); - } - }, { - key: 'deleteSubscriptionInfo', - value: function deleteSubscriptionInfo(requestId) { - return this.subscriptionInfos.delete(requestId); - } - }, { - key: '_pushEvent', - value: function _pushEvent(type) { - return function (subscriptionId, parseObjectJSON) { - var response = { - 'op': type, - 'clientId': this.id - }; - if (typeof subscriptionId !== 'undefined') { - response['requestId'] = subscriptionId; - } - if (typeof parseObjectJSON !== 'undefined') { - var fields = void 0; - if (this.subscriptionInfos.has(subscriptionId)) { - fields = this.subscriptionInfos.get(subscriptionId).fields; - } - response['object'] = this._toJSONWithFields(parseObjectJSON, fields); - } - Client.pushResponse(this.parseWebSocket, JSON.stringify(response)); - }; - } - }, { - key: '_toJSONWithFields', - value: function _toJSONWithFields(parseObjectJSON, fields) { - if (!fields) { - return parseObjectJSON; - } - var limitedParseObject = {}; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = dafaultFields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var field = _step.value; - - limitedParseObject[field] = parseObjectJSON[field]; - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = fields[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var _field = _step2.value; - - if (_field in parseObjectJSON) { - limitedParseObject[_field] = parseObjectJSON[_field]; - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - - return limitedParseObject; - } - }], [{ - key: 'pushResponse', - value: function pushResponse(parseWebSocket, message) { - _PLog2.default.verbose('Push Response : %j', message); - parseWebSocket.send(message); - } - }, { - key: 'pushError', - value: function pushError(parseWebSocket, code, error) { - var reconnect = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3]; - - Client.pushResponse(parseWebSocket, JSON.stringify({ - 'op': 'error', - 'error': error, - 'code': code, - 'reconnect': reconnect - })); - } - }]); - - return Client; -}(); - -exports.Client = Client; \ No newline at end of file diff --git a/lib/LiveQuery/EventEmitterPubSub.js b/lib/LiveQuery/EventEmitterPubSub.js deleted file mode 100644 index 90159a156d..0000000000 --- a/lib/LiveQuery/EventEmitterPubSub.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.EventEmitterPubSub = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _events = require('events'); - -var _events2 = _interopRequireDefault(_events); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var emitter = new _events2.default.EventEmitter(); - -var Publisher = function () { - function Publisher(emitter) { - _classCallCheck(this, Publisher); - - this.emitter = emitter; - } - - _createClass(Publisher, [{ - key: 'publish', - value: function publish(channel, message) { - this.emitter.emit(channel, message); - } - }]); - - return Publisher; -}(); - -var Subscriber = function (_events$EventEmitter) { - _inherits(Subscriber, _events$EventEmitter); - - function Subscriber(emitter) { - _classCallCheck(this, Subscriber); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Subscriber).call(this)); - - _this.emitter = emitter; - _this.subscriptions = new Map(); - return _this; - } - - _createClass(Subscriber, [{ - key: 'subscribe', - value: function subscribe(channel) { - var _this2 = this; - - var handler = function handler(message) { - _this2.emit('message', channel, message); - }; - this.subscriptions.set(channel, handler); - this.emitter.on(channel, handler); - } - }, { - key: 'unsubscribe', - value: function unsubscribe(channel) { - if (!this.subscriptions.has(channel)) { - return; - } - this.emitter.removeListener(channel, this.subscriptions.get(channel)); - this.subscriptions.delete(channel); - } - }]); - - return Subscriber; -}(_events2.default.EventEmitter); - -function createPublisher() { - return new Publisher(emitter); -} - -function createSubscriber() { - return new Subscriber(emitter); -} - -var EventEmitterPubSub = { - createPublisher: createPublisher, - createSubscriber: createSubscriber -}; - -exports.EventEmitterPubSub = EventEmitterPubSub; \ No newline at end of file diff --git a/lib/LiveQuery/Id.js b/lib/LiveQuery/Id.js deleted file mode 100644 index 048cc21084..0000000000 --- a/lib/LiveQuery/Id.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var Id = function () { - function Id(className, objectId) { - _classCallCheck(this, Id); - - this.className = className; - this.objectId = objectId; - } - - _createClass(Id, [{ - key: 'toString', - value: function toString() { - return this.className + ':' + this.objectId; - } - }], [{ - key: 'fromString', - value: function fromString(str) { - var split = str.split(':'); - if (split.length !== 2) { - throw new TypeError('Cannot create Id object from this string'); - } - return new Id(split[0], split[1]); - } - }]); - - return Id; -}(); - -module.exports = Id; \ No newline at end of file diff --git a/lib/LiveQuery/PLog.js b/lib/LiveQuery/PLog.js deleted file mode 100644 index 12149d8433..0000000000 --- a/lib/LiveQuery/PLog.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -var _logger = require('../logger'); - -var PLog = (0, _logger.addGroup)('parse-live-query-server'); - -module.exports = PLog; \ No newline at end of file diff --git a/lib/LiveQuery/ParseCloudCodePublisher.js b/lib/LiveQuery/ParseCloudCodePublisher.js deleted file mode 100644 index e89761acee..0000000000 --- a/lib/LiveQuery/ParseCloudCodePublisher.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ParseCloudCodePublisher = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _ParsePubSub = require('./ParsePubSub'); - -var _PLog = require('./PLog'); - -var _PLog2 = _interopRequireDefault(_PLog); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var ParseCloudCodePublisher = function () { - - // config object of the publisher, right now it only contains the redisURL, - // but we may extend it later. - function ParseCloudCodePublisher() { - var config = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - _classCallCheck(this, ParseCloudCodePublisher); - - this.parsePublisher = _ParsePubSub.ParsePubSub.createPublisher(config); - } - - _createClass(ParseCloudCodePublisher, [{ - key: 'onCloudCodeAfterSave', - value: function onCloudCodeAfterSave(request) { - this._onCloudCodeMessage('afterSave', request); - } - }, { - key: 'onCloudCodeAfterDelete', - value: function onCloudCodeAfterDelete(request) { - this._onCloudCodeMessage('afterDelete', request); - } - - // Request is the request object from cloud code functions. request.object is a ParseObject. - - }, { - key: '_onCloudCodeMessage', - value: function _onCloudCodeMessage(type, request) { - _PLog2.default.verbose('Raw request from cloud code current : %j | original : %j', request.object, request.original); - // We need the full JSON which includes className - var message = { - currentParseObject: request.object._toFullJSON() - }; - if (request.original) { - message.originalParseObject = request.original._toFullJSON(); - } - this.parsePublisher.publish(type, JSON.stringify(message)); - } - }]); - - return ParseCloudCodePublisher; -}(); - -exports.ParseCloudCodePublisher = ParseCloudCodePublisher; \ No newline at end of file diff --git a/lib/LiveQuery/ParseLiveQueryServer.js b/lib/LiveQuery/ParseLiveQueryServer.js deleted file mode 100644 index 0b7b03ee72..0000000000 --- a/lib/LiveQuery/ParseLiveQueryServer.js +++ /dev/null @@ -1,727 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ParseLiveQueryServer = undefined; - -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _tv = require('tv4'); - -var _tv2 = _interopRequireDefault(_tv); - -var _node = require('parse/node'); - -var _node2 = _interopRequireDefault(_node); - -var _Subscription = require('./Subscription'); - -var _Client = require('./Client'); - -var _ParseWebSocketServer = require('./ParseWebSocketServer'); - -var _PLog = require('./PLog'); - -var _PLog2 = _interopRequireDefault(_PLog); - -var _RequestSchema = require('./RequestSchema'); - -var _RequestSchema2 = _interopRequireDefault(_RequestSchema); - -var _QueryTools = require('./QueryTools'); - -var _ParsePubSub = require('./ParsePubSub'); - -var _SessionTokenCache = require('./SessionTokenCache'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var ParseLiveQueryServer = function () { - // className -> (queryHash -> subscription) - function ParseLiveQueryServer(server, config) { - var _this = this; - - _classCallCheck(this, ParseLiveQueryServer); - - this.clientId = 0; - this.clients = new Map(); - this.subscriptions = new Map(); - - config = config || {}; - // Set LogLevel - _PLog2.default.level = config.logLevel || 'INFO'; - // Store keys, convert obj to map - var keyPairs = config.keyPairs || {}; - this.keyPairs = new Map(); - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = Object.keys(keyPairs)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var key = _step.value; - - this.keyPairs.set(key, keyPairs[key]); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - _PLog2.default.verbose('Support key pairs', this.keyPairs); - - // Initialize Parse - _node2.default.Object.disableSingleInstance(); - _node2.default.User.enableUnsafeCurrentUser(); - - var serverURL = config.serverURL || _node2.default.serverURL; - _node2.default.serverURL = serverURL; - var appId = config.appId || _node2.default.applicationId; - var javascriptKey = _node2.default.javaScriptKey; - var masterKey = config.masterKey || _node2.default.masterKey; - _node2.default.initialize(appId, javascriptKey, masterKey); - - // Initialize websocket server - this.parseWebSocketServer = new _ParseWebSocketServer.ParseWebSocketServer(server, function (parseWebsocket) { - return _this._onConnect(parseWebsocket); - }, config.websocketTimeout); - - // Initialize subscriber - this.subscriber = _ParsePubSub.ParsePubSub.createSubscriber({ - redisURL: config.redisURL - }); - this.subscriber.subscribe('afterSave'); - this.subscriber.subscribe('afterDelete'); - // Register message handler for subscriber. When publisher get messages, it will publish message - // to the subscribers and the handler will be called. - this.subscriber.on('message', function (channel, messageStr) { - _PLog2.default.verbose('Subscribe messsage %j', messageStr); - var message = JSON.parse(messageStr); - _this._inflateParseObject(message); - if (channel === 'afterSave') { - _this._onAfterSave(message); - } else if (channel === 'afterDelete') { - _this._onAfterDelete(message); - } else { - _PLog2.default.error('Get message %s from unknown channel %j', message, channel); - } - }); - - // Initialize sessionToken cache - this.sessionTokenCache = new _SessionTokenCache.SessionTokenCache(config.cacheTimeout); - } - - // Message is the JSON object from publisher. Message.currentParseObject is the ParseObject JSON after changes. - // Message.originalParseObject is the original ParseObject JSON. - - // The subscriber we use to get object update from publisher - - - _createClass(ParseLiveQueryServer, [{ - key: '_inflateParseObject', - value: function _inflateParseObject(message) { - // Inflate merged object - var currentParseObject = message.currentParseObject; - var className = currentParseObject.className; - var parseObject = new _node2.default.Object(className); - parseObject._finishFetch(currentParseObject); - message.currentParseObject = parseObject; - // Inflate original object - var originalParseObject = message.originalParseObject; - if (originalParseObject) { - className = originalParseObject.className; - parseObject = new _node2.default.Object(className); - parseObject._finishFetch(originalParseObject); - message.originalParseObject = parseObject; - } - } - - // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. - // Message.originalParseObject is the original ParseObject. - - }, { - key: '_onAfterDelete', - value: function _onAfterDelete(message) { - var _this2 = this; - - _PLog2.default.verbose('afterDelete is triggered'); - - var deletedParseObject = message.currentParseObject.toJSON(); - var className = deletedParseObject.className; - _PLog2.default.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id); - _PLog2.default.verbose('Current client number : %d', this.clients.size); - - var classSubscriptions = this.subscriptions.get(className); - if (typeof classSubscriptions === 'undefined') { - _PLog2.default.error('Can not find subscriptions under this class ' + className); - return; - } - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = classSubscriptions.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var subscription = _step2.value; - - var isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription); - if (!isSubscriptionMatched) { - continue; - } - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - var _loop = function _loop() { - var _step3$value = _slicedToArray(_step3.value, 2); - - var clientId = _step3$value[0]; - var requestIds = _step3$value[1]; - - var client = _this2.clients.get(clientId); - if (typeof client === 'undefined') { - return 'continue'; - } - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - var _loop2 = function _loop2() { - var requestId = _step4.value; - - var acl = message.currentParseObject.getACL(); - // Check ACL - _this2._matchesACL(acl, client, requestId).then(function (isMatched) { - if (!isMatched) { - return null; - } - client.pushDelete(requestId, deletedParseObject); - }, function (error) { - _PLog2.default.error('Matching ACL error : ', error); - }); - }; - - for (var _iterator4 = requestIds[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - _loop2(); - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - }; - - for (var _iterator3 = subscription.clientRequestIds.entries()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var _ret = _loop(); - - if (_ret === 'continue') continue; - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - } - - // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. - // Message.originalParseObject is the original ParseObject. - - }, { - key: '_onAfterSave', - value: function _onAfterSave(message) { - var _this3 = this; - - _PLog2.default.verbose('afterSave is triggered'); - - var originalParseObject = null; - if (message.originalParseObject) { - originalParseObject = message.originalParseObject.toJSON(); - } - var currentParseObject = message.currentParseObject.toJSON(); - var className = currentParseObject.className; - _PLog2.default.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id); - _PLog2.default.verbose('Current client number : %d', this.clients.size); - - var classSubscriptions = this.subscriptions.get(className); - if (typeof classSubscriptions === 'undefined') { - _PLog2.default.error('Can not find subscriptions under this class ' + className); - return; - } - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; - - try { - var _loop3 = function _loop3() { - var subscription = _step5.value; - - var isOriginalSubscriptionMatched = _this3._matchesSubscription(originalParseObject, subscription); - var isCurrentSubscriptionMatched = _this3._matchesSubscription(currentParseObject, subscription); - var _iteratorNormalCompletion6 = true; - var _didIteratorError6 = false; - var _iteratorError6 = undefined; - - try { - var _loop4 = function _loop4() { - var _step6$value = _slicedToArray(_step6.value, 2); - - var clientId = _step6$value[0]; - var requestIds = _step6$value[1]; - - var client = _this3.clients.get(clientId); - if (typeof client === 'undefined') { - return 'continue'; - } - var _iteratorNormalCompletion7 = true; - var _didIteratorError7 = false; - var _iteratorError7 = undefined; - - try { - var _loop5 = function _loop5() { - var requestId = _step7.value; - - // Set orignal ParseObject ACL checking promise, if the object does not match - // subscription, we do not need to check ACL - var originalACLCheckingPromise = void 0; - if (!isOriginalSubscriptionMatched) { - originalACLCheckingPromise = _node2.default.Promise.as(false); - } else { - var originalACL = void 0; - if (message.originalParseObject) { - originalACL = message.originalParseObject.getACL(); - } - originalACLCheckingPromise = _this3._matchesACL(originalACL, client, requestId); - } - // Set current ParseObject ACL checking promise, if the object does not match - // subscription, we do not need to check ACL - var currentACLCheckingPromise = void 0; - if (!isCurrentSubscriptionMatched) { - currentACLCheckingPromise = _node2.default.Promise.as(false); - } else { - var currentACL = message.currentParseObject.getACL(); - currentACLCheckingPromise = _this3._matchesACL(currentACL, client, requestId); - } - - _node2.default.Promise.when(originalACLCheckingPromise, currentACLCheckingPromise).then(function (isOriginalMatched, isCurrentMatched) { - _PLog2.default.verbose('Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', originalParseObject, currentParseObject, isOriginalSubscriptionMatched, isCurrentSubscriptionMatched, isOriginalMatched, isCurrentMatched, subscription.hash); - - // Decide event type - var type = void 0; - if (isOriginalMatched && isCurrentMatched) { - type = 'Update'; - } else if (isOriginalMatched && !isCurrentMatched) { - type = 'Leave'; - } else if (!isOriginalMatched && isCurrentMatched) { - if (originalParseObject) { - type = 'Enter'; - } else { - type = 'Create'; - } - } else { - return null; - } - var functionName = 'push' + type; - client[functionName](requestId, currentParseObject); - }, function (error) { - _PLog2.default.error('Matching ACL error : ', error); - }); - }; - - for (var _iterator7 = requestIds[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { - _loop5(); - } - } catch (err) { - _didIteratorError7 = true; - _iteratorError7 = err; - } finally { - try { - if (!_iteratorNormalCompletion7 && _iterator7.return) { - _iterator7.return(); - } - } finally { - if (_didIteratorError7) { - throw _iteratorError7; - } - } - } - }; - - for (var _iterator6 = subscription.clientRequestIds.entries()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { - var _ret4 = _loop4(); - - if (_ret4 === 'continue') continue; - } - } catch (err) { - _didIteratorError6 = true; - _iteratorError6 = err; - } finally { - try { - if (!_iteratorNormalCompletion6 && _iterator6.return) { - _iterator6.return(); - } - } finally { - if (_didIteratorError6) { - throw _iteratorError6; - } - } - } - }; - - for (var _iterator5 = classSubscriptions.values()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - _loop3(); - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } - } - } - }, { - key: '_onConnect', - value: function _onConnect(parseWebsocket) { - var _this4 = this; - - parseWebsocket.on('message', function (request) { - if (typeof request === 'string') { - request = JSON.parse(request); - } - _PLog2.default.verbose('Request: %j', request); - - // Check whether this request is a valid request, return error directly if not - if (!_tv2.default.validate(request, _RequestSchema2.default['general']) || !_tv2.default.validate(request, _RequestSchema2.default[request.op])) { - _Client.Client.pushError(parseWebsocket, 1, _tv2.default.error.message); - _PLog2.default.error('Connect message error %s', _tv2.default.error.message); - return; - } - - switch (request.op) { - case 'connect': - _this4._handleConnect(parseWebsocket, request); - break; - case 'subscribe': - _this4._handleSubscribe(parseWebsocket, request); - break; - case 'unsubscribe': - _this4._handleUnsubscribe(parseWebsocket, request); - break; - default: - _Client.Client.pushError(parseWebsocket, 3, 'Get unknown operation'); - _PLog2.default.error('Get unknown operation', request.op); - } - }); - - parseWebsocket.on('disconnect', function () { - _PLog2.default.log('Client disconnect: %d', parseWebsocket.clientId); - var clientId = parseWebsocket.clientId; - if (!_this4.clients.has(clientId)) { - _PLog2.default.error('Can not find client %d on disconnect', clientId); - return; - } - - // Delete client - var client = _this4.clients.get(clientId); - _this4.clients.delete(clientId); - - // Delete client from subscriptions - var _iteratorNormalCompletion8 = true; - var _didIteratorError8 = false; - var _iteratorError8 = undefined; - - try { - for (var _iterator8 = client.subscriptionInfos.entries()[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { - var _step8$value = _slicedToArray(_step8.value, 2); - - var _requestId = _step8$value[0]; - var subscriptionInfo = _step8$value[1]; - - var _subscription = subscriptionInfo.subscription; - _subscription.deleteClientSubscription(clientId, _requestId); - - // If there is no client which is subscribing this subscription, remove it from subscriptions - var classSubscriptions = _this4.subscriptions.get(_subscription.className); - if (!_subscription.hasSubscribingClient()) { - classSubscriptions.delete(_subscription.hash); - } - // If there is no subscriptions under this class, remove it from subscriptions - if (classSubscriptions.size === 0) { - _this4.subscriptions.delete(_subscription.className); - } - } - } catch (err) { - _didIteratorError8 = true; - _iteratorError8 = err; - } finally { - try { - if (!_iteratorNormalCompletion8 && _iterator8.return) { - _iterator8.return(); - } - } finally { - if (_didIteratorError8) { - throw _iteratorError8; - } - } - } - - _PLog2.default.verbose('Current clients %d', _this4.clients.size); - _PLog2.default.verbose('Current subscriptions %d', _this4.subscriptions.size); - }); - } - }, { - key: '_matchesSubscription', - value: function _matchesSubscription(parseObject, subscription) { - // Object is undefined or null, not match - if (!parseObject) { - return false; - } - return (0, _QueryTools.matchesQuery)(parseObject, subscription.query); - } - }, { - key: '_matchesACL', - value: function _matchesACL(acl, client, requestId) { - var _this5 = this; - - // If ACL is undefined or null, or ACL has public read access, return true directly - if (!acl || acl.getPublicReadAccess()) { - return _node2.default.Promise.as(true); - } - // Check subscription sessionToken matches ACL first - var subscriptionInfo = client.getSubscriptionInfo(requestId); - if (typeof subscriptionInfo === 'undefined') { - return _node2.default.Promise.as(false); - } - - var subscriptionSessionToken = subscriptionInfo.sessionToken; - return this.sessionTokenCache.getUserId(subscriptionSessionToken).then(function (userId) { - return acl.getReadAccess(userId); - }).then(function (isSubscriptionSessionTokenMatched) { - if (isSubscriptionSessionTokenMatched) { - return _node2.default.Promise.as(true); - } - // Check client sessionToken matches ACL - var clientSessionToken = client.sessionToken; - return _this5.sessionTokenCache.getUserId(clientSessionToken).then(function (userId) { - return acl.getReadAccess(userId); - }); - }).then(function (isMatched) { - return _node2.default.Promise.as(isMatched); - }, function (error) { - return _node2.default.Promise.as(false); - }); - } - }, { - key: '_handleConnect', - value: function _handleConnect(parseWebsocket, request) { - if (!this._validateKeys(request, this.keyPairs)) { - _Client.Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); - _PLog2.default.error('Key in request is not valid'); - return; - } - var client = new _Client.Client(this.clientId, parseWebsocket); - parseWebsocket.clientId = this.clientId; - this.clientId += 1; - this.clients.set(parseWebsocket.clientId, client); - _PLog2.default.log('Create new client: %d', parseWebsocket.clientId); - client.pushConnect(); - } - }, { - key: '_validateKeys', - value: function _validateKeys(request, validKeyPairs) { - if (!validKeyPairs || validKeyPairs.size == 0) { - return true; - } - var isValid = false; - var _iteratorNormalCompletion9 = true; - var _didIteratorError9 = false; - var _iteratorError9 = undefined; - - try { - for (var _iterator9 = validKeyPairs[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { - var _step9$value = _slicedToArray(_step9.value, 2); - - var key = _step9$value[0]; - var secret = _step9$value[1]; - - if (!request[key] || request[key] !== secret) { - continue; - } - isValid = true; - break; - } - } catch (err) { - _didIteratorError9 = true; - _iteratorError9 = err; - } finally { - try { - if (!_iteratorNormalCompletion9 && _iterator9.return) { - _iterator9.return(); - } - } finally { - if (_didIteratorError9) { - throw _iteratorError9; - } - } - } - - return isValid; - } - }, { - key: '_handleSubscribe', - value: function _handleSubscribe(parseWebsocket, request) { - // If we can not find this client, return error to client - if (!parseWebsocket.hasOwnProperty('clientId')) { - _Client.Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before subscribing'); - _PLog2.default.error('Can not find this client, make sure you connect to server before subscribing'); - return; - } - var client = this.clients.get(parseWebsocket.clientId); - - // Get subscription from subscriptions, create one if necessary - var subscriptionHash = (0, _QueryTools.queryHash)(request.query); - // Add className to subscriptions if necessary - var className = request.query.className; - if (!this.subscriptions.has(className)) { - this.subscriptions.set(className, new Map()); - } - var classSubscriptions = this.subscriptions.get(className); - var subscription = void 0; - if (classSubscriptions.has(subscriptionHash)) { - subscription = classSubscriptions.get(subscriptionHash); - } else { - subscription = new _Subscription.Subscription(className, request.query.where, subscriptionHash); - classSubscriptions.set(subscriptionHash, subscription); - } - - // Add subscriptionInfo to client - var subscriptionInfo = { - subscription: subscription - }; - // Add selected fields and sessionToken for this subscription if necessary - if (request.query.fields) { - subscriptionInfo.fields = request.query.fields; - } - if (request.sessionToken) { - subscriptionInfo.sessionToken = request.sessionToken; - } - client.addSubscriptionInfo(request.requestId, subscriptionInfo); - - // Add clientId to subscription - subscription.addClientSubscription(parseWebsocket.clientId, request.requestId); - - client.pushSubscribe(request.requestId); - - _PLog2.default.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId); - _PLog2.default.verbose('Current client number: %d', this.clients.size); - } - }, { - key: '_handleUnsubscribe', - value: function _handleUnsubscribe(parseWebsocket, request) { - // If we can not find this client, return error to client - if (!parseWebsocket.hasOwnProperty('clientId')) { - _Client.Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before unsubscribing'); - _PLog2.default.error('Can not find this client, make sure you connect to server before unsubscribing'); - return; - } - var requestId = request.requestId; - var client = this.clients.get(parseWebsocket.clientId); - if (typeof client === 'undefined') { - _Client.Client.pushError(parseWebsocket, 2, 'Cannot find client with clientId ' + parseWebsocket.clientId + '. Make sure you connect to live query server before unsubscribing.'); - _PLog2.default.error('Can not find this client ' + parseWebsocket.clientId); - return; - } - - var subscriptionInfo = client.getSubscriptionInfo(requestId); - if (typeof subscriptionInfo === 'undefined') { - _Client.Client.pushError(parseWebsocket, 2, 'Cannot find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId + '. Make sure you subscribe to live query server before unsubscribing.'); - _PLog2.default.error('Can not find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId); - return; - } - - // Remove subscription from client - client.deleteSubscriptionInfo(requestId); - // Remove client from subscription - var subscription = subscriptionInfo.subscription; - var className = subscription.className; - subscription.deleteClientSubscription(parseWebsocket.clientId, requestId); - // If there is no client which is subscribing this subscription, remove it from subscriptions - var classSubscriptions = this.subscriptions.get(className); - if (!subscription.hasSubscribingClient()) { - classSubscriptions.delete(subscription.hash); - } - // If there is no subscriptions under this class, remove it from subscriptions - if (classSubscriptions.size === 0) { - this.subscriptions.delete(className); - } - - client.pushUnsubscribe(request.requestId); - - _PLog2.default.verbose('Delete client: %d | subscription: %d', parseWebsocket.clientId, request.requestId); - } - }]); - - return ParseLiveQueryServer; -}(); - -ParseLiveQueryServer.setLogLevel = function (logLevel) { - _PLog2.default.logLevel = logLevel; -}; - -exports.ParseLiveQueryServer = ParseLiveQueryServer; \ No newline at end of file diff --git a/lib/LiveQuery/ParsePubSub.js b/lib/LiveQuery/ParsePubSub.js deleted file mode 100644 index d3ba5fcb6f..0000000000 --- a/lib/LiveQuery/ParsePubSub.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ParsePubSub = undefined; - -var _RedisPubSub = require('./RedisPubSub'); - -var _EventEmitterPubSub = require('./EventEmitterPubSub'); - -var ParsePubSub = {}; - -function useRedis(config) { - var redisURL = config.redisURL; - return typeof redisURL !== 'undefined' && redisURL !== ''; -} - -ParsePubSub.createPublisher = function (config) { - if (useRedis(config)) { - return _RedisPubSub.RedisPubSub.createPublisher(config.redisURL); - } else { - return _EventEmitterPubSub.EventEmitterPubSub.createPublisher(); - } -}; - -ParsePubSub.createSubscriber = function (config) { - if (useRedis(config)) { - return _RedisPubSub.RedisPubSub.createSubscriber(config.redisURL); - } else { - return _EventEmitterPubSub.EventEmitterPubSub.createSubscriber(); - } -}; - -exports.ParsePubSub = ParsePubSub; \ No newline at end of file diff --git a/lib/LiveQuery/ParseWebSocketServer.js b/lib/LiveQuery/ParseWebSocketServer.js deleted file mode 100644 index f72aeaf59e..0000000000 --- a/lib/LiveQuery/ParseWebSocketServer.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ParseWebSocket = exports.ParseWebSocketServer = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PLog = require('./PLog'); - -var _PLog2 = _interopRequireDefault(_PLog); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var typeMap = new Map([['disconnect', 'close']]); - -var ParseWebSocketServer = exports.ParseWebSocketServer = function ParseWebSocketServer(server, onConnect) { - var websocketTimeout = arguments.length <= 2 || arguments[2] === undefined ? 10 * 1000 : arguments[2]; - - _classCallCheck(this, ParseWebSocketServer); - - var WebSocketServer = require('ws').Server; - var wss = new WebSocketServer({ server: server }); - wss.on('listening', function () { - _PLog2.default.log('Parse LiveQuery Server starts running'); - }); - wss.on('connection', function (ws) { - onConnect(new ParseWebSocket(ws)); - // Send ping to client periodically - var pingIntervalId = setInterval(function () { - if (ws.readyState == ws.OPEN) { - ws.ping(); - } else { - clearInterval(pingIntervalId); - } - }, websocketTimeout); - }); - this.server = wss; -}; - -var ParseWebSocket = exports.ParseWebSocket = function () { - function ParseWebSocket(ws) { - _classCallCheck(this, ParseWebSocket); - - this.ws = ws; - } - - _createClass(ParseWebSocket, [{ - key: 'on', - value: function on(type, callback) { - var wsType = typeMap.has(type) ? typeMap.get(type) : type; - this.ws.on(wsType, callback); - } - }, { - key: 'send', - value: function send(message, channel) { - this.ws.send(message); - } - }]); - - return ParseWebSocket; -}(); \ No newline at end of file diff --git a/lib/LiveQuery/QueryTools.js b/lib/LiveQuery/QueryTools.js deleted file mode 100644 index 7622c44f24..0000000000 --- a/lib/LiveQuery/QueryTools.js +++ /dev/null @@ -1,282 +0,0 @@ -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var equalObjects = require('./equalObjects'); -var Id = require('./Id'); -var Parse = require('parse/node'); - -/** - * Query Hashes are deterministic hashes for Parse Queries. - * Any two queries that have the same set of constraints will produce the same - * hash. This lets us reliably group components by the queries they depend upon, - * and quickly determine if a query has changed. - */ - -/** - * Convert $or queries into an array of where conditions - */ -function flattenOrQueries(where) { - if (!where.hasOwnProperty('$or')) { - return where; - } - var accum = []; - for (var i = 0; i < where.$or.length; i++) { - accum = accum.concat(where.$or[i]); - } - return accum; -} - -/** - * Deterministically turns an object into a string. Disregards ordering - */ -function stringify(object) { - if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object' || object === null) { - if (typeof object === 'string') { - return '"' + object.replace(/\|/g, '%|') + '"'; - } - return object + ''; - } - if (Array.isArray(object)) { - var copy = object.map(stringify); - copy.sort(); - return '[' + copy.join(',') + ']'; - } - var sections = []; - var keys = Object.keys(object); - keys.sort(); - for (var k = 0; k < keys.length; k++) { - sections.push(stringify(keys[k]) + ':' + stringify(object[keys[k]])); - } - return '{' + sections.join(',') + '}'; -} - -/** - * Generate a hash from a query, with unique fields for columns, values, order, - * skip, and limit. - */ -function queryHash(query) { - if (query instanceof Parse.Query) { - query = { - className: query.className, - where: query._where - }; - } - var where = flattenOrQueries(query.where || {}); - var columns = []; - var values = []; - var i; - if (Array.isArray(where)) { - var uniqueColumns = {}; - for (i = 0; i < where.length; i++) { - var subValues = {}; - var keys = Object.keys(where[i]); - keys.sort(); - for (var j = 0; j < keys.length; j++) { - subValues[keys[j]] = where[i][keys[j]]; - uniqueColumns[keys[j]] = true; - } - values.push(subValues); - } - columns = Object.keys(uniqueColumns); - columns.sort(); - } else { - columns = Object.keys(where); - columns.sort(); - for (i = 0; i < columns.length; i++) { - values.push(where[columns[i]]); - } - } - - var sections = [columns.join(','), stringify(values)]; - - return query.className + ':' + sections.join('|'); -} - -/** - * matchesQuery -- Determines if an object would be returned by a Parse Query - * It's a lightweight, where-clause only implementation of a full query engine. - * Since we find queries that match objects, rather than objects that match - * queries, we can avoid building a full-blown query tool. - */ -function matchesQuery(object, query) { - if (query instanceof Parse.Query) { - var className = object.id instanceof Id ? object.id.className : object.className; - if (className !== query.className) { - return false; - } - return matchesQuery(object, query._where); - } - for (var field in query) { - if (!matchesKeyConstraints(object, field, query[field])) { - return false; - } - } - return true; -} - -/** - * Determines whether an object matches a single key's constraints - */ -function matchesKeyConstraints(object, key, constraints) { - if (constraints === null) { - return false; - } - var i; - if (key === '$or') { - for (i = 0; i < constraints.length; i++) { - if (matchesQuery(object, constraints[i])) { - return true; - } - } - return false; - } - if (key === '$relatedTo') { - // Bail! We can't handle relational queries locally - return false; - } - // Equality (or Array contains) cases - if ((typeof constraints === 'undefined' ? 'undefined' : _typeof(constraints)) !== 'object') { - if (Array.isArray(object[key])) { - return object[key].indexOf(constraints) > -1; - } - return object[key] === constraints; - } - var compareTo; - if (constraints.__type) { - if (constraints.__type === 'Pointer') { - return typeof object[key] !== 'undefined' && constraints.className === object[key].className && constraints.objectId === object[key].objectId; - } - compareTo = Parse._decode(key, constraints); - if (Array.isArray(object[key])) { - for (i = 0; i < object[key].length; i++) { - if (equalObjects(object[key][i], compareTo)) { - return true; - } - } - return false; - } - return equalObjects(object[key], compareTo); - } - // More complex cases - for (var condition in constraints) { - compareTo = constraints[condition]; - if (compareTo.__type) { - compareTo = Parse._decode(key, compareTo); - } - switch (condition) { - case '$lt': - if (object[key] >= compareTo) { - return false; - } - break; - case '$lte': - if (object[key] > compareTo) { - return false; - } - break; - case '$gt': - if (object[key] <= compareTo) { - return false; - } - break; - case '$gte': - if (object[key] < compareTo) { - return false; - } - break; - case '$ne': - if (equalObjects(object[key], compareTo)) { - return false; - } - break; - case '$in': - if (compareTo.indexOf(object[key]) < 0) { - return false; - } - break; - case '$nin': - if (compareTo.indexOf(object[key]) > -1) { - return false; - } - break; - case '$all': - for (i = 0; i < compareTo.length; i++) { - if (object[key].indexOf(compareTo[i]) < 0) { - return false; - } - } - break; - case '$exists': - var propertyExists = typeof object[key] !== 'undefined'; - var existenceIsRequired = constraints['$exists']; - if (typeof constraints['$exists'] !== 'boolean') { - // The SDK will never submit a non-boolean for $exists, but if someone - // tries to submit a non-boolean for $exits outside the SDKs, just ignore it. - break; - } - if (!propertyExists && existenceIsRequired || propertyExists && !existenceIsRequired) { - return false; - } - break; - case '$regex': - if ((typeof compareTo === 'undefined' ? 'undefined' : _typeof(compareTo)) === 'object') { - return compareTo.test(object[key]); - } - // JS doesn't support perl-style escaping - var expString = ''; - var escapeEnd = -2; - var escapeStart = compareTo.indexOf('\\Q'); - while (escapeStart > -1) { - // Add the unescaped portion - expString += compareTo.substring(escapeEnd + 2, escapeStart); - escapeEnd = compareTo.indexOf('\\E', escapeStart); - if (escapeEnd > -1) { - expString += compareTo.substring(escapeStart + 2, escapeEnd).replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&'); - } - - escapeStart = compareTo.indexOf('\\Q', escapeEnd); - } - expString += compareTo.substring(Math.max(escapeStart, escapeEnd + 2)); - var exp = new RegExp(expString, constraints.$options || ''); - if (!exp.test(object[key])) { - return false; - } - break; - case '$nearSphere': - var distance = compareTo.radiansTo(object[key]); - var max = constraints.$maxDistance || Infinity; - return distance <= max; - case '$within': - var southWest = compareTo.$box[0]; - var northEast = compareTo.$box[1]; - if (southWest.latitude > northEast.latitude || southWest.longitude > northEast.longitude) { - // Invalid box, crosses the date line - return false; - } - return object[key].latitude > southWest.latitude && object[key].latitude < northEast.latitude && object[key].longitude > southWest.longitude && object[key].longitude < northEast.longitude; - case '$options': - // Not a query type, but a way to add options to $regex. Ignore and - // avoid the default - break; - case '$maxDistance': - // Not a query type, but a way to add a cap to $nearSphere. Ignore and - // avoid the default - break; - case '$select': - return false; - case '$dontSelect': - return false; - default: - return false; - } - } - return true; -} - -var QueryTools = { - queryHash: queryHash, - matchesQuery: matchesQuery -}; - -module.exports = QueryTools; \ No newline at end of file diff --git a/lib/LiveQuery/RedisPubSub.js b/lib/LiveQuery/RedisPubSub.js deleted file mode 100644 index 7def28dbe7..0000000000 --- a/lib/LiveQuery/RedisPubSub.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.RedisPubSub = undefined; - -var _redis = require('redis'); - -var _redis2 = _interopRequireDefault(_redis); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function createPublisher(redisURL) { - return _redis2.default.createClient(redisURL, { no_ready_check: true }); -} - -function createSubscriber(redisURL) { - return _redis2.default.createClient(redisURL, { no_ready_check: true }); -} - -var RedisPubSub = { - createPublisher: createPublisher, - createSubscriber: createSubscriber -}; - -exports.RedisPubSub = RedisPubSub; \ No newline at end of file diff --git a/lib/LiveQuery/RequestSchema.js b/lib/LiveQuery/RequestSchema.js deleted file mode 100644 index 8ae11cafb4..0000000000 --- a/lib/LiveQuery/RequestSchema.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var general = { - 'title': 'General request schema', - 'type': 'object', - 'properties': { - 'op': { - 'type': 'string', - 'enum': ['connect', 'subscribe', 'unsubscribe'] - } - } -}; - -var connect = { - 'title': 'Connect operation schema', - 'type': 'object', - 'properties': { - 'op': 'connect', - 'applicationId': { - 'type': 'string' - }, - 'javascriptKey': { - type: 'string' - }, - 'masterKey': { - type: 'string' - }, - 'clientKey': { - type: 'string' - }, - 'windowsKey': { - type: 'string' - }, - 'restAPIKey': { - 'type': 'string' - }, - 'sessionToken': { - 'type': 'string' - } - }, - 'required': ['op', 'applicationId'], - "additionalProperties": false -}; - -var subscribe = { - 'title': 'Subscribe operation schema', - 'type': 'object', - 'properties': { - 'op': 'subscribe', - 'requestId': { - 'type': 'number' - }, - 'query': { - 'title': 'Query field schema', - 'type': 'object', - 'properties': { - 'className': { - 'type': 'string' - }, - 'where': { - 'type': 'object' - }, - 'fields': { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - } - }, - 'required': ['where', 'className'], - 'additionalProperties': false - }, - 'sessionToken': { - 'type': 'string' - } - }, - 'required': ['op', 'requestId', 'query'], - 'additionalProperties': false -}; - -var unsubscribe = { - 'title': 'Unsubscribe operation schema', - 'type': 'object', - 'properties': { - 'op': 'unsubscribe', - 'requestId': { - 'type': 'number' - } - }, - 'required': ['op', 'requestId'], - "additionalProperties": false -}; - -var RequestSchema = { - 'general': general, - 'connect': connect, - 'subscribe': subscribe, - 'unsubscribe': unsubscribe -}; - -exports.default = RequestSchema; \ No newline at end of file diff --git a/lib/LiveQuery/SessionTokenCache.js b/lib/LiveQuery/SessionTokenCache.js deleted file mode 100644 index 2045744b0f..0000000000 --- a/lib/LiveQuery/SessionTokenCache.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SessionTokenCache = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _node = require('parse/node'); - -var _node2 = _interopRequireDefault(_node); - -var _lruCache = require('lru-cache'); - -var _lruCache2 = _interopRequireDefault(_lruCache); - -var _PLog = require('./PLog'); - -var _PLog2 = _interopRequireDefault(_PLog); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var SessionTokenCache = function () { - function SessionTokenCache() { - var timeout = arguments.length <= 0 || arguments[0] === undefined ? 30 * 24 * 60 * 60 * 1000 : arguments[0]; - var maxSize = arguments.length <= 1 || arguments[1] === undefined ? 10000 : arguments[1]; - - _classCallCheck(this, SessionTokenCache); - - this.cache = new _lruCache2.default({ - max: maxSize, - maxAge: timeout - }); - } - - _createClass(SessionTokenCache, [{ - key: 'getUserId', - value: function getUserId(sessionToken) { - var _this = this; - - if (!sessionToken) { - return _node2.default.Promise.error('Empty sessionToken'); - } - var userId = this.cache.get(sessionToken); - if (userId) { - _PLog2.default.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken); - return _node2.default.Promise.as(userId); - } - return _node2.default.User.become(sessionToken).then(function (user) { - _PLog2.default.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken); - var userId = user.id; - _this.cache.set(sessionToken, userId); - return _node2.default.Promise.as(userId); - }, function (error) { - _PLog2.default.error('Can not fetch userId for sessionToken %j, error %j', sessionToken, error); - return _node2.default.Promise.error(error); - }); - } - }]); - - return SessionTokenCache; -}(); - -exports.SessionTokenCache = SessionTokenCache; \ No newline at end of file diff --git a/lib/LiveQuery/Subscription.js b/lib/LiveQuery/Subscription.js deleted file mode 100644 index d9d9453563..0000000000 --- a/lib/LiveQuery/Subscription.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Subscription = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _QueryTools = require('./QueryTools'); - -var _PLog = require('./PLog'); - -var _PLog2 = _interopRequireDefault(_PLog); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var Subscription = function () { - // It is query condition eg query.where - function Subscription(className, query, queryHash) { - _classCallCheck(this, Subscription); - - this.className = className; - this.query = query; - this.hash = queryHash; - this.clientRequestIds = new Map(); - } - - _createClass(Subscription, [{ - key: 'addClientSubscription', - value: function addClientSubscription(clientId, requestId) { - if (!this.clientRequestIds.has(clientId)) { - this.clientRequestIds.set(clientId, []); - } - var requestIds = this.clientRequestIds.get(clientId); - requestIds.push(requestId); - } - }, { - key: 'deleteClientSubscription', - value: function deleteClientSubscription(clientId, requestId) { - var requestIds = this.clientRequestIds.get(clientId); - if (typeof requestIds === 'undefined') { - _PLog2.default.error('Can not find client %d to delete', clientId); - return; - } - - var index = requestIds.indexOf(requestId); - if (index < 0) { - _PLog2.default.error('Can not find client %d subscription %d to delete', clientId, requestId); - return; - } - requestIds.splice(index, 1); - // Delete client reference if it has no subscription - if (requestIds.length == 0) { - this.clientRequestIds.delete(clientId); - } - } - }, { - key: 'hasSubscribingClient', - value: function hasSubscribingClient() { - return this.clientRequestIds.size > 0; - } - }]); - - return Subscription; -}(); - -exports.Subscription = Subscription; \ No newline at end of file diff --git a/lib/LiveQuery/equalObjects.js b/lib/LiveQuery/equalObjects.js deleted file mode 100644 index a0e058a2c9..0000000000 --- a/lib/LiveQuery/equalObjects.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var toString = Object.prototype.toString; - -/** - * Determines whether two objects represent the same primitive, special Parse - * type, or full Parse Object. - */ -function equalObjects(a, b) { - if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) !== (typeof b === 'undefined' ? 'undefined' : _typeof(b))) { - return false; - } - if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) !== 'object') { - return a === b; - } - if (a === b) { - return true; - } - if (toString.call(a) === '[object Date]') { - if (toString.call(b) === '[object Date]') { - return +a === +b; - } - return false; - } - if (Array.isArray(a)) { - if (Array.isArray(b)) { - if (a.length !== b.length) { - return false; - } - for (var i = 0; i < a.length; i++) { - if (!equalObjects(a[i], b[i])) { - return false; - } - } - return true; - } - return false; - } - if (Object.keys(a).length !== Object.keys(b).length) { - return false; - } - for (var key in a) { - if (!equalObjects(a[key], b[key])) { - return false; - } - } - return true; -} - -module.exports = equalObjects; \ No newline at end of file diff --git a/lib/ParseServer.js b/lib/ParseServer.js deleted file mode 100644 index cc019c6676..0000000000 --- a/lib/ParseServer.js +++ /dev/null @@ -1,453 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _logger = require('./logger'); - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -var _Config = require('./Config'); - -var _Config2 = _interopRequireDefault(_Config); - -var _package = require('../package.json'); - -var _package2 = _interopRequireDefault(_package); - -var _PromiseRouter = require('./PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _requiredParameter = require('./requiredParameter'); - -var _requiredParameter2 = _interopRequireDefault(_requiredParameter); - -var _AnalyticsRouter = require('./Routers/AnalyticsRouter'); - -var _ClassesRouter = require('./Routers/ClassesRouter'); - -var _FeaturesRouter = require('./Routers/FeaturesRouter'); - -var _InMemoryCacheAdapter = require('./Adapters/Cache/InMemoryCacheAdapter'); - -var _AnalyticsController = require('./Controllers/AnalyticsController'); - -var _CacheController = require('./Controllers/CacheController'); - -var _AnalyticsAdapter = require('./Adapters/Analytics/AnalyticsAdapter'); - -var _FileLoggerAdapter = require('./Adapters/Logger/FileLoggerAdapter'); - -var _FilesController = require('./Controllers/FilesController'); - -var _FilesRouter = require('./Routers/FilesRouter'); - -var _FunctionsRouter = require('./Routers/FunctionsRouter'); - -var _GlobalConfigRouter = require('./Routers/GlobalConfigRouter'); - -var _GridStoreAdapter = require('./Adapters/Files/GridStoreAdapter'); - -var _HooksController = require('./Controllers/HooksController'); - -var _HooksRouter = require('./Routers/HooksRouter'); - -var _IAPValidationRouter = require('./Routers/IAPValidationRouter'); - -var _InstallationsRouter = require('./Routers/InstallationsRouter'); - -var _AdapterLoader = require('./Adapters/AdapterLoader'); - -var _LiveQueryController = require('./Controllers/LiveQueryController'); - -var _LoggerController = require('./Controllers/LoggerController'); - -var _LogsRouter = require('./Routers/LogsRouter'); - -var _ParseLiveQueryServer = require('./LiveQuery/ParseLiveQueryServer'); - -var _PublicAPIRouter = require('./Routers/PublicAPIRouter'); - -var _PushController = require('./Controllers/PushController'); - -var _PushRouter = require('./Routers/PushRouter'); - -var _cryptoUtils = require('./cryptoUtils'); - -var _RolesRouter = require('./Routers/RolesRouter'); - -var _SchemasRouter = require('./Routers/SchemasRouter'); - -var _SessionsRouter = require('./Routers/SessionsRouter'); - -var _UserController = require('./Controllers/UserController'); - -var _UsersRouter = require('./Routers/UsersRouter'); - -var _PurgeRouter = require('./Routers/PurgeRouter'); - -var _DatabaseController = require('./Controllers/DatabaseController'); - -var _DatabaseController2 = _interopRequireDefault(_DatabaseController); - -var _SchemaCache = require('./Controllers/SchemaCache'); - -var _SchemaCache2 = _interopRequireDefault(_SchemaCache); - -var _parseServerPushAdapter = require('parse-server-push-adapter'); - -var _parseServerPushAdapter2 = _interopRequireDefault(_parseServerPushAdapter); - -var _MongoStorageAdapter = require('./Adapters/Storage/Mongo/MongoStorageAdapter'); - -var _MongoStorageAdapter2 = _interopRequireDefault(_MongoStorageAdapter); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -// ParseServer - open-source compatible API Server for Parse apps - -var batch = require('./batch'), - bodyParser = require('body-parser'), - DatabaseAdapter = require('./DatabaseAdapter'), - express = require('express'), - middlewares = require('./middlewares'), - multer = require('multer'), - Parse = require('parse/node').Parse, - path = require('path'), - authDataManager = require('./authDataManager'); - -if (!global._babelPolyfill) { - require('babel-polyfill'); -} - -var SchemaController = require('./Controllers/SchemaController'); - -// Mutate the Parse object to add the Cloud Code handlers -addParseCloud(); - -var requiredUserFields = { fields: _extends({}, SchemaController.defaultColumns._Default, SchemaController.defaultColumns._User) }; - -// ParseServer works like a constructor of an express app. -// The args that we understand are: -// "analyticsAdapter": an adapter class for analytics -// "filesAdapter": a class like GridStoreAdapter providing create, get, -// and delete -// "loggerAdapter": a class like FileLoggerAdapter providing info, error, -// and query -// "jsonLogs": log as structured JSON objects -// "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us -// what database this Parse API connects to. -// "cloud": relative location to cloud code to require, or a function -// that is given an instance of Parse as a parameter. Use this instance of Parse -// to register your cloud code hooks and functions. -// "appId": the application id to host -// "masterKey": the master key for requests to this app -// "facebookAppIds": an array of valid Facebook Application IDs, required -// if using Facebook login -// "collectionPrefix": optional prefix for database collection names -// "fileKey": optional key from Parse dashboard for supporting older files -// hosted by Parse -// "clientKey": optional key from Parse dashboard -// "dotNetKey": optional key from Parse dashboard -// "restAPIKey": optional key from Parse dashboard -// "webhookKey": optional key from Parse dashboard -// "javascriptKey": optional key from Parse dashboard -// "push": optional key from configure push -// "sessionLength": optional length in seconds for how long Sessions should be valid for - -var ParseServer = function () { - function ParseServer(_ref) { - var _ref$appId = _ref.appId; - var appId = _ref$appId === undefined ? (0, _requiredParameter2.default)('You must provide an appId!') : _ref$appId; - var _ref$masterKey = _ref.masterKey; - var masterKey = _ref$masterKey === undefined ? (0, _requiredParameter2.default)('You must provide a masterKey!') : _ref$masterKey; - var appName = _ref.appName; - var _ref$analyticsAdapter = _ref.analyticsAdapter; - var analyticsAdapter = _ref$analyticsAdapter === undefined ? undefined : _ref$analyticsAdapter; - var filesAdapter = _ref.filesAdapter; - var push = _ref.push; - var loggerAdapter = _ref.loggerAdapter; - var jsonLogs = _ref.jsonLogs; - var logsFolder = _ref.logsFolder; - var databaseURI = _ref.databaseURI; - var databaseOptions = _ref.databaseOptions; - var databaseAdapter = _ref.databaseAdapter; - var cloud = _ref.cloud; - var _ref$collectionPrefix = _ref.collectionPrefix; - var collectionPrefix = _ref$collectionPrefix === undefined ? '' : _ref$collectionPrefix; - var clientKey = _ref.clientKey; - var javascriptKey = _ref.javascriptKey; - var dotNetKey = _ref.dotNetKey; - var restAPIKey = _ref.restAPIKey; - var webhookKey = _ref.webhookKey; - var _ref$fileKey = _ref.fileKey; - var fileKey = _ref$fileKey === undefined ? undefined : _ref$fileKey; - var _ref$facebookAppIds = _ref.facebookAppIds; - var facebookAppIds = _ref$facebookAppIds === undefined ? [] : _ref$facebookAppIds; - var _ref$enableAnonymousU = _ref.enableAnonymousUsers; - var enableAnonymousUsers = _ref$enableAnonymousU === undefined ? true : _ref$enableAnonymousU; - var _ref$allowClientClass = _ref.allowClientClassCreation; - var allowClientClassCreation = _ref$allowClientClass === undefined ? true : _ref$allowClientClass; - var _ref$oauth = _ref.oauth; - var oauth = _ref$oauth === undefined ? {} : _ref$oauth; - var _ref$serverURL = _ref.serverURL; - var serverURL = _ref$serverURL === undefined ? (0, _requiredParameter2.default)('You must provide a serverURL!') : _ref$serverURL; - var _ref$maxUploadSize = _ref.maxUploadSize; - var maxUploadSize = _ref$maxUploadSize === undefined ? '20mb' : _ref$maxUploadSize; - var _ref$verifyUserEmails = _ref.verifyUserEmails; - var verifyUserEmails = _ref$verifyUserEmails === undefined ? false : _ref$verifyUserEmails; - var _ref$preventLoginWith = _ref.preventLoginWithUnverifiedEmail; - var preventLoginWithUnverifiedEmail = _ref$preventLoginWith === undefined ? false : _ref$preventLoginWith; - var emailVerifyTokenValidityDuration = _ref.emailVerifyTokenValidityDuration; - var cacheAdapter = _ref.cacheAdapter; - var emailAdapter = _ref.emailAdapter; - var publicServerURL = _ref.publicServerURL; - var _ref$customPages = _ref.customPages; - var customPages = _ref$customPages === undefined ? { - invalidLink: undefined, - verifyEmailSuccess: undefined, - choosePassword: undefined, - passwordResetSuccess: undefined - } : _ref$customPages; - var _ref$liveQuery = _ref.liveQuery; - var liveQuery = _ref$liveQuery === undefined ? {} : _ref$liveQuery; - var _ref$sessionLength = _ref.sessionLength; - var sessionLength = _ref$sessionLength === undefined ? 31536000 : _ref$sessionLength; - var _ref$expireInactiveSe = _ref.expireInactiveSessions; - var expireInactiveSessions = _ref$expireInactiveSe === undefined ? true : _ref$expireInactiveSe; - var _ref$verbose = _ref.verbose; - var verbose = _ref$verbose === undefined ? false : _ref$verbose; - var _ref$revokeSessionOnP = _ref.revokeSessionOnPasswordReset; - var revokeSessionOnPasswordReset = _ref$revokeSessionOnP === undefined ? true : _ref$revokeSessionOnP; - var _ref$schemaCacheTTL = _ref.schemaCacheTTL; - var schemaCacheTTL = _ref$schemaCacheTTL === undefined ? 5 : _ref$schemaCacheTTL; - var _ref$__indexBuildComp = _ref.__indexBuildCompletionCallbackForTests; - - var __indexBuildCompletionCallbackForTests = _ref$__indexBuildComp === undefined ? function () {} : _ref$__indexBuildComp; - - _classCallCheck(this, ParseServer); - - // Initialize the node client SDK automatically - Parse.initialize(appId, javascriptKey || 'unused', masterKey); - Parse.serverURL = serverURL; - if ((databaseOptions || databaseURI || collectionPrefix !== '') && databaseAdapter) { - throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/connectionPrefix.'; - } else if (!databaseAdapter) { - databaseAdapter = new _MongoStorageAdapter2.default({ - uri: databaseURI, - collectionPrefix: collectionPrefix, - mongoOptions: databaseOptions - }); - } else { - databaseAdapter = (0, _AdapterLoader.loadAdapter)(databaseAdapter); - } - - if (!filesAdapter && !databaseURI) { - throw 'When using an explicit database adapter, you must also use and explicit filesAdapter.'; - } - - if (logsFolder) { - (0, _logger.configureLogger)({ logsFolder: logsFolder, jsonLogs: jsonLogs }); - } - - if (cloud) { - addParseCloud(); - if (typeof cloud === 'function') { - cloud(Parse); - } else if (typeof cloud === 'string') { - require(path.resolve(process.cwd(), cloud)); - } else { - throw "argument 'cloud' must either be a string or a function"; - } - } - - if (verbose || process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER) { - (0, _logger.configureLogger)({ level: 'silly', jsonLogs: jsonLogs }); - } - - var filesControllerAdapter = (0, _AdapterLoader.loadAdapter)(filesAdapter, function () { - return new _GridStoreAdapter.GridStoreAdapter(databaseURI); - }); - // Pass the push options too as it works with the default - var pushControllerAdapter = (0, _AdapterLoader.loadAdapter)(push && push.adapter, _parseServerPushAdapter2.default, push || {}); - var loggerControllerAdapter = (0, _AdapterLoader.loadAdapter)(loggerAdapter, _FileLoggerAdapter.FileLoggerAdapter); - var emailControllerAdapter = (0, _AdapterLoader.loadAdapter)(emailAdapter); - var cacheControllerAdapter = (0, _AdapterLoader.loadAdapter)(cacheAdapter, _InMemoryCacheAdapter.InMemoryCacheAdapter, { appId: appId }); - var analyticsControllerAdapter = (0, _AdapterLoader.loadAdapter)(analyticsAdapter, _AnalyticsAdapter.AnalyticsAdapter); - - // We pass the options and the base class for the adatper, - // Note that passing an instance would work too - var filesController = new _FilesController.FilesController(filesControllerAdapter, appId); - var pushController = new _PushController.PushController(pushControllerAdapter, appId, push); - var loggerController = new _LoggerController.LoggerController(loggerControllerAdapter, appId); - var userController = new _UserController.UserController(emailControllerAdapter, appId, { verifyUserEmails: verifyUserEmails }); - var liveQueryController = new _LiveQueryController.LiveQueryController(liveQuery); - var cacheController = new _CacheController.CacheController(cacheControllerAdapter, appId); - var databaseController = new _DatabaseController2.default(databaseAdapter, new _SchemaCache2.default(cacheController, schemaCacheTTL)); - var hooksController = new _HooksController.HooksController(appId, databaseController, webhookKey); - var analyticsController = new _AnalyticsController.AnalyticsController(analyticsControllerAdapter); - - // TODO: create indexes on first creation of a _User object. Otherwise it's impossible to - // have a Parse app without it having a _User collection. - var userClassPromise = databaseController.loadSchema().then(function (schema) { - return schema.enforceClassExists('_User'); - }); - - var usernameUniqueness = userClassPromise.then(function () { - return databaseController.adapter.ensureUniqueness('_User', requiredUserFields, ['username']); - }).catch(function (error) { - _logger.logger.warn('Unable to ensure uniqueness for usernames: ', error); - return Promise.reject(error); - }); - - var emailUniqueness = userClassPromise.then(function () { - return databaseController.adapter.ensureUniqueness('_User', requiredUserFields, ['email']); - }).catch(function (error) { - _logger.logger.warn('Unable to ensure uniqueness for user email addresses: ', error); - return Promise.reject(error); - }); - - _cache2.default.put(appId, { - appId: appId, - masterKey: masterKey, - serverURL: serverURL, - collectionPrefix: collectionPrefix, - clientKey: clientKey, - javascriptKey: javascriptKey, - dotNetKey: dotNetKey, - restAPIKey: restAPIKey, - webhookKey: webhookKey, - fileKey: fileKey, - facebookAppIds: facebookAppIds, - analyticsController: analyticsController, - cacheController: cacheController, - filesController: filesController, - pushController: pushController, - loggerController: loggerController, - hooksController: hooksController, - userController: userController, - verifyUserEmails: verifyUserEmails, - preventLoginWithUnverifiedEmail: preventLoginWithUnverifiedEmail, - emailVerifyTokenValidityDuration: emailVerifyTokenValidityDuration, - allowClientClassCreation: allowClientClassCreation, - authDataManager: authDataManager(oauth, enableAnonymousUsers), - appName: appName, - publicServerURL: publicServerURL, - customPages: customPages, - maxUploadSize: maxUploadSize, - liveQueryController: liveQueryController, - sessionLength: Number(sessionLength), - expireInactiveSessions: expireInactiveSessions, - jsonLogs: jsonLogs, - revokeSessionOnPasswordReset: revokeSessionOnPasswordReset, - databaseController: databaseController, - schemaCacheTTL: schemaCacheTTL - }); - - // To maintain compatibility. TODO: Remove in some version that breaks backwards compatability - if (process.env.FACEBOOK_APP_ID) { - _cache2.default.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID); - } - - _Config2.default.validate(_cache2.default.get(appId)); - this.config = _cache2.default.get(appId); - hooksController.load(); - - // Note: Tests will start to fail if any validation happens after this is called. - if (process.env.TESTING) { - __indexBuildCompletionCallbackForTests(Promise.all([usernameUniqueness, emailUniqueness])); - } - } - - _createClass(ParseServer, [{ - key: 'app', - get: function get() { - return ParseServer.app(this.config); - } - }], [{ - key: 'app', - value: function app(_ref2) { - var _ref2$maxUploadSize = _ref2.maxUploadSize; - var maxUploadSize = _ref2$maxUploadSize === undefined ? '20mb' : _ref2$maxUploadSize; - var appId = _ref2.appId; - - // This app serves the Parse API directly. - // It's the equivalent of https://api.parse.com/1 in the hosted Parse API. - var api = express(); - //api.use("/apps", express.static(__dirname + "/public")); - // File handling needs to be before default middlewares are applied - api.use('/', middlewares.allowCrossDomain, new _FilesRouter.FilesRouter().getExpressRouter({ - maxUploadSize: maxUploadSize - })); - - api.use('/', bodyParser.urlencoded({ extended: false }), new _PublicAPIRouter.PublicAPIRouter().expressApp()); - - // TODO: separate this from the regular ParseServer object - if (process.env.TESTING == 1) { - api.use('/', require('./testing-routes').router); - } - - api.use(bodyParser.json({ 'type': '*/*', limit: maxUploadSize })); - api.use(middlewares.allowCrossDomain); - api.use(middlewares.allowMethodOverride); - api.use(middlewares.handleParseHeaders); - - var routers = [new _ClassesRouter.ClassesRouter(), new _UsersRouter.UsersRouter(), new _SessionsRouter.SessionsRouter(), new _RolesRouter.RolesRouter(), new _AnalyticsRouter.AnalyticsRouter(), new _InstallationsRouter.InstallationsRouter(), new _FunctionsRouter.FunctionsRouter(), new _SchemasRouter.SchemasRouter(), new _PushRouter.PushRouter(), new _LogsRouter.LogsRouter(), new _IAPValidationRouter.IAPValidationRouter(), new _FeaturesRouter.FeaturesRouter(), new _GlobalConfigRouter.GlobalConfigRouter(), new _PurgeRouter.PurgeRouter()]; - - if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) { - routers.push(new _HooksRouter.HooksRouter()); - } - - var routes = routers.reduce(function (memo, router) { - return memo.concat(router.routes); - }, []); - - var appRouter = new _PromiseRouter2.default(routes, appId); - - batch.mountOnto(appRouter); - - api.use(appRouter.expressApp()); - - api.use(middlewares.handleParseErrors); - - //This causes tests to spew some useless warnings, so disable in test - if (!process.env.TESTING) { - process.on('uncaughtException', function (err) { - if (err.code === "EADDRINUSE") { - // user-friendly message for this common error - console.error('Unable to listen on port ' + err.port + '. The port is already in use.'); - process.exit(0); - } else { - throw err; - } - }); - } - return api; - } - }, { - key: 'createLiveQueryServer', - value: function createLiveQueryServer(httpServer, config) { - return new _ParseLiveQueryServer.ParseLiveQueryServer(httpServer, config); - } - }]); - - return ParseServer; -}(); - -function addParseCloud() { - var ParseCloud = require("./cloud-code/Parse.Cloud"); - Object.assign(Parse.Cloud, ParseCloud); - global.Parse = Parse; -} - -exports.default = ParseServer; \ No newline at end of file diff --git a/lib/PromiseRouter.js b/lib/PromiseRouter.js deleted file mode 100644 index 49b8a26828..0000000000 --- a/lib/PromiseRouter.js +++ /dev/null @@ -1,397 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // A router that is based on promises rather than req/res/next. -// This is intended to replace the use of express.Router to handle -// subsections of the API surface. -// This will make it easier to have methods like 'batch' that -// themselves use our routing information, without disturbing express -// components that external developers may be modifying. - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -var _express = require('express'); - -var _express2 = _interopRequireDefault(_express); - -var _url = require('url'); - -var _url2 = _interopRequireDefault(_url); - -var _logger = require('./logger'); - -var _logger2 = _interopRequireDefault(_logger); - -var _util = require('util'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var PromiseRouter = function () { - // Each entry should be an object with: - // path: the path to route, in express format - // method: the HTTP method that this route handles. - // Must be one of: POST, GET, PUT, DELETE - // handler: a function that takes request, and returns a promise. - // Successful handlers should resolve to an object with fields: - // status: optional. the http status code. defaults to 200 - // response: a json object with the content of the response - // location: optional. a location header - function PromiseRouter() { - var routes = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; - var appId = arguments[1]; - - _classCallCheck(this, PromiseRouter); - - this.routes = routes; - this.appId = appId; - this.mountRoutes(); - } - - // Leave the opportunity to - // subclasses to mount their routes by overriding - - - _createClass(PromiseRouter, [{ - key: 'mountRoutes', - value: function mountRoutes() {} - - // Merge the routes into this one - - }, { - key: 'merge', - value: function merge(router) { - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = router.routes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var route = _step.value; - - this.routes.push(route); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } - }, { - key: 'route', - value: function route(method, path) { - for (var _len = arguments.length, handlers = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { - handlers[_key - 2] = arguments[_key]; - } - - switch (method) { - case 'POST': - case 'GET': - case 'PUT': - case 'DELETE': - break; - default: - throw 'cannot route method: ' + method; - } - - var handler = handlers[0]; - - if (handlers.length > 1) { - var length = handlers.length; - handler = function handler(req) { - return handlers.reduce(function (promise, handler) { - return promise.then(function (result) { - return handler(req); - }); - }, Promise.resolve()); - }; - } - - this.routes.push({ - path: path, - method: method, - handler: handler - }); - } - }, { - key: 'match', - - - // Returns an object with: - // handler: the handler that should deal with this request - // params: any :-params that got parsed from the path - // Returns undefined if there is no match. - value: function match(method, path) { - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = this.routes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var route = _step2.value; - - if (route.method != method) { - continue; - } - // NOTE: we can only route the specific wildcards :className and - // :objectId, and in that order. - // This is pretty hacky but I don't want to rebuild the entire - // express route matcher. Maybe there's a way to reuse its logic. - var pattern = '^' + route.path + '$'; - - pattern = pattern.replace(':className', '(_?[A-Za-z][A-Za-z_0-9]*)'); - pattern = pattern.replace(':objectId', '([A-Za-z0-9]+)'); - var re = new RegExp(pattern); - var m = path.match(re); - if (!m) { - continue; - } - var params = {}; - if (m[1]) { - params.className = m[1]; - } - if (m[2]) { - params.objectId = m[2]; - } - - return { params: params, handler: route.handler }; - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - } - }, { - key: 'mountOnto', - - - // Mount the routes on this router onto an express app (or express router) - value: function mountOnto(expressApp) { - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = this.routes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var route = _step3.value; - - switch (route.method) { - case 'POST': - expressApp.post(route.path, makeExpressHandler(this.appId, route.handler)); - break; - case 'GET': - expressApp.get(route.path, makeExpressHandler(this.appId, route.handler)); - break; - case 'PUT': - expressApp.put(route.path, makeExpressHandler(this.appId, route.handler)); - break; - case 'DELETE': - expressApp.delete(route.path, makeExpressHandler(this.appId, route.handler)); - break; - default: - throw 'unexpected code branch'; - } - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - } - }, { - key: 'expressApp', - value: function expressApp() { - var expressApp = (0, _express2.default)(); - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = this.routes[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var route = _step4.value; - - switch (route.method) { - case 'POST': - expressApp.post(route.path, makeExpressHandler(this.appId, route.handler)); - break; - case 'GET': - expressApp.get(route.path, makeExpressHandler(this.appId, route.handler)); - break; - case 'PUT': - expressApp.put(route.path, makeExpressHandler(this.appId, route.handler)); - break; - case 'DELETE': - expressApp.delete(route.path, makeExpressHandler(this.appId, route.handler)); - break; - default: - throw 'unexpected code branch'; - } - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - return expressApp; - } - }]); - - return PromiseRouter; -}(); - -// A helper function to make an express handler out of a a promise -// handler. -// Express handlers should never throw; if a promise handler throws we -// just treat it like it resolved to an error. - - -exports.default = PromiseRouter; -function makeExpressHandler(appId, promiseHandler) { - var config = _cache2.default.get(appId); - return function (req, res, next) { - try { - (function () { - var url = maskSensitiveUrl(req); - var body = maskSensitiveBody(req); - var stringifiedBody = JSON.stringify(body, null, 2); - _logger2.default.verbose('REQUEST for [' + req.method + '] ' + url + ': ' + stringifiedBody, { - method: req.method, - url: url, - headers: req.headers, - body: body - }); - promiseHandler(req).then(function (result) { - if (!result.response && !result.location && !result.text) { - _logger2.default.error('the handler did not include a "response" or a "location" field'); - throw 'control should not get here'; - } - - var stringifiedResponse = JSON.stringify(result, null, 2); - _logger2.default.verbose('RESPONSE from [' + req.method + '] ' + url + ': ' + stringifiedResponse, { result: result }); - - var status = result.status || 200; - res.status(status); - - if (result.text) { - res.send(result.text); - return next(); - } - - if (result.location) { - res.set('Location', result.location); - // Override the default expressjs response - // as it double encodes %encoded chars in URL - if (!result.response) { - res.send('Found. Redirecting to ' + result.location); - return next(); - } - } - if (result.headers) { - Object.keys(result.headers).forEach(function (header) { - res.set(header, result.headers[header]); - }); - } - res.json(result.response); - next(); - }, function (e) { - _logger2.default.error('Error generating response. ' + (0, _util.inspect)(e), { error: e }); - next(e); - }); - })(); - } catch (e) { - _logger2.default.error('Error handling request: ' + (0, _util.inspect)(e), { error: e }); - next(e); - } - }; -} - -function maskSensitiveBody(req) { - var maskBody = Object.assign({}, req.body); - var shouldMaskBody = req.method === 'POST' && req.originalUrl.endsWith('/users') && !req.originalUrl.includes('classes') || req.method === 'PUT' && /users\/\w+$/.test(req.originalUrl) && !req.originalUrl.includes('classes') || req.originalUrl.includes('classes/_User'); - if (shouldMaskBody) { - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; - - try { - for (var _iterator5 = Object.keys(maskBody)[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - var key = _step5.value; - - if (key == 'password') { - maskBody[key] = '********'; - break; - } - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } - } - } - return maskBody; -} - -function maskSensitiveUrl(req) { - var maskUrl = req.originalUrl.toString(); - var shouldMaskUrl = req.method === 'GET' && req.originalUrl.includes('/login') && !req.originalUrl.includes('classes'); - if (shouldMaskUrl) { - var password = _url2.default.parse(req.originalUrl, true).query.password; - if (password) { - maskUrl = maskUrl.replace('password=' + password, 'password=********'); - } - } - return maskUrl; -} \ No newline at end of file diff --git a/lib/RestQuery.js b/lib/RestQuery.js deleted file mode 100644 index 634c4142c7..0000000000 --- a/lib/RestQuery.js +++ /dev/null @@ -1,892 +0,0 @@ -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _FilesController = require('./Controllers/FilesController'); - -var _FilesController2 = _interopRequireDefault(_FilesController); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// An object that encapsulates everything we need to run a 'find' -// operation, encoded in the REST API format. - -var SchemaController = require('./Controllers/SchemaController'); -var Parse = require('parse/node').Parse; - -// restOptions can include: -// skip -// limit -// order -// count -// include -// keys -// redirectClassNameForKey -function RestQuery(config, auth, className) { - var restWhere = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; - var restOptions = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; - var clientSDK = arguments[5]; - - - this.config = config; - this.auth = auth; - this.className = className; - this.restWhere = restWhere; - this.clientSDK = clientSDK; - this.response = null; - this.findOptions = {}; - if (!this.auth.isMaster) { - this.findOptions.acl = this.auth.user ? [this.auth.user.id] : null; - if (this.className == '_Session') { - if (!this.findOptions.acl) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'This session token is invalid.'); - } - this.restWhere = { - '$and': [this.restWhere, { - 'user': { - __type: 'Pointer', - className: '_User', - objectId: this.auth.user.id - } - }] - }; - } - } - - this.doCount = false; - - // The format for this.include is not the same as the format for the - // include option - it's the paths we should include, in order, - // stored as arrays, taking into account that we need to include foo - // before including foo.bar. Also it should dedupe. - // For example, passing an arg of include=foo.bar,foo.baz could lead to - // this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']] - this.include = []; - - for (var option in restOptions) { - switch (option) { - case 'keys': - this.keys = new Set(restOptions.keys.split(',')); - this.keys.add('objectId'); - this.keys.add('createdAt'); - this.keys.add('updatedAt'); - break; - case 'count': - this.doCount = true; - break; - case 'skip': - case 'limit': - this.findOptions[option] = restOptions[option]; - break; - case 'order': - var fields = restOptions.order.split(','); - var sortMap = {}; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = fields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var field = _step.value; - - if (field[0] == '-') { - sortMap[field.slice(1)] = -1; - } else { - sortMap[field] = 1; - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - this.findOptions.sort = sortMap; - break; - case 'include': - var paths = restOptions.include.split(','); - var pathSet = {}; - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = paths[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var path = _step2.value; - - // Add all prefixes with a .-split to pathSet - var parts = path.split('.'); - for (var len = 1; len <= parts.length; len++) { - pathSet[parts.slice(0, len).join('.')] = true; - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - - this.include = Object.keys(pathSet).sort(function (a, b) { - return a.length - b.length; - }).map(function (s) { - return s.split('.'); - }); - break; - case 'redirectClassNameForKey': - this.redirectKey = restOptions.redirectClassNameForKey; - this.redirectClassName = null; - break; - default: - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad option: ' + option); - } - } -} - -// A convenient method to perform all the steps of processing a query -// in order. -// Returns a promise for the response - an object with optional keys -// 'results' and 'count'. -// TODO: consolidate the replaceX functions -RestQuery.prototype.execute = function () { - var _this = this; - - return Promise.resolve().then(function () { - return _this.buildRestWhere(); - }).then(function () { - return _this.runFind(); - }).then(function () { - return _this.runCount(); - }).then(function () { - return _this.handleInclude(); - }).then(function () { - return _this.response; - }); -}; - -RestQuery.prototype.buildRestWhere = function () { - var _this2 = this; - - return Promise.resolve().then(function () { - return _this2.getUserAndRoleACL(); - }).then(function () { - return _this2.redirectClassNameForKey(); - }).then(function () { - return _this2.validateClientClassCreation(); - }).then(function () { - return _this2.replaceSelect(); - }).then(function () { - return _this2.replaceDontSelect(); - }).then(function () { - return _this2.replaceInQuery(); - }).then(function () { - return _this2.replaceNotInQuery(); - }); -}; - -// Uses the Auth object to get the list of roles, adds the user id -RestQuery.prototype.getUserAndRoleACL = function () { - var _this3 = this; - - if (this.auth.isMaster || !this.auth.user) { - return Promise.resolve(); - } - return this.auth.getUserRoles().then(function (roles) { - roles.push(_this3.auth.user.id); - _this3.findOptions.acl = roles; - return Promise.resolve(); - }); -}; - -// Changes the className if redirectClassNameForKey is set. -// Returns a promise. -RestQuery.prototype.redirectClassNameForKey = function () { - var _this4 = this; - - if (!this.redirectKey) { - return Promise.resolve(); - } - - // We need to change the class name based on the schema - return this.config.database.redirectClassNameForKey(this.className, this.redirectKey).then(function (newClassName) { - _this4.className = newClassName; - _this4.redirectClassName = newClassName; - }); -}; - -// Validates this operation against the allowClientClassCreation config. -RestQuery.prototype.validateClientClassCreation = function () { - var _this5 = this; - - if (this.config.allowClientClassCreation === false && !this.auth.isMaster && SchemaController.systemClasses.indexOf(this.className) === -1) { - return this.config.database.loadSchema().then(function (schemaController) { - return schemaController.hasClass(_this5.className); - }).then(function (hasClass) { - if (hasClass !== true) { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'This user is not allowed to access ' + 'non-existent class: ' + _this5.className); - } - }); - } else { - return Promise.resolve(); - } -}; - -function transformInQuery(inQueryObject, className, results) { - var values = []; - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = results[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var result = _step3.value; - - values.push({ - __type: 'Pointer', - className: className, - objectId: result.objectId - }); - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - - delete inQueryObject['$inQuery']; - if (Array.isArray(inQueryObject['$in'])) { - inQueryObject['$in'] = inQueryObject['$in'].concat(values); - } else { - inQueryObject['$in'] = values; - } -} - -// Replaces a $inQuery clause by running the subquery, if there is an -// $inQuery clause. -// The $inQuery clause turns into an $in with values that are just -// pointers to the objects returned in the subquery. -RestQuery.prototype.replaceInQuery = function () { - var _this6 = this; - - var inQueryObject = findObjectWithKey(this.restWhere, '$inQuery'); - if (!inQueryObject) { - return; - } - - // The inQuery value must have precisely two keys - where and className - var inQueryValue = inQueryObject['$inQuery']; - if (!inQueryValue.where || !inQueryValue.className) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $inQuery'); - } - - var additionalOptions = { - redirectClassNameForKey: inQueryValue.redirectClassNameForKey - }; - - var subquery = new RestQuery(this.config, this.auth, inQueryValue.className, inQueryValue.where, additionalOptions); - return subquery.execute().then(function (response) { - transformInQuery(inQueryObject, subquery.className, response.results); - // Recurse to repeat - return _this6.replaceInQuery(); - }); -}; - -function transformNotInQuery(notInQueryObject, className, results) { - var values = []; - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = results[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var result = _step4.value; - - values.push({ - __type: 'Pointer', - className: className, - objectId: result.objectId - }); - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - delete notInQueryObject['$notInQuery']; - if (Array.isArray(notInQueryObject['$nin'])) { - notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values); - } else { - notInQueryObject['$nin'] = values; - } -} - -// Replaces a $notInQuery clause by running the subquery, if there is an -// $notInQuery clause. -// The $notInQuery clause turns into a $nin with values that are just -// pointers to the objects returned in the subquery. -RestQuery.prototype.replaceNotInQuery = function () { - var _this7 = this; - - var notInQueryObject = findObjectWithKey(this.restWhere, '$notInQuery'); - if (!notInQueryObject) { - return; - } - - // The notInQuery value must have precisely two keys - where and className - var notInQueryValue = notInQueryObject['$notInQuery']; - if (!notInQueryValue.where || !notInQueryValue.className) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $notInQuery'); - } - - var additionalOptions = { - redirectClassNameForKey: notInQueryValue.redirectClassNameForKey - }; - - var subquery = new RestQuery(this.config, this.auth, notInQueryValue.className, notInQueryValue.where, additionalOptions); - return subquery.execute().then(function (response) { - transformNotInQuery(notInQueryObject, subquery.className, response.results); - // Recurse to repeat - return _this7.replaceNotInQuery(); - }); -}; - -var transformSelect = function transformSelect(selectObject, key, objects) { - var values = []; - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; - - try { - for (var _iterator5 = objects[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - var result = _step5.value; - - values.push(result[key]); - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } - } - - delete selectObject['$select']; - if (Array.isArray(selectObject['$in'])) { - selectObject['$in'] = selectObject['$in'].concat(values); - } else { - selectObject['$in'] = values; - } -}; - -// Replaces a $select clause by running the subquery, if there is a -// $select clause. -// The $select clause turns into an $in with values selected out of -// the subquery. -// Returns a possible-promise. -RestQuery.prototype.replaceSelect = function () { - var _this8 = this; - - var selectObject = findObjectWithKey(this.restWhere, '$select'); - if (!selectObject) { - return; - } - - // The select value must have precisely two keys - query and key - var selectValue = selectObject['$select']; - // iOS SDK don't send where if not set, let it pass - if (!selectValue.query || !selectValue.key || _typeof(selectValue.query) !== 'object' || !selectValue.query.className || Object.keys(selectValue).length !== 2) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $select'); - } - - var additionalOptions = { - redirectClassNameForKey: selectValue.query.redirectClassNameForKey - }; - - var subquery = new RestQuery(this.config, this.auth, selectValue.query.className, selectValue.query.where, additionalOptions); - return subquery.execute().then(function (response) { - transformSelect(selectObject, selectValue.key, response.results); - // Keep replacing $select clauses - return _this8.replaceSelect(); - }); -}; - -var transformDontSelect = function transformDontSelect(dontSelectObject, key, objects) { - var values = []; - var _iteratorNormalCompletion6 = true; - var _didIteratorError6 = false; - var _iteratorError6 = undefined; - - try { - for (var _iterator6 = objects[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { - var result = _step6.value; - - values.push(result[key]); - } - } catch (err) { - _didIteratorError6 = true; - _iteratorError6 = err; - } finally { - try { - if (!_iteratorNormalCompletion6 && _iterator6.return) { - _iterator6.return(); - } - } finally { - if (_didIteratorError6) { - throw _iteratorError6; - } - } - } - - delete dontSelectObject['$dontSelect']; - if (Array.isArray(dontSelectObject['$nin'])) { - dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values); - } else { - dontSelectObject['$nin'] = values; - } -}; - -// Replaces a $dontSelect clause by running the subquery, if there is a -// $dontSelect clause. -// The $dontSelect clause turns into an $nin with values selected out of -// the subquery. -// Returns a possible-promise. -RestQuery.prototype.replaceDontSelect = function () { - var _this9 = this; - - var dontSelectObject = findObjectWithKey(this.restWhere, '$dontSelect'); - if (!dontSelectObject) { - return; - } - - // The dontSelect value must have precisely two keys - query and key - var dontSelectValue = dontSelectObject['$dontSelect']; - if (!dontSelectValue.query || !dontSelectValue.key || _typeof(dontSelectValue.query) !== 'object' || !dontSelectValue.query.className || Object.keys(dontSelectValue).length !== 2) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $dontSelect'); - } - var additionalOptions = { - redirectClassNameForKey: dontSelectValue.query.redirectClassNameForKey - }; - - var subquery = new RestQuery(this.config, this.auth, dontSelectValue.query.className, dontSelectValue.query.where, additionalOptions); - return subquery.execute().then(function (response) { - transformDontSelect(dontSelectObject, dontSelectValue.key, response.results); - // Keep replacing $dontSelect clauses - return _this9.replaceDontSelect(); - }); -}; - -// Returns a promise for whether it was successful. -// Populates this.response with an object that only has 'results'. -RestQuery.prototype.runFind = function () { - var _this10 = this; - - if (this.findOptions.limit === 0) { - this.response = { results: [] }; - return Promise.resolve(); - } - return this.config.database.find(this.className, this.restWhere, this.findOptions).then(function (results) { - if (_this10.className === '_User') { - var _iteratorNormalCompletion7 = true; - var _didIteratorError7 = false; - var _iteratorError7 = undefined; - - try { - for (var _iterator7 = results[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { - var result = _step7.value; - - delete result.password; - - if (result.authData) { - Object.keys(result.authData).forEach(function (provider) { - if (result.authData[provider] === null) { - delete result.authData[provider]; - } - }); - if (Object.keys(result.authData).length == 0) { - delete result.authData; - } - } - } - } catch (err) { - _didIteratorError7 = true; - _iteratorError7 = err; - } finally { - try { - if (!_iteratorNormalCompletion7 && _iterator7.return) { - _iterator7.return(); - } - } finally { - if (_didIteratorError7) { - throw _iteratorError7; - } - } - } - } - - _this10.config.filesController.expandFilesInObject(_this10.config, results); - - if (_this10.keys) { - var keySet = _this10.keys; - results = results.map(function (object) { - var newObject = {}; - for (var key in object) { - if (keySet.has(key)) { - newObject[key] = object[key]; - } - } - return newObject; - }); - } - - if (_this10.redirectClassName) { - var _iteratorNormalCompletion8 = true; - var _didIteratorError8 = false; - var _iteratorError8 = undefined; - - try { - for (var _iterator8 = results[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { - var r = _step8.value; - - r.className = _this10.redirectClassName; - } - } catch (err) { - _didIteratorError8 = true; - _iteratorError8 = err; - } finally { - try { - if (!_iteratorNormalCompletion8 && _iterator8.return) { - _iterator8.return(); - } - } finally { - if (_didIteratorError8) { - throw _iteratorError8; - } - } - } - } - _this10.response = { results: results }; - }); -}; - -// Returns a promise for whether it was successful. -// Populates this.response.count with the count -RestQuery.prototype.runCount = function () { - var _this11 = this; - - if (!this.doCount) { - return; - } - this.findOptions.count = true; - delete this.findOptions.skip; - delete this.findOptions.limit; - return this.config.database.find(this.className, this.restWhere, this.findOptions).then(function (c) { - _this11.response.count = c; - }); -}; - -// Augments this.response with data at the paths provided in this.include. -RestQuery.prototype.handleInclude = function () { - var _this12 = this; - - if (this.include.length == 0) { - return; - } - - var pathResponse = includePath(this.config, this.auth, this.response, this.include[0]); - if (pathResponse.then) { - return pathResponse.then(function (newResponse) { - _this12.response = newResponse; - _this12.include = _this12.include.slice(1); - return _this12.handleInclude(); - }); - } else if (this.include.length > 0) { - this.include = this.include.slice(1); - return this.handleInclude(); - } - - return pathResponse; -}; - -// Adds included values to the response. -// Path is a list of field names. -// Returns a promise for an augmented response. -function includePath(config, auth, response, path) { - var pointers = findPointers(response.results, path); - if (pointers.length == 0) { - return response; - } - var pointersHash = {}; - var objectIds = {}; - var _iteratorNormalCompletion9 = true; - var _didIteratorError9 = false; - var _iteratorError9 = undefined; - - try { - for (var _iterator9 = pointers[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { - var pointer = _step9.value; - - var className = pointer.className; - // only include the good pointers - if (className) { - pointersHash[className] = pointersHash[className] || []; - pointersHash[className].push(pointer.objectId); - } - } - } catch (err) { - _didIteratorError9 = true; - _iteratorError9 = err; - } finally { - try { - if (!_iteratorNormalCompletion9 && _iterator9.return) { - _iterator9.return(); - } - } finally { - if (_didIteratorError9) { - throw _iteratorError9; - } - } - } - - var queryPromises = Object.keys(pointersHash).map(function (className) { - var where = { 'objectId': { '$in': pointersHash[className] } }; - var query = new RestQuery(config, auth, className, where); - return query.execute().then(function (results) { - results.className = className; - return Promise.resolve(results); - }); - }); - - // Get the objects for all these object ids - return Promise.all(queryPromises).then(function (responses) { - var replace = responses.reduce(function (replace, includeResponse) { - var _iteratorNormalCompletion10 = true; - var _didIteratorError10 = false; - var _iteratorError10 = undefined; - - try { - for (var _iterator10 = includeResponse.results[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { - var obj = _step10.value; - - obj.__type = 'Object'; - obj.className = includeResponse.className; - - if (obj.className == "_User" && !auth.isMaster) { - delete obj.sessionToken; - delete obj.authData; - } - replace[obj.objectId] = obj; - } - } catch (err) { - _didIteratorError10 = true; - _iteratorError10 = err; - } finally { - try { - if (!_iteratorNormalCompletion10 && _iterator10.return) { - _iterator10.return(); - } - } finally { - if (_didIteratorError10) { - throw _iteratorError10; - } - } - } - - return replace; - }, {}); - - var resp = { - results: replacePointers(response.results, path, replace) - }; - if (response.count) { - resp.count = response.count; - } - return resp; - }); -} - -// Object may be a list of REST-format object to find pointers in, or -// it may be a single object. -// If the path yields things that aren't pointers, this throws an error. -// Path is a list of fields to search into. -// Returns a list of pointers in REST format. -function findPointers(object, path) { - if (object instanceof Array) { - var answer = []; - var _iteratorNormalCompletion11 = true; - var _didIteratorError11 = false; - var _iteratorError11 = undefined; - - try { - for (var _iterator11 = object[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { - var x = _step11.value; - - answer = answer.concat(findPointers(x, path)); - } - } catch (err) { - _didIteratorError11 = true; - _iteratorError11 = err; - } finally { - try { - if (!_iteratorNormalCompletion11 && _iterator11.return) { - _iterator11.return(); - } - } finally { - if (_didIteratorError11) { - throw _iteratorError11; - } - } - } - - return answer; - } - - if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object') { - return []; - } - - if (path.length == 0) { - if (object.__type == 'Pointer') { - return [object]; - } - return []; - } - - var subobject = object[path[0]]; - if (!subobject) { - return []; - } - return findPointers(subobject, path.slice(1)); -} - -// Object may be a list of REST-format objects to replace pointers -// in, or it may be a single object. -// Path is a list of fields to search into. -// replace is a map from object id -> object. -// Returns something analogous to object, but with the appropriate -// pointers inflated. -function replacePointers(object, path, replace) { - if (object instanceof Array) { - return object.map(function (obj) { - return replacePointers(obj, path, replace); - }).filter(function (obj) { - return obj != null && obj != undefined; - }); - } - - if ((typeof object === 'undefined' ? 'undefined' : _typeof(object)) !== 'object') { - return object; - } - - if (path.length === 0) { - if (object.__type === 'Pointer') { - return replace[object.objectId]; - } - return object; - } - - var subobject = object[path[0]]; - if (!subobject) { - return object; - } - var newsub = replacePointers(subobject, path.slice(1), replace); - var answer = {}; - for (var key in object) { - if (key == path[0]) { - answer[key] = newsub; - } else { - answer[key] = object[key]; - } - } - return answer; -} - -// Finds a subobject that has the given key, if there is one. -// Returns undefined otherwise. -function findObjectWithKey(root, key) { - if ((typeof root === 'undefined' ? 'undefined' : _typeof(root)) !== 'object') { - return; - } - if (root instanceof Array) { - var _iteratorNormalCompletion12 = true; - var _didIteratorError12 = false; - var _iteratorError12 = undefined; - - try { - for (var _iterator12 = root[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { - var item = _step12.value; - - var answer = findObjectWithKey(item, key); - if (answer) { - return answer; - } - } - } catch (err) { - _didIteratorError12 = true; - _iteratorError12 = err; - } finally { - try { - if (!_iteratorNormalCompletion12 && _iterator12.return) { - _iterator12.return(); - } - } finally { - if (_didIteratorError12) { - throw _iteratorError12; - } - } - } - } - if (root && root[key]) { - return root; - } - for (var subkey in root) { - var answer = findObjectWithKey(root[subkey], key); - if (answer) { - return answer; - } - } -} - -module.exports = RestQuery; \ No newline at end of file diff --git a/lib/RestWrite.js b/lib/RestWrite.js deleted file mode 100644 index 034ba5c9a0..0000000000 --- a/lib/RestWrite.js +++ /dev/null @@ -1,944 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _RestQuery = require('./RestQuery'); - -var _RestQuery2 = _interopRequireDefault(_RestQuery); - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// A RestWrite encapsulates everything we need to run an operation -// that writes to the database. -// This could be either a "create" or an "update". - -var SchemaController = require('./Controllers/SchemaController'); -var deepcopy = require('deepcopy'); - -var Auth = require('./Auth'); -var Config = require('./Config'); -var cryptoUtils = require('./cryptoUtils'); -var passwordCrypto = require('./password'); -var Parse = require('parse/node'); -var triggers = require('./triggers'); -var ClientSDK = require('./ClientSDK'); - - -// query and data are both provided in REST API format. So data -// types are encoded by plain old objects. -// If query is null, this is a "create" and the data in data should be -// created. -// Otherwise this is an "update" - the object matching the query -// should get updated with data. -// RestWrite will handle objectId, createdAt, and updatedAt for -// everything. It also knows to use triggers and special modifications -// for the _User class. -function RestWrite(config, auth, className, query, data, originalData, clientSDK) { - this.config = config; - this.auth = auth; - this.className = className; - this.clientSDK = clientSDK; - this.storage = {}; - this.runOptions = {}; - if (!query && data.objectId) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.'); - } - - // When the operation is complete, this.response may have several - // fields. - // response: the actual data to be returned - // status: the http status code. if not present, treated like a 200 - // location: the location header. if not present, no location header - this.response = null; - - // Processing this operation may mutate our data, so we operate on a - // copy - this.query = deepcopy(query); - this.data = deepcopy(data); - // We never change originalData, so we do not need a deep copy - this.originalData = originalData; - - // The timestamp we'll use for this whole operation - this.updatedAt = Parse._encode(new Date()).iso; -} - -// A convenient method to perform all the steps of processing the -// write, in order. -// Returns a promise for a {response, status, location} object. -// status and location are optional. -RestWrite.prototype.execute = function () { - var _this = this; - - return Promise.resolve().then(function () { - return _this.getUserAndRoleACL(); - }).then(function () { - return _this.validateClientClassCreation(); - }).then(function () { - return _this.validateSchema(); - }).then(function () { - return _this.handleInstallation(); - }).then(function () { - return _this.handleSession(); - }).then(function () { - return _this.validateAuthData(); - }).then(function () { - return _this.runBeforeTrigger(); - }).then(function () { - return _this.setRequiredFieldsIfNeeded(); - }).then(function () { - return _this.transformUser(); - }).then(function () { - return _this.expandFilesForExistingObjects(); - }).then(function () { - return _this.runDatabaseOperation(); - }).then(function () { - return _this.createSessionTokenIfNeeded(); - }).then(function () { - return _this.handleFollowup(); - }).then(function () { - return _this.runAfterTrigger(); - }).then(function () { - return _this.cleanUserAuthData(); - }).then(function () { - return _this.response; - }); -}; - -// Uses the Auth object to get the list of roles, adds the user id -RestWrite.prototype.getUserAndRoleACL = function () { - var _this2 = this; - - if (this.auth.isMaster) { - return Promise.resolve(); - } - - this.runOptions.acl = ['*']; - - if (this.auth.user) { - return this.auth.getUserRoles().then(function (roles) { - roles.push(_this2.auth.user.id); - _this2.runOptions.acl = _this2.runOptions.acl.concat(roles); - return; - }); - } else { - return Promise.resolve(); - } -}; - -// Validates this operation against the allowClientClassCreation config. -RestWrite.prototype.validateClientClassCreation = function () { - var _this3 = this; - - if (this.config.allowClientClassCreation === false && !this.auth.isMaster && SchemaController.systemClasses.indexOf(this.className) === -1) { - return this.config.database.loadSchema().then(function (schemaController) { - return schemaController.hasClass(_this3.className); - }).then(function (hasClass) { - if (hasClass !== true) { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'This user is not allowed to access ' + 'non-existent class: ' + _this3.className); - } - }); - } else { - return Promise.resolve(); - } -}; - -// Validates this operation against the schema. -RestWrite.prototype.validateSchema = function () { - return this.config.database.validateObject(this.className, this.data, this.query, this.runOptions); -}; - -// Runs any beforeSave triggers against this operation. -// Any change leads to our data being mutated. -RestWrite.prototype.runBeforeTrigger = function () { - var _this4 = this; - - if (this.response) { - return; - } - - // Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class. - if (!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)) { - return Promise.resolve(); - } - - // Cloud code gets a bit of extra data for its objects - var extraData = { className: this.className }; - if (this.query && this.query.objectId) { - extraData.objectId = this.query.objectId; - } - - var originalObject = null; - var updatedObject = triggers.inflate(extraData, this.originalData); - if (this.query && this.query.objectId) { - // This is an update for existing object. - originalObject = triggers.inflate(extraData, this.originalData); - } - updatedObject.set(this.sanitizedData()); - - return Promise.resolve().then(function () { - return triggers.maybeRunTrigger(triggers.Types.beforeSave, _this4.auth, updatedObject, originalObject, _this4.config); - }).then(function (response) { - if (response && response.object) { - if (!_lodash2.default.isEqual(_this4.data, response.object)) { - _this4.storage.changedByTrigger = true; - } - _this4.data = response.object; - // We should delete the objectId for an update write - if (_this4.query && _this4.query.objectId) { - delete _this4.data.objectId; - } - return _this4.validateSchema(); - } - }); -}; - -RestWrite.prototype.setRequiredFieldsIfNeeded = function () { - if (this.data) { - // Add default fields - this.data.updatedAt = this.updatedAt; - if (!this.query) { - this.data.createdAt = this.updatedAt; - - // Only assign new objectId if we are creating new object - if (!this.data.objectId) { - this.data.objectId = cryptoUtils.newObjectId(); - } - } - } - return Promise.resolve(); -}; - -// Transforms auth data for a user object. -// Does nothing if this isn't a user object. -// Returns a promise for when we're done if it can't finish this tick. -RestWrite.prototype.validateAuthData = function () { - if (this.className !== '_User') { - return; - } - - if (!this.query && !this.data.authData) { - if (typeof this.data.username !== 'string') { - throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'bad or missing username'); - } - if (typeof this.data.password !== 'string') { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required'); - } - } - - if (!this.data.authData || !Object.keys(this.data.authData).length) { - return; - } - - var authData = this.data.authData; - var providers = Object.keys(authData); - if (providers.length > 0) { - var canHandleAuthData = providers.reduce(function (canHandle, provider) { - var providerAuthData = authData[provider]; - var hasToken = providerAuthData && providerAuthData.id; - return canHandle && (hasToken || providerAuthData == null); - }, true); - if (canHandleAuthData) { - return this.handleAuthData(authData); - } - } - throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.'); -}; - -RestWrite.prototype.handleAuthDataValidation = function (authData) { - var _this5 = this; - - var validations = Object.keys(authData).map(function (provider) { - if (authData[provider] === null) { - return Promise.resolve(); - } - var validateAuthData = _this5.config.authDataManager.getValidatorForProvider(provider); - if (!validateAuthData) { - throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.'); - }; - return validateAuthData(authData[provider]); - }); - return Promise.all(validations); -}; - -RestWrite.prototype.findUsersWithAuthData = function (authData) { - var providers = Object.keys(authData); - var query = providers.reduce(function (memo, provider) { - if (!authData[provider]) { - return memo; - } - var queryKey = 'authData.' + provider + '.id'; - var query = {}; - query[queryKey] = authData[provider].id; - memo.push(query); - return memo; - }, []).filter(function (q) { - return (typeof q === 'undefined' ? 'undefined' : _typeof(q)) !== undefined; - }); - - var findPromise = Promise.resolve([]); - if (query.length > 0) { - findPromise = this.config.database.find(this.className, { '$or': query }, {}); - } - - return findPromise; -}; - -RestWrite.prototype.handleAuthData = function (authData) { - var _this6 = this; - - var results = void 0; - return this.handleAuthDataValidation(authData).then(function () { - return _this6.findUsersWithAuthData(authData); - }).then(function (r) { - results = r; - if (results.length > 1) { - // More than 1 user with the passed id's - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); - } - - _this6.storage['authProvider'] = Object.keys(authData).join(','); - - if (results.length > 0) { - if (!_this6.query) { - var _ret = function () { - // Login with auth data - delete results[0].password; - var userResult = results[0]; - - // need to set the objectId first otherwise location has trailing undefined - _this6.data.objectId = userResult.objectId; - - // Determine if authData was updated - var mutatedAuthData = {}; - Object.keys(authData).forEach(function (provider) { - var providerData = authData[provider]; - var userAuthData = userResult.authData[provider]; - if (!_lodash2.default.isEqual(providerData, userAuthData)) { - mutatedAuthData[provider] = providerData; - } - }); - - _this6.response = { - response: userResult, - location: _this6.location() - }; - - // We have authData that is updated on login - // that can happen when token are refreshed, - // We should update the token and let the user in - if (Object.keys(mutatedAuthData).length > 0) { - // Assign the new authData in the response - Object.keys(mutatedAuthData).forEach(function (provider) { - _this6.response.response.authData[provider] = mutatedAuthData[provider]; - }); - // Run the DB update directly, as 'master' - // Just update the authData part - return { - v: _this6.config.database.update(_this6.className, { objectId: _this6.data.objectId }, { authData: mutatedAuthData }, {}) - }; - } - return { - v: void 0 - }; - }(); - - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; - } else if (_this6.query && _this6.query.objectId) { - // Trying to update auth data but users - // are different - if (results[0].objectId !== _this6.query.objectId) { - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); - } - } - } - return; - }); -}; - -// The non-third-party parts of User transformation -RestWrite.prototype.transformUser = function () { - var _this7 = this; - - if (this.className !== '_User') { - return; - } - - var promise = Promise.resolve(); - - if (this.query) { - // If we're updating a _User object, we need to clear out the cache for that user. Find all their - // session tokens, and remove them from the cache. - promise = new _RestQuery2.default(this.config, Auth.master(this.config), '_Session', { user: { - __type: "Pointer", - className: "_User", - objectId: this.objectId() - } }).execute().then(function (results) { - results.results.forEach(function (session) { - return _this7.config.cacheController.user.del(session.sessionToken); - }); - }); - } - - return promise.then(function () { - // Transform the password - if (!_this7.data.password) { - return; - } - if (_this7.query && !_this7.auth.isMaster) { - _this7.storage['clearSessions'] = true; - _this7.storage['generateNewSession'] = true; - } - return passwordCrypto.hash(_this7.data.password).then(function (hashedPassword) { - _this7.data._hashed_password = hashedPassword; - delete _this7.data.password; - }); - }).then(function () { - // Check for username uniqueness - if (!_this7.data.username) { - if (!_this7.query) { - _this7.data.username = cryptoUtils.randomString(25); - _this7.responseShouldHaveUsername = true; - } - return; - } - // We need to a find to check for duplicate username in case they are missing the unique index on usernames - // TODO: Check if there is a unique index, and if so, skip this query. - return _this7.config.database.find(_this7.className, { username: _this7.data.username, objectId: { '$ne': _this7.objectId() } }, { limit: 1 }).then(function (results) { - if (results.length > 0) { - throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.'); - } - return; - }); - }).then(function () { - if (!_this7.data.email || _this7.data.email.__op === 'Delete') { - return; - } - // Validate basic email address format - if (!_this7.data.email.match(/^.+@.+$/)) { - throw new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.'); - } - // Same problem for email as above for username - return _this7.config.database.find(_this7.className, { email: _this7.data.email, objectId: { '$ne': _this7.objectId() } }, { limit: 1 }).then(function (results) { - if (results.length > 0) { - throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.'); - } - // We updated the email, send a new validation - _this7.storage['sendVerificationEmail'] = true; - _this7.config.userController.setEmailVerifyToken(_this7.data); - }); - }); -}; - -RestWrite.prototype.createSessionTokenIfNeeded = function () { - if (this.className !== '_User') { - return; - } - if (this.query) { - return; - } - return this.createSessionToken(); -}; - -RestWrite.prototype.createSessionToken = function () { - var token = 'r:' + cryptoUtils.newToken(); - - var expiresAt = this.config.generateSessionExpiresAt(); - var sessionData = { - sessionToken: token, - user: { - __type: 'Pointer', - className: '_User', - objectId: this.objectId() - }, - createdWith: { - 'action': 'signup', - 'authProvider': this.storage['authProvider'] || 'password' - }, - restricted: false, - installationId: this.auth.installationId, - expiresAt: Parse._encode(expiresAt) - }; - if (this.response && this.response.response) { - this.response.response.sessionToken = token; - } - var create = new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData); - return create.execute(); -}; - -// Handles any followup logic -RestWrite.prototype.handleFollowup = function () { - if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) { - var sessionQuery = { - user: { - __type: 'Pointer', - className: '_User', - objectId: this.objectId() - } - }; - delete this.storage['clearSessions']; - return this.config.database.destroy('_Session', sessionQuery).then(this.handleFollowup.bind(this)); - } - - if (this.storage && this.storage['generateNewSession']) { - delete this.storage['generateNewSession']; - return this.createSessionToken().then(this.handleFollowup.bind(this)); - } - - if (this.storage && this.storage['sendVerificationEmail']) { - delete this.storage['sendVerificationEmail']; - // Fire and forget! - this.config.userController.sendVerificationEmail(this.data); - return this.handleFollowup.bind(this); - } -}; - -// Handles the _Session class specialness. -// Does nothing if this isn't an installation object. -RestWrite.prototype.handleSession = function () { - var _this8 = this; - - if (this.response || this.className !== '_Session') { - return; - } - - if (!this.auth.user && !this.auth.isMaster) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); - } - - // TODO: Verify proper error to throw - if (this.data.ACL) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot set ' + 'ACL on a Session.'); - } - - if (!this.query && !this.auth.isMaster) { - var token = 'r:' + cryptoUtils.newToken(); - var expiresAt = this.config.generateSessionExpiresAt(); - var sessionData = { - sessionToken: token, - user: { - __type: 'Pointer', - className: '_User', - objectId: this.auth.user.id - }, - createdWith: { - 'action': 'create' - }, - restricted: true, - expiresAt: Parse._encode(expiresAt) - }; - for (var key in this.data) { - if (key == 'objectId') { - continue; - } - sessionData[key] = this.data[key]; - } - var create = new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData); - return create.execute().then(function (results) { - if (!results.response) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Error creating session.'); - } - sessionData['objectId'] = results.response['objectId']; - _this8.response = { - status: 201, - location: results.location, - response: sessionData - }; - }); - } -}; - -// Handles the _Installation class specialness. -// Does nothing if this isn't an installation object. -// If an installation is found, this can mutate this.query and turn a create -// into an update. -// Returns a promise for when we're done if it can't finish this tick. -RestWrite.prototype.handleInstallation = function () { - var _this9 = this; - - if (this.response || this.className !== '_Installation') { - return; - } - - if (!this.query && !this.data.deviceToken && !this.data.installationId) { - throw new Parse.Error(135, 'at least one ID field (deviceToken, installationId) ' + 'must be specified in this operation'); - } - - if (!this.query && !this.data.deviceType) { - throw new Parse.Error(135, 'deviceType must be specified in this operation'); - } - - // If the device token is 64 characters long, we assume it is for iOS - // and lowercase it. - if (this.data.deviceToken && this.data.deviceToken.length == 64) { - this.data.deviceToken = this.data.deviceToken.toLowerCase(); - } - - // TODO: We may need installationId from headers, plumb through Auth? - // per installation_handler.go - - // We lowercase the installationId if present - if (this.data.installationId) { - this.data.installationId = this.data.installationId.toLowerCase(); - } - - var promise = Promise.resolve(); - - var idMatch; // Will be a match on either objectId or installationId - var objectIdMatch; - var installationIdMatch; - var deviceTokenMatches = []; - - // Instead of issuing 3 reads, let's do it with one OR. - var orQueries = []; - if (this.query && this.query.objectId) { - orQueries.push({ - objectId: this.query.objectId - }); - } - if (this.data.installationId) { - orQueries.push({ - 'installationId': this.data.installationId - }); - } - if (this.data.deviceToken) { - orQueries.push({ 'deviceToken': this.data.deviceToken }); - } - - if (orQueries.length == 0) { - return; - } - - promise = promise.then(function () { - return _this9.config.database.find('_Installation', { - '$or': orQueries - }, {}); - }).then(function (results) { - results.forEach(function (result) { - if (_this9.query && _this9.query.objectId && result.objectId == _this9.query.objectId) { - objectIdMatch = result; - } - if (result.installationId == _this9.data.installationId) { - installationIdMatch = result; - } - if (result.deviceToken == _this9.data.deviceToken) { - deviceTokenMatches.push(result); - } - }); - - // Sanity checks when running a query - if (_this9.query && _this9.query.objectId) { - if (!objectIdMatch) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for update.'); - } - if (_this9.data.installationId && objectIdMatch.installationId && _this9.data.installationId !== objectIdMatch.installationId) { - throw new Parse.Error(136, 'installationId may not be changed in this ' + 'operation'); - } - if (_this9.data.deviceToken && objectIdMatch.deviceToken && _this9.data.deviceToken !== objectIdMatch.deviceToken && !_this9.data.installationId && !objectIdMatch.installationId) { - throw new Parse.Error(136, 'deviceToken may not be changed in this ' + 'operation'); - } - if (_this9.data.deviceType && _this9.data.deviceType && _this9.data.deviceType !== objectIdMatch.deviceType) { - throw new Parse.Error(136, 'deviceType may not be changed in this ' + 'operation'); - } - } - - if (_this9.query && _this9.query.objectId && objectIdMatch) { - idMatch = objectIdMatch; - } - - if (_this9.data.installationId && installationIdMatch) { - idMatch = installationIdMatch; - } - }).then(function () { - if (!idMatch) { - if (!deviceTokenMatches.length) { - return; - } else if (deviceTokenMatches.length == 1 && (!deviceTokenMatches[0]['installationId'] || !_this9.data.installationId)) { - // Single match on device token but none on installationId, and either - // the passed object or the match is missing an installationId, so we - // can just return the match. - return deviceTokenMatches[0]['objectId']; - } else if (!_this9.data.installationId) { - throw new Parse.Error(132, 'Must specify installationId when deviceToken ' + 'matches multiple Installation objects'); - } else { - // Multiple device token matches and we specified an installation ID, - // or a single match where both the passed and matching objects have - // an installation ID. Try cleaning out old installations that match - // the deviceToken, and return nil to signal that a new object should - // be created. - var delQuery = { - 'deviceToken': _this9.data.deviceToken, - 'installationId': { - '$ne': _this9.data.installationId - } - }; - if (_this9.data.appIdentifier) { - delQuery['appIdentifier'] = _this9.data.appIdentifier; - } - _this9.config.database.destroy('_Installation', delQuery); - return; - } - } else { - if (deviceTokenMatches.length == 1 && !deviceTokenMatches[0]['installationId']) { - // Exactly one device token match and it doesn't have an installation - // ID. This is the one case where we want to merge with the existing - // object. - var delQuery = { objectId: idMatch.objectId }; - return _this9.config.database.destroy('_Installation', delQuery).then(function () { - return deviceTokenMatches[0]['objectId']; - }); - } else { - if (_this9.data.deviceToken && idMatch.deviceToken != _this9.data.deviceToken) { - // We're setting the device token on an existing installation, so - // we should try cleaning out old installations that match this - // device token. - var delQuery = { - 'deviceToken': _this9.data.deviceToken - }; - // We have a unique install Id, use that to preserve - // the interesting installation - if (_this9.data.installationId) { - delQuery['installationId'] = { - '$ne': _this9.data.installationId - }; - } else if (idMatch.objectId && _this9.data.objectId && idMatch.objectId == _this9.data.objectId) { - // we passed an objectId, preserve that instalation - delQuery['objectId'] = { - '$ne': idMatch.objectId - }; - } else { - // What to do here? can't really clean up everything... - return idMatch.objectId; - } - if (_this9.data.appIdentifier) { - delQuery['appIdentifier'] = _this9.data.appIdentifier; - } - _this9.config.database.destroy('_Installation', delQuery); - } - // In non-merge scenarios, just return the installation match id - return idMatch.objectId; - } - } - }).then(function (objId) { - if (objId) { - _this9.query = { objectId: objId }; - delete _this9.data.objectId; - delete _this9.data.createdAt; - } - // TODO: Validate ops (add/remove on channels, $inc on badge, etc.) - }); - return promise; -}; - -// If we short-circuted the object response - then we need to make sure we expand all the files, -// since this might not have a query, meaning it won't return the full result back. -// TODO: (nlutsenko) This should die when we move to per-class based controllers on _Session/_User -RestWrite.prototype.expandFilesForExistingObjects = function () { - // Check whether we have a short-circuited response - only then run expansion. - if (this.response && this.response.response) { - this.config.filesController.expandFilesInObject(this.config, this.response.response); - } -}; - -RestWrite.prototype.runDatabaseOperation = function () { - var _this10 = this; - - if (this.response) { - return; - } - - if (this.className === '_Role') { - this.config.cacheController.role.clear(); - } - - if (this.className === '_User' && this.query && !this.auth.couldUpdateUserId(this.query.objectId)) { - throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Cannot modify user ' + this.query.objectId + '.'); - } - - if (this.className === '_Product' && this.data.download) { - this.data.downloadName = this.data.download.name; - } - - // TODO: Add better detection for ACL, ensuring a user can't be locked from - // their own user record. - if (this.data.ACL && this.data.ACL['*unresolved']) { - throw new Parse.Error(Parse.Error.INVALID_ACL, 'Invalid ACL.'); - } - - if (this.query) { - // Force the user to not lockout - // Matched with parse.com - if (this.className === '_User' && this.data.ACL) { - this.data.ACL[this.query.objectId] = { read: true, write: true }; - } - // Run an update - return this.config.database.update(this.className, this.query, this.data, this.runOptions).then(function (response) { - response.updatedAt = _this10.updatedAt; - if (_this10.storage.changedByTrigger) { - _this10.updateResponseWithData(response, _this10.data); - } - _this10.response = { response: response }; - }); - } else { - // Set the default ACL for the new _User - if (this.className === '_User') { - var ACL = this.data.ACL; - // default public r/w ACL - if (!ACL) { - ACL = {}; - ACL['*'] = { read: true, write: false }; - } - // make sure the user is not locked down - ACL[this.data.objectId] = { read: true, write: true }; - this.data.ACL = ACL; - } - - // Run a create - return this.config.database.create(this.className, this.data, this.runOptions).catch(function (error) { - if (_this10.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) { - throw error; - } - // If this was a failed user creation due to username or email already taken, we need to - // check whether it was username or email and return the appropriate error. - - // TODO: See if we can later do this without additional queries by using named indexes. - return _this10.config.database.find(_this10.className, { username: _this10.data.username, objectId: { '$ne': _this10.objectId() } }, { limit: 1 }).then(function (results) { - if (results.length > 0) { - throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.'); - } - return _this10.config.database.find(_this10.className, { email: _this10.data.email, objectId: { '$ne': _this10.objectId() } }, { limit: 1 }); - }).then(function (results) { - if (results.length > 0) { - throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.'); - } - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided'); - }); - }).then(function (response) { - response.objectId = _this10.data.objectId; - response.createdAt = _this10.data.createdAt; - - if (_this10.responseShouldHaveUsername) { - response.username = _this10.data.username; - } - if (_this10.storage.changedByTrigger) { - _this10.updateResponseWithData(response, _this10.data); - } - _this10.response = { - status: 201, - response: response, - location: _this10.location() - }; - }); - } -}; - -// Returns nothing - doesn't wait for the trigger. -RestWrite.prototype.runAfterTrigger = function () { - if (!this.response || !this.response.response) { - return; - } - - // Avoid doing any setup for triggers if there is no 'afterSave' trigger for this class. - var hasAfterSaveHook = triggers.triggerExists(this.className, triggers.Types.afterSave, this.config.applicationId); - var hasLiveQuery = this.config.liveQueryController.hasLiveQuery(this.className); - if (!hasAfterSaveHook && !hasLiveQuery) { - return Promise.resolve(); - } - - var extraData = { className: this.className }; - if (this.query && this.query.objectId) { - extraData.objectId = this.query.objectId; - } - - // Build the original object, we only do this for a update write. - var originalObject = void 0; - if (this.query && this.query.objectId) { - originalObject = triggers.inflate(extraData, this.originalData); - } - - // Build the inflated object, different from beforeSave, originalData is not empty - // since developers can change data in the beforeSave. - var updatedObject = triggers.inflate(extraData, this.originalData); - updatedObject.set(this.sanitizedData()); - updatedObject._handleSaveResponse(this.response.response, this.response.status || 200); - - // Notifiy LiveQueryServer if possible - this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject); - - // Run afterSave trigger - triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config); -}; - -// A helper to figure out what location this operation happens at. -RestWrite.prototype.location = function () { - var middle = this.className === '_User' ? '/users/' : '/classes/' + this.className + '/'; - return this.config.mount + middle + this.data.objectId; -}; - -// A helper to get the object id for this operation. -// Because it could be either on the query or on the data -RestWrite.prototype.objectId = function () { - return this.data.objectId || this.query.objectId; -}; - -// Returns a copy of the data and delete bad keys (_auth_data, _hashed_password...) -RestWrite.prototype.sanitizedData = function () { - var data = Object.keys(this.data).reduce(function (data, key) { - // Regexp comes from Parse.Object.prototype.validate - if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) { - delete data[key]; - } - return data; - }, deepcopy(this.data)); - return Parse._decode(undefined, data); -}; - -RestWrite.prototype.cleanUserAuthData = function () { - var _this11 = this; - - if (this.response && this.response.response && this.className === '_User') { - (function () { - var user = _this11.response.response; - if (user.authData) { - Object.keys(user.authData).forEach(function (provider) { - if (user.authData[provider] === null) { - delete user.authData[provider]; - } - }); - if (Object.keys(user.authData).length == 0) { - delete user.authData; - } - } - })(); - } -}; - -RestWrite.prototype.updateResponseWithData = function (response, data) { - var clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK); - Object.keys(data).forEach(function (fieldName) { - var dataValue = data[fieldName]; - var responseValue = response[fieldName]; - - response[fieldName] = responseValue || dataValue; - - // Strips operations from responses - if (response[fieldName] && response[fieldName].__op) { - delete response[fieldName]; - if (clientSupportsDelete && dataValue.__op == 'Delete') { - response[fieldName] = dataValue; - } - } - }); - return response; -}; - -exports.default = RestWrite; - -module.exports = RestWrite; \ No newline at end of file diff --git a/lib/Routers/AnalyticsRouter.js b/lib/Routers/AnalyticsRouter.js deleted file mode 100644 index c8632f069b..0000000000 --- a/lib/Routers/AnalyticsRouter.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AnalyticsRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // AnalyticsRouter.js - - -function appOpened(req) { - var analyticsController = req.config.analyticsController; - return analyticsController.appOpened(req); -} - -function trackEvent(req) { - var analyticsController = req.config.analyticsController; - return analyticsController.trackEvent(req); -} - -var AnalyticsRouter = exports.AnalyticsRouter = function (_PromiseRouter) { - _inherits(AnalyticsRouter, _PromiseRouter); - - function AnalyticsRouter() { - _classCallCheck(this, AnalyticsRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(AnalyticsRouter).apply(this, arguments)); - } - - _createClass(AnalyticsRouter, [{ - key: 'mountRoutes', - value: function mountRoutes() { - this.route('POST', '/events/AppOpened', appOpened); - this.route('POST', '/events/:eventName', trackEvent); - } - }]); - - return AnalyticsRouter; -}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/ClassesRouter.js b/lib/Routers/ClassesRouter.js deleted file mode 100644 index 811e4880cb..0000000000 --- a/lib/Routers/ClassesRouter.js +++ /dev/null @@ -1,278 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ClassesRouter = undefined; - -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -var _url = require('url'); - -var _url2 = _interopRequireDefault(_url); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var ALLOWED_GET_QUERY_KEYS = ['keys', 'include']; - -var ClassesRouter = exports.ClassesRouter = function (_PromiseRouter) { - _inherits(ClassesRouter, _PromiseRouter); - - function ClassesRouter() { - _classCallCheck(this, ClassesRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(ClassesRouter).apply(this, arguments)); - } - - _createClass(ClassesRouter, [{ - key: 'handleFind', - value: function handleFind(req) { - var body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); - var options = {}; - var allowConstraints = ['skip', 'limit', 'order', 'count', 'keys', 'include', 'redirectClassNameForKey', 'where']; - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = Object.keys(body)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var key = _step.value; - - if (allowConstraints.indexOf(key) === -1) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Invalid parameter for query: ' + key); - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - if (body.skip) { - options.skip = Number(body.skip); - } - if (body.limit || body.limit === 0) { - options.limit = Number(body.limit); - } else { - options.limit = Number(100); - } - if (body.order) { - options.order = String(body.order); - } - if (body.count) { - options.count = true; - } - if (typeof body.keys == 'string') { - options.keys = body.keys; - } - if (body.include) { - options.include = String(body.include); - } - if (body.redirectClassNameForKey) { - options.redirectClassNameForKey = String(body.redirectClassNameForKey); - } - if (typeof body.where === 'string') { - body.where = JSON.parse(body.where); - } - return _rest2.default.find(req.config, req.auth, req.params.className, body.where, options, req.info.clientSDK).then(function (response) { - if (response && response.results) { - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = response.results[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var result = _step2.value; - - if (result.sessionToken) { - result.sessionToken = req.info.sessionToken || result.sessionToken; - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - } - return { response: response }; - }); - } - - // Returns a promise for a {response} object. - - }, { - key: 'handleGet', - value: function handleGet(req) { - var body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); - var options = {}; - - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = Object.keys(body)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var key = _step3.value; - - if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter'); - } - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - - if (typeof body.keys == 'string') { - options.keys = body.keys; - } - if (body.include) { - options.include = String(body.include); - } - - return _rest2.default.get(req.config, req.auth, req.params.className, req.params.objectId, options, req.info.clientSDK).then(function (response) { - if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } - - if (req.params.className === "_User") { - - delete response.results[0].sessionToken; - - var user = response.results[0]; - - if (req.auth.user && user.objectId == req.auth.user.id) { - // Force the session token - response.results[0].sessionToken = req.info.sessionToken; - } - } - return { response: response.results[0] }; - }); - } - }, { - key: 'handleCreate', - value: function handleCreate(req) { - return _rest2.default.create(req.config, req.auth, req.params.className, req.body, req.info.clientSDK); - } - }, { - key: 'handleUpdate', - value: function handleUpdate(req) { - return _rest2.default.update(req.config, req.auth, req.params.className, req.params.objectId, req.body, req.info.clientSDK); - } - }, { - key: 'handleDelete', - value: function handleDelete(req) { - return _rest2.default.del(req.config, req.auth, req.params.className, req.params.objectId, req.info.clientSDK).then(function () { - return { response: {} }; - }); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/classes/:className', function (req) { - return _this2.handleFind(req); - }); - this.route('GET', '/classes/:className/:objectId', function (req) { - return _this2.handleGet(req); - }); - this.route('POST', '/classes/:className', function (req) { - return _this2.handleCreate(req); - }); - this.route('PUT', '/classes/:className/:objectId', function (req) { - return _this2.handleUpdate(req); - }); - this.route('DELETE', '/classes/:className/:objectId', function (req) { - return _this2.handleDelete(req); - }); - } - }], [{ - key: 'JSONFromQuery', - value: function JSONFromQuery(query) { - var json = {}; - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = Object.entries(query)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var _step4$value = _slicedToArray(_step4.value, 2); - - var key = _step4$value[0]; - var value = _step4$value[1]; - - try { - json[key] = JSON.parse(value); - } catch (e) { - json[key] = value; - } - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - return json; - } - }]); - - return ClassesRouter; -}(_PromiseRouter3.default); - -exports.default = ClassesRouter; \ No newline at end of file diff --git a/lib/Routers/FeaturesRouter.js b/lib/Routers/FeaturesRouter.js deleted file mode 100644 index 4319e4abef..0000000000 --- a/lib/Routers/FeaturesRouter.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.FeaturesRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _package = require('../../package.json'); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require('../middlewares'); - -var middleware = _interopRequireWildcard(_middlewares); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var FeaturesRouter = exports.FeaturesRouter = function (_PromiseRouter) { - _inherits(FeaturesRouter, _PromiseRouter); - - function FeaturesRouter() { - _classCallCheck(this, FeaturesRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(FeaturesRouter).apply(this, arguments)); - } - - _createClass(FeaturesRouter, [{ - key: 'mountRoutes', - value: function mountRoutes() { - this.route('GET', '/serverInfo', middleware.promiseEnforceMasterKeyAccess, function (req) { - var features = { - globalConfig: { - create: true, - read: true, - update: true, - delete: true - }, - hooks: { - create: false, - read: false, - update: false, - delete: false - }, - logs: { - level: true, - size: true, - order: true, - until: true, - from: true - }, - push: { - immediatePush: req.config.pushController.pushIsAvailable, - scheduledPush: false, - storedPushData: req.config.pushController.pushIsAvailable, - pushAudiences: false - }, - schemas: { - addField: true, - removeField: true, - addClass: true, - removeClass: true, - clearAllDataFromClass: true, - exportClass: false, - editClassLevelPermissions: true, - editPointerPermissions: true - } - }; - - return { response: { - features: features, - parseServerVersion: _package.version - } }; - }); - } - }]); - - return FeaturesRouter; -}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/FilesRouter.js b/lib/Routers/FilesRouter.js deleted file mode 100644 index 7e7dbcda6a..0000000000 --- a/lib/Routers/FilesRouter.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.FilesRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _express = require('express'); - -var _express2 = _interopRequireDefault(_express); - -var _bodyParser = require('body-parser'); - -var _bodyParser2 = _interopRequireDefault(_bodyParser); - -var _middlewares = require('../middlewares'); - -var Middlewares = _interopRequireWildcard(_middlewares); - -var _cryptoUtils = require('../cryptoUtils'); - -var _Config = require('../Config'); - -var _Config2 = _interopRequireDefault(_Config); - -var _mime = require('mime'); - -var _mime2 = _interopRequireDefault(_mime); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var FilesRouter = exports.FilesRouter = function () { - function FilesRouter() { - _classCallCheck(this, FilesRouter); - } - - _createClass(FilesRouter, [{ - key: 'getExpressRouter', - value: function getExpressRouter() { - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var router = _express2.default.Router(); - router.get('/files/:appId/:filename', this.getHandler); - - router.post('/files', function (req, res, next) { - next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename not provided.')); - }); - - router.post('/files/:filename', Middlewares.allowCrossDomain, _bodyParser2.default.raw({ type: function type() { - return true; - }, limit: options.maxUploadSize || '20mb' }), // Allow uploads without Content-Type, or with any Content-Type. - Middlewares.handleParseHeaders, this.createHandler); - - router.delete('/files/:filename', Middlewares.allowCrossDomain, Middlewares.handleParseHeaders, Middlewares.enforceMasterKeyAccess, this.deleteHandler); - return router; - } - }, { - key: 'getHandler', - value: function getHandler(req, res) { - var config = new _Config2.default(req.params.appId); - var filesController = config.filesController; - var filename = req.params.filename; - filesController.getFileData(config, filename).then(function (data) { - res.status(200); - var contentType = _mime2.default.lookup(filename); - res.set('Content-Type', contentType); - res.end(data); - }).catch(function (err) { - res.status(404); - res.set('Content-Type', 'text/plain'); - res.end('File not found.'); - }); - } - }, { - key: 'createHandler', - value: function createHandler(req, res, next) { - if (!req.body || !req.body.length) { - next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')); - return; - } - - if (req.params.filename.length > 128) { - next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.')); - return; - } - - if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) { - next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.')); - return; - } - - var filename = req.params.filename; - var contentType = req.get('Content-type'); - var config = req.config; - var filesController = config.filesController; - - filesController.createFile(config, filename, req.body, contentType).then(function (result) { - res.status(201); - res.set('Location', result.url); - res.json(result); - }).catch(function (err) { - next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Could not store file.')); - }); - } - }, { - key: 'deleteHandler', - value: function deleteHandler(req, res, next) { - var filesController = req.config.filesController; - filesController.deleteFile(req.config, req.params.filename).then(function () { - res.status(200); - // TODO: return useful JSON here? - res.end(); - }).catch(function (error) { - next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR, 'Could not delete file.')); - }); - } - }]); - - return FilesRouter; -}(); \ No newline at end of file diff --git a/lib/Routers/FunctionsRouter.js b/lib/Routers/FunctionsRouter.js deleted file mode 100644 index bd9221f3a4..0000000000 --- a/lib/Routers/FunctionsRouter.js +++ /dev/null @@ -1,155 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.FunctionsRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _lodash = require('lodash'); - -var _lodash2 = _interopRequireDefault(_lodash); - -var _logger = require('../logger'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -// FunctionsRouter.js - -var express = require('express'), - Parse = require('parse/node').Parse, - triggers = require('../triggers'); - -function parseObject(obj) { - if (Array.isArray(obj)) { - return obj.map(function (item) { - return parseObject(item); - }); - } else if (obj && obj.__type == 'Date') { - return Object.assign(new Date(obj.iso), obj); - } else if (obj && obj.__type == 'File') { - return Parse.File.fromJSON(obj); - } else if (obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object') { - return parseParams(obj); - } else { - return obj; - } -} - -function parseParams(params) { - return _lodash2.default.mapValues(params, parseObject); -} - -var FunctionsRouter = exports.FunctionsRouter = function (_PromiseRouter) { - _inherits(FunctionsRouter, _PromiseRouter); - - function FunctionsRouter() { - _classCallCheck(this, FunctionsRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(FunctionsRouter).apply(this, arguments)); - } - - _createClass(FunctionsRouter, [{ - key: 'mountRoutes', - value: function mountRoutes() { - this.route('POST', '/functions/:functionName', FunctionsRouter.handleCloudFunction); - } - }], [{ - key: 'createResponseObject', - value: function createResponseObject(resolve, reject) { - return { - success: function success(result) { - resolve({ - response: { - result: Parse._encode(result) - } - }); - }, - error: function error(code, message) { - if (!message) { - message = code; - code = Parse.Error.SCRIPT_FAILED; - } - reject(new Parse.Error(code, message)); - } - }; - } - }, { - key: 'handleCloudFunction', - value: function handleCloudFunction(req) { - var applicationId = req.config.applicationId; - var theFunction = triggers.getFunction(req.params.functionName, applicationId); - var theValidator = triggers.getValidator(req.params.functionName, applicationId); - if (theFunction) { - var request; - var result; - - var _ret = function () { - var params = Object.assign({}, req.body, req.query); - params = parseParams(params); - request = { - params: params, - master: req.auth && req.auth.isMaster, - user: req.auth && req.auth.user, - installationId: req.info.installationId, - log: req.config.loggerController && req.config.loggerController.adapter, - headers: req.headers - }; - - - if (theValidator && typeof theValidator === "function") { - result = theValidator(request); - - if (!result) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed.'); - } - } - - return { - v: new Promise(function (resolve, reject) { - var response = FunctionsRouter.createResponseObject(function (result) { - _logger.logger.info('Ran cloud function ' + req.params.functionName + ' with:\nInput: ' + JSON.stringify(params) + '\nResult: ' + JSON.stringify(result.response.result), { - functionName: req.params.functionName, - params: params, - result: result.response.resut - }); - resolve(result); - }, function (error) { - _logger.logger.error('Failed running cloud function ' + req.params.functionName + ' with:\nInput: ' + JSON.stringify(params) + 'Error: ' + JSON.stringify(error), { - functionName: req.params.functionName, - params: params, - error: error - }); - reject(error); - }); - // Force the keys before the function calls. - Parse.applicationId = req.config.applicationId; - Parse.javascriptKey = req.config.javascriptKey; - Parse.masterKey = req.config.masterKey; - theFunction(request, response); - }) - }; - }(); - - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; - } else { - throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function.'); - } - } - }]); - - return FunctionsRouter; -}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/GlobalConfigRouter.js b/lib/Routers/GlobalConfigRouter.js deleted file mode 100644 index d59b8a3b03..0000000000 --- a/lib/Routers/GlobalConfigRouter.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.GlobalConfigRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require('../middlewares'); - -var middleware = _interopRequireWildcard(_middlewares); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // global_config.js - -var GlobalConfigRouter = exports.GlobalConfigRouter = function (_PromiseRouter) { - _inherits(GlobalConfigRouter, _PromiseRouter); - - function GlobalConfigRouter() { - _classCallCheck(this, GlobalConfigRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(GlobalConfigRouter).apply(this, arguments)); - } - - _createClass(GlobalConfigRouter, [{ - key: 'getGlobalConfig', - value: function getGlobalConfig(req) { - return req.config.database.find('_GlobalConfig', { objectId: 1 }, { limit: 1 }).then(function (results) { - if (results.length != 1) { - // If there is no config in the database - return empty config. - return { response: { params: {} } }; - } - var globalConfig = results[0]; - return { response: { params: globalConfig.params } }; - }); - } - }, { - key: 'updateGlobalConfig', - value: function updateGlobalConfig(req) { - var params = req.body.params; - // Transform in dot notation to make sure it works - var update = Object.keys(params).reduce(function (acc, key) { - acc['params.' + key] = params[key]; - return acc; - }, {}); - return req.config.database.update('_GlobalConfig', { objectId: 1 }, update, { upsert: true }).then(function () { - return { response: { result: true } }; - }); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/config', function (req) { - return _this2.getGlobalConfig(req); - }); - this.route('PUT', '/config', middleware.promiseEnforceMasterKeyAccess, function (req) { - return _this2.updateGlobalConfig(req); - }); - } - }]); - - return GlobalConfigRouter; -}(_PromiseRouter3.default); - -exports.default = GlobalConfigRouter; \ No newline at end of file diff --git a/lib/Routers/HooksRouter.js b/lib/Routers/HooksRouter.js deleted file mode 100644 index 909c0d88dc..0000000000 --- a/lib/Routers/HooksRouter.js +++ /dev/null @@ -1,155 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HooksRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _node = require('parse/node'); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require('../middlewares'); - -var middleware = _interopRequireWildcard(_middlewares); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var HooksRouter = exports.HooksRouter = function (_PromiseRouter) { - _inherits(HooksRouter, _PromiseRouter); - - function HooksRouter() { - _classCallCheck(this, HooksRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(HooksRouter).apply(this, arguments)); - } - - _createClass(HooksRouter, [{ - key: 'createHook', - value: function createHook(aHook, config) { - return config.hooksController.createHook(aHook).then(function (hook) { - return { response: hook }; - }); - } - }, { - key: 'updateHook', - value: function updateHook(aHook, config) { - return config.hooksController.updateHook(aHook).then(function (hook) { - return { response: hook }; - }); - } - }, { - key: 'handlePost', - value: function handlePost(req) { - return this.createHook(req.body, req.config); - } - }, { - key: 'handleGetFunctions', - value: function handleGetFunctions(req) { - var hooksController = req.config.hooksController; - if (req.params.functionName) { - return hooksController.getFunction(req.params.functionName).then(function (foundFunction) { - if (!foundFunction) { - throw new _node.Parse.Error(143, 'no function named: ' + req.params.functionName + ' is defined'); - } - return Promise.resolve({ response: foundFunction }); - }); - } - - return hooksController.getFunctions().then(function (functions) { - return { response: functions || [] }; - }, function (err) { - throw err; - }); - } - }, { - key: 'handleGetTriggers', - value: function handleGetTriggers(req) { - var hooksController = req.config.hooksController; - if (req.params.className && req.params.triggerName) { - - return hooksController.getTrigger(req.params.className, req.params.triggerName).then(function (foundTrigger) { - if (!foundTrigger) { - throw new _node.Parse.Error(143, 'class ' + req.params.className + ' does not exist'); - } - return Promise.resolve({ response: foundTrigger }); - }); - } - - return hooksController.getTriggers().then(function (triggers) { - return { response: triggers || [] }; - }); - } - }, { - key: 'handleDelete', - value: function handleDelete(req) { - var hooksController = req.config.hooksController; - if (req.params.functionName) { - return hooksController.deleteFunction(req.params.functionName).then(function () { - return { response: {} }; - }); - } else if (req.params.className && req.params.triggerName) { - return hooksController.deleteTrigger(req.params.className, req.params.triggerName).then(function () { - return { response: {} }; - }); - } - return Promise.resolve({ response: {} }); - } - }, { - key: 'handleUpdate', - value: function handleUpdate(req) { - var hook; - if (req.params.functionName && req.body.url) { - hook = {}; - hook.functionName = req.params.functionName; - hook.url = req.body.url; - } else if (req.params.className && req.params.triggerName && req.body.url) { - hook = {}; - hook.className = req.params.className; - hook.triggerName = req.params.triggerName; - hook.url = req.body.url; - } else { - throw new _node.Parse.Error(143, "invalid hook declaration"); - } - return this.updateHook(hook, req.config); - } - }, { - key: 'handlePut', - value: function handlePut(req) { - var body = req.body; - if (body.__op == "Delete") { - return this.handleDelete(req); - } else { - return this.handleUpdate(req); - } - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - this.route('GET', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this)); - this.route('GET', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this)); - this.route('GET', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this)); - this.route('GET', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this)); - this.route('POST', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this)); - this.route('POST', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this)); - this.route('PUT', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this)); - this.route('PUT', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this)); - } - }]); - - return HooksRouter; -}(_PromiseRouter3.default); - -exports.default = HooksRouter; \ No newline at end of file diff --git a/lib/Routers/IAPValidationRouter.js b/lib/Routers/IAPValidationRouter.js deleted file mode 100644 index ce91f9846e..0000000000 --- a/lib/Routers/IAPValidationRouter.js +++ /dev/null @@ -1,143 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.IAPValidationRouter = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require("../PromiseRouter"); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var request = require("request"); -var rest = require("../rest"); -var Auth = require("../Auth"); - -// TODO move validation logic in IAPValidationController -var IAP_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt"; -var IAP_PRODUCTION_URL = "https://buy.itunes.apple.com/verifyReceipt"; - -var APP_STORE_ERRORS = { - 21000: "The App Store could not read the JSON object you provided.", - 21002: "The data in the receipt-data property was malformed or missing.", - 21003: "The receipt could not be authenticated.", - 21004: "The shared secret you provided does not match the shared secret on file for your account.", - 21005: "The receipt server is not currently available.", - 21006: "This receipt is valid but the subscription has expired.", - 21007: "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.", - 21008: "This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead." -}; - -function appStoreError(status) { - status = parseInt(status); - var errorString = APP_STORE_ERRORS[status] || "unknown error."; - return { status: status, error: errorString }; -} - -function validateWithAppStore(url, receipt) { - return new Promise(function (fulfill, reject) { - request.post({ - url: url, - body: { "receipt-data": receipt }, - json: true - }, function (err, res, body) { - var status = body.status; - if (status == 0) { - // No need to pass anything, status is OK - return fulfill(); - } - // receipt is from test and should go to test - return reject(body); - }); - }); -} - -function getFileForProductIdentifier(productIdentifier, req) { - return rest.find(req.config, req.auth, '_Product', { productIdentifier: productIdentifier }, undefined, req.info.clientSDK).then(function (result) { - var products = result.results; - if (!products || products.length != 1) { - // Error not found or too many - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } - - var download = products[0].download; - return Promise.resolve({ response: download }); - }); -} - -var IAPValidationRouter = exports.IAPValidationRouter = function (_PromiseRouter) { - _inherits(IAPValidationRouter, _PromiseRouter); - - function IAPValidationRouter() { - _classCallCheck(this, IAPValidationRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(IAPValidationRouter).apply(this, arguments)); - } - - _createClass(IAPValidationRouter, [{ - key: "handleRequest", - value: function handleRequest(req) { - var receipt = req.body.receipt; - var productIdentifier = req.body.productIdentifier; - - if (!receipt || !productIdentifier) { - // TODO: Error, malformed request - throw new Parse.Error(Parse.Error.INVALID_JSON, "missing receipt or productIdentifier"); - } - - // Transform the object if there - // otherwise assume it's in Base64 already - if ((typeof receipt === "undefined" ? "undefined" : _typeof(receipt)) == "object") { - if (receipt["__type"] == "Bytes") { - receipt = receipt.base64; - } - } - - if (process.env.NODE_ENV == "test" && req.body.bypassAppStoreValidation) { - return getFileForProductIdentifier(productIdentifier, req); - } - - function successCallback() { - return getFileForProductIdentifier(productIdentifier, req); - }; - - function errorCallback(error) { - return Promise.resolve({ response: appStoreError(error.status) }); - } - - return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then(function () { - - return successCallback(); - }, function (error) { - if (error.status == 21007) { - return validateWithAppStore(IAP_SANDBOX_URL, receipt).then(function () { - return successCallback(); - }, function (error) { - return errorCallback(error); - }); - } - - return errorCallback(error); - }); - } - }, { - key: "mountRoutes", - value: function mountRoutes() { - this.route("POST", "/validate_purchase", this.handleRequest); - } - }]); - - return IAPValidationRouter; -}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/InstallationsRouter.js b/lib/Routers/InstallationsRouter.js deleted file mode 100644 index 7b8d5ac4c6..0000000000 --- a/lib/Routers/InstallationsRouter.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.InstallationsRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - -var _ClassesRouter2 = require('./ClassesRouter'); - -var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); - -var _PromiseRouter = require('../PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // InstallationsRouter.js - -var InstallationsRouter = exports.InstallationsRouter = function (_ClassesRouter) { - _inherits(InstallationsRouter, _ClassesRouter); - - function InstallationsRouter() { - _classCallCheck(this, InstallationsRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(InstallationsRouter).apply(this, arguments)); - } - - _createClass(InstallationsRouter, [{ - key: 'handleFind', - value: function handleFind(req) { - var body = Object.assign(req.body, _ClassesRouter3.default.JSONFromQuery(req.query)); - var options = {}; - - if (body.skip) { - options.skip = Number(body.skip); - } - if (body.limit || body.limit === 0) { - options.limit = Number(body.limit); - } - if (body.order) { - options.order = String(body.order); - } - if (body.count) { - options.count = true; - } - if (body.include) { - options.include = String(body.include); - } - - return _rest2.default.find(req.config, req.auth, '_Installation', body.where, options, req.info.clientSDK).then(function (response) { - return { response: response }; - }); - } - }, { - key: 'handleGet', - value: function handleGet(req) { - req.params.className = '_Installation'; - return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleGet', this).call(this, req); - } - }, { - key: 'handleCreate', - value: function handleCreate(req) { - req.params.className = '_Installation'; - return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleCreate', this).call(this, req); - } - }, { - key: 'handleUpdate', - value: function handleUpdate(req) { - req.params.className = '_Installation'; - return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleUpdate', this).call(this, req); - } - }, { - key: 'handleDelete', - value: function handleDelete(req) { - req.params.className = '_Installation'; - return _get(Object.getPrototypeOf(InstallationsRouter.prototype), 'handleDelete', this).call(this, req); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/installations', function (req) { - return _this2.handleFind(req); - }); - this.route('GET', '/installations/:objectId', function (req) { - return _this2.handleGet(req); - }); - this.route('POST', '/installations', function (req) { - return _this2.handleCreate(req); - }); - this.route('PUT', '/installations/:objectId', function (req) { - return _this2.handleUpdate(req); - }); - this.route('DELETE', '/installations/:objectId', function (req) { - return _this2.handleDelete(req); - }); - } - }]); - - return InstallationsRouter; -}(_ClassesRouter3.default); - -exports.default = InstallationsRouter; \ No newline at end of file diff --git a/lib/Routers/LogsRouter.js b/lib/Routers/LogsRouter.js deleted file mode 100644 index 79b4076a0c..0000000000 --- a/lib/Routers/LogsRouter.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.LogsRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _node = require('parse/node'); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require('../middlewares'); - -var middleware = _interopRequireWildcard(_middlewares); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var LogsRouter = exports.LogsRouter = function (_PromiseRouter) { - _inherits(LogsRouter, _PromiseRouter); - - function LogsRouter() { - _classCallCheck(this, LogsRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(LogsRouter).apply(this, arguments)); - } - - _createClass(LogsRouter, [{ - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/scriptlog', middleware.promiseEnforceMasterKeyAccess, this.validateRequest, function (req) { - return _this2.handleGET(req); - }); - } - }, { - key: 'validateRequest', - value: function validateRequest(req) { - if (!req.config || !req.config.loggerController) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not availabe'); - } - } - - // Returns a promise for a {response} object. - // query params: - // level (optional) Level of logging you want to query for (info || error) - // from (optional) Start time for the search. Defaults to 1 week ago. - // until (optional) End time for the search. Defaults to current time. - // order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”. - // size (optional) Number of rows returned by search. Defaults to 10 - // n same as size, overrides size if set - - }, { - key: 'handleGET', - value: function handleGET(req) { - var from = req.query.from; - var until = req.query.until; - var size = req.query.size; - if (req.query.n) { - size = req.query.n; - } - - var order = req.query.order; - var level = req.query.level; - var options = { - from: from, - until: until, - size: size, - order: order, - level: level - }; - - return req.config.loggerController.getLogs(options).then(function (result) { - return Promise.resolve({ - response: result - }); - }); - } - }]); - - return LogsRouter; -}(_PromiseRouter3.default); - -exports.default = LogsRouter; \ No newline at end of file diff --git a/lib/Routers/PublicAPIRouter.js b/lib/Routers/PublicAPIRouter.js deleted file mode 100644 index ce5c28050e..0000000000 --- a/lib/Routers/PublicAPIRouter.js +++ /dev/null @@ -1,239 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PublicAPIRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _UserController = require('../Controllers/UserController'); - -var _UserController2 = _interopRequireDefault(_UserController); - -var _Config = require('../Config'); - -var _Config2 = _interopRequireDefault(_Config); - -var _express = require('express'); - -var _express2 = _interopRequireDefault(_express); - -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -var _fs = require('fs'); - -var _fs2 = _interopRequireDefault(_fs); - -var _querystring = require('querystring'); - -var _querystring2 = _interopRequireDefault(_querystring); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var public_html = _path2.default.resolve(__dirname, "../../public_html"); -var views = _path2.default.resolve(__dirname, '../../views'); - -var PublicAPIRouter = exports.PublicAPIRouter = function (_PromiseRouter) { - _inherits(PublicAPIRouter, _PromiseRouter); - - function PublicAPIRouter() { - _classCallCheck(this, PublicAPIRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(PublicAPIRouter).apply(this, arguments)); - } - - _createClass(PublicAPIRouter, [{ - key: 'verifyEmail', - value: function verifyEmail(req) { - var _this2 = this; - - var _req$query = req.query; - var token = _req$query.token; - var username = _req$query.username; - - var appId = req.params.appId; - var config = new _Config2.default(appId); - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - if (!token || !username) { - return this.invalidLink(req); - } - - var userController = config.userController; - return userController.verifyEmail(username, token).then(function () { - var params = _querystring2.default.stringify({ username: username }); - return Promise.resolve({ - status: 302, - location: config.verifyEmailSuccessURL + '?' + params - }); - }, function () { - return _this2.invalidLink(req); - }); - } - }, { - key: 'changePassword', - value: function changePassword(req) { - return new Promise(function (resolve, reject) { - var config = new _Config2.default(req.query.id); - if (!config.publicServerURL) { - return resolve({ - status: 404, - text: 'Not found.' - }); - } - // Should we keep the file in memory or leave like that? - _fs2.default.readFile(_path2.default.resolve(views, "choose_password"), 'utf-8', function (err, data) { - if (err) { - return reject(err); - } - data = data.replace("PARSE_SERVER_URL", '\'' + config.publicServerURL + '\''); - resolve({ - text: data - }); - }); - }); - } - }, { - key: 'requestResetPassword', - value: function requestResetPassword(req) { - var _this3 = this; - - var config = req.config; - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - var _req$query2 = req.query; - var username = _req$query2.username; - var token = _req$query2.token; - - - if (!username || !token) { - return this.invalidLink(req); - } - - return config.userController.checkResetTokenValidity(username, token).then(function (user) { - var params = _querystring2.default.stringify({ token: token, id: config.applicationId, username: username, app: config.appName }); - return Promise.resolve({ - status: 302, - location: config.choosePasswordURL + '?' + params - }); - }, function () { - return _this3.invalidLink(req); - }); - } - }, { - key: 'resetPassword', - value: function resetPassword(req) { - - var config = req.config; - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - var _req$body = req.body; - var username = _req$body.username; - var token = _req$body.token; - var new_password = _req$body.new_password; - - - if (!username || !token || !new_password) { - return this.invalidLink(req); - } - - return config.userController.updatePassword(username, token, new_password).then(function (result) { - return Promise.resolve({ - status: 302, - location: config.passwordResetSuccessURL - }); - }, function (err) { - var params = _querystring2.default.stringify({ username: username, token: token, id: config.applicationId, error: err, app: config.appName }); - return Promise.resolve({ - status: 302, - location: config.choosePasswordURL + '?' + params - }); - }); - } - }, { - key: 'invalidLink', - value: function invalidLink(req) { - return Promise.resolve({ - status: 302, - location: req.config.invalidLinkURL - }); - } - }, { - key: 'missingPublicServerURL', - value: function missingPublicServerURL() { - return Promise.resolve({ - text: 'Not found.', - status: 404 - }); - } - }, { - key: 'setConfig', - value: function setConfig(req) { - req.config = new _Config2.default(req.params.appId); - return Promise.resolve(); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this4 = this; - - this.route('GET', '/apps/:appId/verify_email', function (req) { - _this4.setConfig(req); - }, function (req) { - return _this4.verifyEmail(req); - }); - - this.route('GET', '/apps/choose_password', function (req) { - return _this4.changePassword(req); - }); - - this.route('POST', '/apps/:appId/request_password_reset', function (req) { - _this4.setConfig(req); - }, function (req) { - return _this4.resetPassword(req); - }); - - this.route('GET', '/apps/:appId/request_password_reset', function (req) { - _this4.setConfig(req); - }, function (req) { - return _this4.requestResetPassword(req); - }); - } - }, { - key: 'expressApp', - value: function expressApp() { - var router = (0, _express2.default)(); - router.use("/apps", _express2.default.static(public_html)); - router.use("/", _get(Object.getPrototypeOf(PublicAPIRouter.prototype), 'expressApp', this).call(this)); - return router; - } - }]); - - return PublicAPIRouter; -}(_PromiseRouter3.default); - -exports.default = PublicAPIRouter; \ No newline at end of file diff --git a/lib/Routers/PurgeRouter.js b/lib/Routers/PurgeRouter.js deleted file mode 100644 index 9b26b79b7b..0000000000 --- a/lib/Routers/PurgeRouter.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PurgeRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require('../middlewares'); - -var middleware = _interopRequireWildcard(_middlewares); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var PurgeRouter = exports.PurgeRouter = function (_PromiseRouter) { - _inherits(PurgeRouter, _PromiseRouter); - - function PurgeRouter() { - _classCallCheck(this, PurgeRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(PurgeRouter).apply(this, arguments)); - } - - _createClass(PurgeRouter, [{ - key: 'handlePurge', - value: function handlePurge(req) { - return req.config.database.purgeCollection(req.params.className).then(function () { - var cacheAdapter = req.config.cacheController; - if (req.params.className == '_Session') { - cacheAdapter.user.clear(); - } else if (req.params.className == '_Role') { - cacheAdapter.role.clear(); - } - return { response: {} }; - }); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('DELETE', '/purge/:className', middleware.promiseEnforceMasterKeyAccess, function (req) { - return _this2.handlePurge(req); - }); - } - }]); - - return PurgeRouter; -}(_PromiseRouter3.default); - -exports.default = PurgeRouter; \ No newline at end of file diff --git a/lib/Routers/PushRouter.js b/lib/Routers/PushRouter.js deleted file mode 100644 index dadd1cc065..0000000000 --- a/lib/Routers/PushRouter.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PushRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require("../PromiseRouter"); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require("../middlewares"); - -var middleware = _interopRequireWildcard(_middlewares); - -var _node = require("parse/node"); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var PushRouter = exports.PushRouter = function (_PromiseRouter) { - _inherits(PushRouter, _PromiseRouter); - - function PushRouter() { - _classCallCheck(this, PushRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(PushRouter).apply(this, arguments)); - } - - _createClass(PushRouter, [{ - key: "mountRoutes", - value: function mountRoutes() { - this.route("POST", "/push", middleware.promiseEnforceMasterKeyAccess, PushRouter.handlePOST); - } - }], [{ - key: "handlePOST", - value: function handlePOST(req) { - var pushController = req.config.pushController; - if (!pushController) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Push controller is not set'); - } - - var where = PushRouter.getQueryCondition(req); - var resolve = void 0; - var promise = new Promise(function (_resolve) { - resolve = _resolve; - }); - pushController.sendPush(req.body, where, req.config, req.auth, function (pushStatusId) { - resolve({ - headers: { - 'X-Parse-Push-Status-Id': pushStatusId - }, - response: { - result: true - } - }); - }); - return promise; - } - - /** - * Get query condition from the request body. - * @param {Object} req A request object - * @returns {Object} The query condition, the where field in a query api call - */ - - }, { - key: "getQueryCondition", - value: function getQueryCondition(req) { - var body = req.body || {}; - var hasWhere = typeof body.where !== 'undefined'; - var hasChannels = typeof body.channels !== 'undefined'; - - var where = void 0; - if (hasWhere && hasChannels) { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Channels and query can not be set at the same time.'); - } else if (hasWhere) { - where = body.where; - } else if (hasChannels) { - where = { - "channels": { - "$in": body.channels - } - }; - } else { - throw new _node.Parse.Error(_node.Parse.Error.PUSH_MISCONFIGURED, 'Sending a push requires either "channels" or a "where" query.'); - } - return where; - } - }]); - - return PushRouter; -}(_PromiseRouter3.default); - -exports.default = PushRouter; \ No newline at end of file diff --git a/lib/Routers/RolesRouter.js b/lib/Routers/RolesRouter.js deleted file mode 100644 index 663099b76a..0000000000 --- a/lib/Routers/RolesRouter.js +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.RolesRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - -var _ClassesRouter2 = require('./ClassesRouter'); - -var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); - -var _PromiseRouter = require('../PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var RolesRouter = exports.RolesRouter = function (_ClassesRouter) { - _inherits(RolesRouter, _ClassesRouter); - - function RolesRouter() { - _classCallCheck(this, RolesRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(RolesRouter).apply(this, arguments)); - } - - _createClass(RolesRouter, [{ - key: 'handleFind', - value: function handleFind(req) { - req.params.className = '_Role'; - return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleFind', this).call(this, req); - } - }, { - key: 'handleGet', - value: function handleGet(req) { - req.params.className = '_Role'; - return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleGet', this).call(this, req); - } - }, { - key: 'handleCreate', - value: function handleCreate(req) { - req.params.className = '_Role'; - return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleCreate', this).call(this, req); - } - }, { - key: 'handleUpdate', - value: function handleUpdate(req) { - req.params.className = '_Role'; - return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleUpdate', this).call(this, req); - } - }, { - key: 'handleDelete', - value: function handleDelete(req) { - req.params.className = '_Role'; - return _get(Object.getPrototypeOf(RolesRouter.prototype), 'handleDelete', this).call(this, req); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/roles', function (req) { - return _this2.handleFind(req); - }); - this.route('GET', '/roles/:objectId', function (req) { - return _this2.handleGet(req); - }); - this.route('POST', '/roles', function (req) { - return _this2.handleCreate(req); - }); - this.route('PUT', '/roles/:objectId', function (req) { - return _this2.handleUpdate(req); - }); - this.route('DELETE', '/roles/:objectId', function (req) { - return _this2.handleDelete(req); - }); - } - }]); - - return RolesRouter; -}(_ClassesRouter3.default); - -exports.default = RolesRouter; \ No newline at end of file diff --git a/lib/Routers/SchemasRouter.js b/lib/Routers/SchemasRouter.js deleted file mode 100644 index 0eaedc4640..0000000000 --- a/lib/Routers/SchemasRouter.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SchemasRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _PromiseRouter2 = require('../PromiseRouter'); - -var _PromiseRouter3 = _interopRequireDefault(_PromiseRouter2); - -var _middlewares = require('../middlewares'); - -var middleware = _interopRequireWildcard(_middlewares); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -// schemas.js - -var express = require('express'), - Parse = require('parse/node').Parse, - SchemaController = require('../Controllers/SchemaController'); - -function classNameMismatchResponse(bodyClass, pathClass) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class name mismatch between ' + bodyClass + ' and ' + pathClass + '.'); -} - -function getAllSchemas(req) { - return req.config.database.loadSchema({ clearCache: true }).then(function (schemaController) { - return schemaController.getAllClasses(true); - }).then(function (schemas) { - return { response: { results: schemas } }; - }); -} - -function getOneSchema(req) { - var className = req.params.className; - return req.config.database.loadSchema({ clearCache: true }).then(function (schemaController) { - return schemaController.getOneSchema(className, true); - }).then(function (schema) { - return { response: schema }; - }).catch(function (error) { - if (error === undefined) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'Class ' + className + ' does not exist.'); - } else { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.'); - } - }); -} - -function createSchema(req) { - if (req.params.className && req.body.className) { - if (req.params.className != req.body.className) { - return classNameMismatchResponse(req.body.className, req.params.className); - } - } - - var className = req.params.className || req.body.className; - if (!className) { - throw new Parse.Error(135, 'POST ' + req.path + ' needs a class name.'); - } - - return req.config.database.loadSchema({ clearCache: true }).then(function (schema) { - return schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions); - }).then(function (schema) { - return { response: schema }; - }); -} - -function modifySchema(req) { - if (req.body.className && req.body.className != req.params.className) { - return classNameMismatchResponse(req.body.className, req.params.className); - } - - var submittedFields = req.body.fields || {}; - var className = req.params.className; - - return req.config.database.loadSchema({ clearCache: true }).then(function (schema) { - return schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database); - }).then(function (result) { - return { response: result }; - }); -} - -var deleteSchema = function deleteSchema(req) { - if (!SchemaController.classNameIsValid(req.params.className)) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, SchemaController.invalidClassNameMessage(req.params.className)); - } - return req.config.database.deleteSchema(req.params.className).then(function () { - return { response: {} }; - }); -}; - -var SchemasRouter = exports.SchemasRouter = function (_PromiseRouter) { - _inherits(SchemasRouter, _PromiseRouter); - - function SchemasRouter() { - _classCallCheck(this, SchemasRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(SchemasRouter).apply(this, arguments)); - } - - _createClass(SchemasRouter, [{ - key: 'mountRoutes', - value: function mountRoutes() { - this.route('GET', '/schemas', middleware.promiseEnforceMasterKeyAccess, getAllSchemas); - this.route('GET', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, getOneSchema); - this.route('POST', '/schemas', middleware.promiseEnforceMasterKeyAccess, createSchema); - this.route('POST', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, createSchema); - this.route('PUT', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, modifySchema); - this.route('DELETE', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, deleteSchema); - } - }]); - - return SchemasRouter; -}(_PromiseRouter3.default); \ No newline at end of file diff --git a/lib/Routers/SessionsRouter.js b/lib/Routers/SessionsRouter.js deleted file mode 100644 index 48c932f0be..0000000000 --- a/lib/Routers/SessionsRouter.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SessionsRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - -var _ClassesRouter2 = require('./ClassesRouter'); - -var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); - -var _PromiseRouter = require('../PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -var _Auth = require('../Auth'); - -var _Auth2 = _interopRequireDefault(_Auth); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var SessionsRouter = exports.SessionsRouter = function (_ClassesRouter) { - _inherits(SessionsRouter, _ClassesRouter); - - function SessionsRouter() { - _classCallCheck(this, SessionsRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(SessionsRouter).apply(this, arguments)); - } - - _createClass(SessionsRouter, [{ - key: 'handleFind', - value: function handleFind(req) { - req.params.className = '_Session'; - return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleFind', this).call(this, req); - } - }, { - key: 'handleGet', - value: function handleGet(req) { - req.params.className = '_Session'; - return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleGet', this).call(this, req); - } - }, { - key: 'handleCreate', - value: function handleCreate(req) { - req.params.className = '_Session'; - return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleCreate', this).call(this, req); - } - }, { - key: 'handleUpdate', - value: function handleUpdate(req) { - req.params.className = '_Session'; - return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleUpdate', this).call(this, req); - } - }, { - key: 'handleDelete', - value: function handleDelete(req) { - req.params.className = '_Session'; - return _get(Object.getPrototypeOf(SessionsRouter.prototype), 'handleDelete', this).call(this, req); - } - }, { - key: 'handleMe', - value: function handleMe(req) { - // TODO: Verify correct behavior - if (!req.info || !req.info.sessionToken) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); - } - return _rest2.default.find(req.config, _Auth2.default.master(req.config), '_Session', { sessionToken: req.info.sessionToken }, undefined, req.info.clientSDK).then(function (response) { - if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.'); - } - return { - response: response.results[0] - }; - }); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/sessions/me', function (req) { - return _this2.handleMe(req); - }); - this.route('GET', '/sessions', function (req) { - return _this2.handleFind(req); - }); - this.route('GET', '/sessions/:objectId', function (req) { - return _this2.handleGet(req); - }); - this.route('POST', '/sessions', function (req) { - return _this2.handleCreate(req); - }); - this.route('PUT', '/sessions/:objectId', function (req) { - return _this2.handleUpdate(req); - }); - this.route('DELETE', '/sessions/:objectId', function (req) { - return _this2.handleDelete(req); - }); - } - }]); - - return SessionsRouter; -}(_ClassesRouter3.default); - -exports.default = SessionsRouter; \ No newline at end of file diff --git a/lib/Routers/UsersRouter.js b/lib/Routers/UsersRouter.js deleted file mode 100644 index aff588d827..0000000000 --- a/lib/Routers/UsersRouter.js +++ /dev/null @@ -1,284 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.UsersRouter = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; - -var _deepcopy = require('deepcopy'); - -var _deepcopy2 = _interopRequireDefault(_deepcopy); - -var _Config = require('../Config'); - -var _Config2 = _interopRequireDefault(_Config); - -var _ClassesRouter2 = require('./ClassesRouter'); - -var _ClassesRouter3 = _interopRequireDefault(_ClassesRouter2); - -var _PromiseRouter = require('../PromiseRouter'); - -var _PromiseRouter2 = _interopRequireDefault(_PromiseRouter); - -var _rest = require('../rest'); - -var _rest2 = _interopRequireDefault(_rest); - -var _Auth = require('../Auth'); - -var _Auth2 = _interopRequireDefault(_Auth); - -var _password = require('../password'); - -var _password2 = _interopRequireDefault(_password); - -var _RestWrite = require('../RestWrite'); - -var _RestWrite2 = _interopRequireDefault(_RestWrite); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // These methods handle the User-related routes. - -var cryptoUtils = require('../cryptoUtils'); -var triggers = require('../triggers'); - -var UsersRouter = exports.UsersRouter = function (_ClassesRouter) { - _inherits(UsersRouter, _ClassesRouter); - - function UsersRouter() { - _classCallCheck(this, UsersRouter); - - return _possibleConstructorReturn(this, Object.getPrototypeOf(UsersRouter).apply(this, arguments)); - } - - _createClass(UsersRouter, [{ - key: 'handleFind', - value: function handleFind(req) { - req.params.className = '_User'; - return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleFind', this).call(this, req); - } - }, { - key: 'handleGet', - value: function handleGet(req) { - req.params.className = '_User'; - return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleGet', this).call(this, req); - } - }, { - key: 'handleCreate', - value: function handleCreate(req) { - var data = (0, _deepcopy2.default)(req.body); - req.body = data; - req.params.className = '_User'; - - return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleCreate', this).call(this, req); - } - }, { - key: 'handleUpdate', - value: function handleUpdate(req) { - req.params.className = '_User'; - return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleUpdate', this).call(this, req); - } - }, { - key: 'handleDelete', - value: function handleDelete(req) { - req.params.className = '_User'; - return _get(Object.getPrototypeOf(UsersRouter.prototype), 'handleDelete', this).call(this, req); - } - }, { - key: 'handleMe', - value: function handleMe(req) { - if (!req.info || !req.info.sessionToken) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); - } - var sessionToken = req.info.sessionToken; - return _rest2.default.find(req.config, _Auth2.default.master(req.config), '_Session', { sessionToken: sessionToken }, { include: 'user' }, req.info.clientSDK).then(function (response) { - if (!response.results || response.results.length == 0 || !response.results[0].user) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); - } else { - var user = response.results[0].user; - // Send token back on the login, because SDKs expect that. - user.sessionToken = sessionToken; - return { response: user }; - } - }); - } - }, { - key: 'handleLogIn', - value: function handleLogIn(req) { - // Use query parameters instead if provided in url - if (!req.body.username && req.query.username) { - req.body = req.query; - } - - // TODO: use the right error codes / descriptions. - if (!req.body.username) { - throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'username is required.'); - } - if (!req.body.password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required.'); - } - - var user = void 0; - return req.config.database.find('_User', { username: req.body.username }).then(function (results) { - if (!results.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); - } - user = results[0]; - - if (req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); - } - - return _password2.default.compare(req.body.password, user.password); - }).then(function (correct) { - - if (!correct) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); - } - - var token = 'r:' + cryptoUtils.newToken(); - user.sessionToken = token; - delete user.password; - - // Sometimes the authData still has null on that keys - // https://github.com/ParsePlatform/parse-server/issues/935 - if (user.authData) { - Object.keys(user.authData).forEach(function (provider) { - if (user.authData[provider] === null) { - delete user.authData[provider]; - } - }); - if (Object.keys(user.authData).length == 0) { - delete user.authData; - } - } - - req.config.filesController.expandFilesInObject(req.config, user); - - var expiresAt = req.config.generateSessionExpiresAt(); - var sessionData = { - sessionToken: token, - user: { - __type: 'Pointer', - className: '_User', - objectId: user.objectId - }, - createdWith: { - 'action': 'login', - 'authProvider': 'password' - }, - restricted: false, - expiresAt: Parse._encode(expiresAt) - }; - - if (req.info.installationId) { - sessionData.installationId = req.info.installationId; - } - - var create = new _RestWrite2.default(req.config, _Auth2.default.master(req.config), '_Session', null, sessionData); - return create.execute(); - }).then(function () { - return { response: user }; - }); - } - }, { - key: 'handleLogOut', - value: function handleLogOut(req) { - var success = { response: {} }; - if (req.info && req.info.sessionToken) { - return _rest2.default.find(req.config, _Auth2.default.master(req.config), '_Session', { sessionToken: req.info.sessionToken }, undefined, req.info.clientSDK).then(function (records) { - if (records.results && records.results.length) { - return _rest2.default.del(req.config, _Auth2.default.master(req.config), '_Session', records.results[0].objectId).then(function () { - return Promise.resolve(success); - }); - } - return Promise.resolve(success); - }); - } - return Promise.resolve(success); - } - }, { - key: 'handleResetRequest', - value: function handleResetRequest(req) { - try { - _Config2.default.validateEmailConfiguration({ - emailAdapter: req.config.userController.adapter, - appName: req.config.appName, - publicServerURL: req.config.publicServerURL, - emailVerifyTokenValidityDuration: req.config.emailVerifyTokenValidityDuration - }); - } catch (e) { - if (typeof e === 'string') { - // Maybe we need a Bad Configuration error, but the SDKs won't understand it. For now, Internal Server Error. - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'An appName, publicServerURL, and emailAdapter are required for password reset functionality.'); - } else { - throw e; - } - } - var email = req.body.email; - - if (!email) { - throw new Parse.Error(Parse.Error.EMAIL_MISSING, "you must provide an email"); - } - var userController = req.config.userController; - return userController.sendPasswordResetEmail(email).then(function (token) { - return Promise.resolve({ - response: {} - }); - }, function (err) { - if (err.code === Parse.Error.OBJECT_NOT_FOUND) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'No user found with email ' + email + '.'); - } else { - throw err; - } - }); - } - }, { - key: 'mountRoutes', - value: function mountRoutes() { - var _this2 = this; - - this.route('GET', '/users', function (req) { - return _this2.handleFind(req); - }); - this.route('POST', '/users', function (req) { - return _this2.handleCreate(req); - }); - this.route('GET', '/users/me', function (req) { - return _this2.handleMe(req); - }); - this.route('GET', '/users/:objectId', function (req) { - return _this2.handleGet(req); - }); - this.route('PUT', '/users/:objectId', function (req) { - return _this2.handleUpdate(req); - }); - this.route('DELETE', '/users/:objectId', function (req) { - return _this2.handleDelete(req); - }); - this.route('GET', '/login', function (req) { - return _this2.handleLogIn(req); - }); - this.route('POST', '/logout', function (req) { - return _this2.handleLogOut(req); - }); - this.route('POST', '/requestPasswordReset', function (req) { - return _this2.handleResetRequest(req); - }); - } - }]); - - return UsersRouter; -}(_ClassesRouter3.default); - -exports.default = UsersRouter; \ No newline at end of file diff --git a/lib/TestUtils.js b/lib/TestUtils.js deleted file mode 100644 index 8ddd7759ee..0000000000 --- a/lib/TestUtils.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _DatabaseAdapter = require('./DatabaseAdapter'); - -var unsupported = function unsupported() { - throw 'Only supported in test environment'; -}; - -var _destroyAllDataPermanently = void 0; -if (process.env.TESTING) { - _destroyAllDataPermanently = _DatabaseAdapter.destroyAllDataPermanently; -} else { - _destroyAllDataPermanently = unsupported; -} - -exports.default = { - destroyAllDataPermanently: _destroyAllDataPermanently }; \ No newline at end of file diff --git a/lib/authDataManager/OAuth1Client.js b/lib/authDataManager/OAuth1Client.js deleted file mode 100644 index afdb8caf7b..0000000000 --- a/lib/authDataManager/OAuth1Client.js +++ /dev/null @@ -1,220 +0,0 @@ -'use strict'; - -var https = require('https'), - crypto = require('crypto'); - -var OAuth = function OAuth(options) { - this.consumer_key = options.consumer_key; - this.consumer_secret = options.consumer_secret; - this.auth_token = options.auth_token; - this.auth_token_secret = options.auth_token_secret; - this.host = options.host; - this.oauth_params = options.oauth_params || {}; -}; - -OAuth.prototype.send = function (method, path, params, body) { - - var request = this.buildRequest(method, path, params, body); - // Encode the body properly, the current Parse Implementation don't do it properly - return new Promise(function (resolve, reject) { - var httpRequest = https.request(request, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to make an OAuth request'); - }); - if (request.body) { - httpRequest.write(request.body); - } - httpRequest.end(); - }); -}; - -OAuth.prototype.buildRequest = function (method, path, params, body) { - if (path.indexOf("/") != 0) { - path = "/" + path; - } - if (params && Object.keys(params).length > 0) { - path += "?" + OAuth.buildParameterString(params); - } - - var request = { - host: this.host, - path: path, - method: method.toUpperCase() - }; - - var oauth_params = this.oauth_params || {}; - oauth_params.oauth_consumer_key = this.consumer_key; - if (this.auth_token) { - oauth_params["oauth_token"] = this.auth_token; - } - - request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret); - - if (body && Object.keys(body).length > 0) { - request.body = OAuth.buildParameterString(body); - } - return request; -}; - -OAuth.prototype.get = function (path, params) { - return this.send("GET", path, params); -}; - -OAuth.prototype.post = function (path, params, body) { - return this.send("POST", path, params, body); -}; - -/* - Proper string %escape encoding -*/ -OAuth.encode = function (str) { - // discuss at: http://phpjs.org/functions/rawurlencode/ - // original by: Brett Zamir (http://brett-zamir.me) - // input by: travc - // input by: Brett Zamir (http://brett-zamir.me) - // input by: Michael Grier - // input by: Ratheous - // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // bugfixed by: Brett Zamir (http://brett-zamir.me) - // bugfixed by: Joris - // reimplemented by: Brett Zamir (http://brett-zamir.me) - // reimplemented by: Brett Zamir (http://brett-zamir.me) - // note: This reflects PHP 5.3/6.0+ behavior - // note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on - // note: pages served as UTF-8 - // example 1: rawurlencode('Kevin van Zonneveld!'); - // returns 1: 'Kevin%20van%20Zonneveld%21' - // example 2: rawurlencode('http://kevin.vanzonneveld.net/'); - // returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F' - // example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a'); - // returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a' - - str = (str + '').toString(); - - // Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current - // PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following. - return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A'); -}; - -OAuth.signatureMethod = "HMAC-SHA1"; -OAuth.version = "1.0"; - -/* - Generate a nonce -*/ -OAuth.nonce = function () { - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (var i = 0; i < 30; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - }return text; -}; - -OAuth.buildParameterString = function (obj) { - var result = {}; - - // Sort keys and encode values - if (obj) { - var keys = Object.keys(obj).sort(); - - // Map key=value, join them by & - return keys.map(function (key) { - return key + "=" + OAuth.encode(obj[key]); - }).join("&"); - } - - return ""; -}; - -/* - Build the signature string from the object -*/ - -OAuth.buildSignatureString = function (method, url, parameters) { - return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join("&"); -}; - -/* - Retuns encoded HMAC-SHA1 from key and text -*/ -OAuth.signature = function (text, key) { - crypto = require("crypto"); - return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64')); -}; - -OAuth.signRequest = function (request, oauth_parameters, consumer_secret, auth_token_secret) { - oauth_parameters = oauth_parameters || {}; - - // Set default values - if (!oauth_parameters.oauth_nonce) { - oauth_parameters.oauth_nonce = OAuth.nonce(); - } - if (!oauth_parameters.oauth_timestamp) { - oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime() / 1000); - } - if (!oauth_parameters.oauth_signature_method) { - oauth_parameters.oauth_signature_method = OAuth.signatureMethod; - } - if (!oauth_parameters.oauth_version) { - oauth_parameters.oauth_version = OAuth.version; - } - - if (!auth_token_secret) { - auth_token_secret = ""; - } - // Force GET method if unset - if (!request.method) { - request.method = "GET"; - } - - // Collect all the parameters in one signatureParameters object - var signatureParams = {}; - var parametersToMerge = [request.params, request.body, oauth_parameters]; - for (var i in parametersToMerge) { - var parameters = parametersToMerge[i]; - for (var k in parameters) { - signatureParams[k] = parameters[k]; - } - } - - // Create a string based on the parameters - var parameterString = OAuth.buildParameterString(signatureParams); - - // Build the signature string - var url = "https://" + request.host + "" + request.path; - - var signatureString = OAuth.buildSignatureString(request.method, url, parameterString); - // Hash the signature string - var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join("&"); - - var signature = OAuth.signature(signatureString, signatureKey); - - // Set the signature in the params - oauth_parameters.oauth_signature = signature; - if (!request.headers) { - request.headers = {}; - } - - // Set the authorization header - var signature = Object.keys(oauth_parameters).sort().map(function (key) { - var value = oauth_parameters[key]; - return key + '="' + value + '"'; - }).join(", "); - - request.headers.Authorization = 'OAuth ' + signature; - - // Set the content type header - request.headers["Content-Type"] = "application/x-www-form-urlencoded"; - return request; -}; - -module.exports = OAuth; \ No newline at end of file diff --git a/lib/authDataManager/facebook.js b/lib/authDataManager/facebook.js deleted file mode 100644 index 6e3c2ae97b..0000000000 --- a/lib/authDataManager/facebook.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -// Helper functions for accessing the Facebook Graph API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return graphRequest('me?fields=id&access_token=' + authData.access_token).then(function (data) { - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId(appIds, authData) { - var access_token = authData.access_token; - if (!appIds.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is not configured.'); - } - return graphRequest('app?access_token=' + access_token).then(function (data) { - if (data && appIds.indexOf(data.id) != -1) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); - }); -} - -// A promisey wrapper for FB graph requests. -function graphRequest(path) { - return new Promise(function (resolve, reject) { - https.get('https://graph.facebook.com/v2.5/' + path, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Facebook.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/github.js b/lib/authDataManager/github.js deleted file mode 100644 index 6d8a13c298..0000000000 --- a/lib/authDataManager/github.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -// Helper functions for accessing the github API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return request('user', authData.access_token).then(function (data) { - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Github auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId() { - return Promise.resolve(); -} - -// A promisey wrapper for api requests -function request(path, access_token) { - return new Promise(function (resolve, reject) { - https.get({ - host: 'api.github.com', - path: '/' + path, - headers: { - 'Authorization': 'bearer ' + access_token, - 'User-Agent': 'parse-server' - } - }, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Github.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/google.js b/lib/authDataManager/google.js deleted file mode 100644 index c2bff64beb..0000000000 --- a/lib/authDataManager/google.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -// Helper functions for accessing the google API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return request("tokeninfo?id_token=" + authData.access_token).then(function (response) { - if (response && response.sub == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Google auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId() { - return Promise.resolve(); -} - -// A promisey wrapper for api requests -function request(path) { - return new Promise(function (resolve, reject) { - https.get("https://www.googleapis.com/oauth2/v3/" + path, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Google.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/index.js b/lib/authDataManager/index.js deleted file mode 100644 index ef7bfbc3e3..0000000000 --- a/lib/authDataManager/index.js +++ /dev/null @@ -1,103 +0,0 @@ -"use strict"; - -var facebook = require('./facebook'); -var instagram = require("./instagram"); -var linkedin = require("./linkedin"); -var meetup = require("./meetup"); -var google = require("./google"); -var github = require("./github"); -var twitter = require("./twitter"); -var spotify = require("./spotify"); -var digits = require("./twitter"); // digits tokens are validated by twitter - -var anonymous = { - validateAuthData: function validateAuthData() { - return Promise.resolve(); - }, - validateAppId: function validateAppId() { - return Promise.resolve(); - } -}; - -var providers = { - facebook: facebook, - instagram: instagram, - linkedin: linkedin, - meetup: meetup, - google: google, - github: github, - twitter: twitter, - spotify: spotify, - anonymous: anonymous, - digits: digits -}; - -module.exports = function () { - var oauthOptions = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var enableAnonymousUsers = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; - - var _enableAnonymousUsers = enableAnonymousUsers; - var setEnableAnonymousUsers = function setEnableAnonymousUsers(enable) { - _enableAnonymousUsers = enable; - }; - // To handle the test cases on configuration - var getValidatorForProvider = function getValidatorForProvider(provider) { - - if (provider === 'anonymous' && !_enableAnonymousUsers) { - return; - } - - var defaultProvider = providers[provider]; - var optionalProvider = oauthOptions[provider]; - - if (!defaultProvider && !optionalProvider) { - return; - } - - var appIds = void 0; - if (optionalProvider) { - appIds = optionalProvider.appIds; - } - - var validateAuthData; - var validateAppId; - - if (defaultProvider) { - validateAuthData = defaultProvider.validateAuthData; - validateAppId = defaultProvider.validateAppId; - } - - // Try the configuration methods - if (optionalProvider) { - if (optionalProvider.module) { - validateAuthData = require(optionalProvider.module).validateAuthData; - validateAppId = require(optionalProvider.module).validateAppId; - }; - - if (optionalProvider.validateAuthData) { - validateAuthData = optionalProvider.validateAuthData; - } - if (optionalProvider.validateAppId) { - validateAppId = optionalProvider.validateAppId; - } - } - - if (!validateAuthData || !validateAppId) { - return; - } - - return function (authData) { - return validateAuthData(authData, optionalProvider).then(function () { - if (appIds) { - return validateAppId(appIds, authData, optionalProvider); - } - return Promise.resolve(); - }); - }; - }; - - return Object.freeze({ - getValidatorForProvider: getValidatorForProvider, - setEnableAnonymousUsers: setEnableAnonymousUsers - }); -}; \ No newline at end of file diff --git a/lib/authDataManager/instagram.js b/lib/authDataManager/instagram.js deleted file mode 100644 index fb22f73e61..0000000000 --- a/lib/authDataManager/instagram.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -// Helper functions for accessing the instagram API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return request("users/self/?access_token=" + authData.access_token).then(function (response) { - if (response && response.data && response.data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Instagram auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId() { - return Promise.resolve(); -} - -// A promisey wrapper for api requests -function request(path) { - return new Promise(function (resolve, reject) { - https.get("https://api.instagram.com/v1/" + path, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Instagram.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/linkedin.js b/lib/authDataManager/linkedin.js deleted file mode 100644 index d4bc36c189..0000000000 --- a/lib/authDataManager/linkedin.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -// Helper functions for accessing the linkedin API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return request('people/~:(id)', authData.access_token).then(function (data) { - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId() { - return Promise.resolve(); -} - -// A promisey wrapper for api requests -function request(path, access_token) { - return new Promise(function (resolve, reject) { - https.get({ - host: 'api.linkedin.com', - path: '/v1/' + path, - headers: { - 'Authorization': 'Bearer ' + access_token, - 'x-li-format': 'json' - } - }, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Linkedin.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/meetup.js b/lib/authDataManager/meetup.js deleted file mode 100644 index 7908a49d91..0000000000 --- a/lib/authDataManager/meetup.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -// Helper functions for accessing the meetup API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return request('member/self', authData.access_token).then(function (data) { - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId() { - return Promise.resolve(); -} - -// A promisey wrapper for api requests -function request(path, access_token) { - return new Promise(function (resolve, reject) { - https.get({ - host: 'api.meetup.com', - path: '/2/' + path, - headers: { - 'Authorization': 'bearer ' + access_token - } - }, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Meetup.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/spotify.js b/lib/authDataManager/spotify.js deleted file mode 100644 index 7f13954349..0000000000 --- a/lib/authDataManager/spotify.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -// Helper functions for accessing the Spotify API. -var https = require('https'); -var Parse = require('parse/node').Parse; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { - return request('me', authData.access_token).then(function (data) { - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills if this app id is valid. -function validateAppId(appIds, authData) { - var access_token = authData.access_token; - if (!appIds.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is not configured.'); - } - return request('me', access_token).then(function (data) { - if (data && appIds.indexOf(data.id) != -1) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.'); - }); -} - -// A promisey wrapper for Spotify API requests. -function request(path, access_token) { - return new Promise(function (resolve, reject) { - https.get({ - host: 'api.spotify.com', - path: '/v1/' + path, - headers: { - 'Authorization': 'Bearer ' + access_token - } - }, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); - }); - }).on('error', function (e) { - reject('Failed to validate this access token with Spotify.'); - }); - }); -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData -}; \ No newline at end of file diff --git a/lib/authDataManager/twitter.js b/lib/authDataManager/twitter.js deleted file mode 100644 index a23c7925cf..0000000000 --- a/lib/authDataManager/twitter.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -// Helper functions for accessing the twitter API. -var OAuth = require('./OAuth1Client'); -var Parse = require('parse/node').Parse; -var logger = require('../logger').default; - -// Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData, options) { - options = handleMultipleConfigurations(authData, options); - var client = new OAuth(options); - client.host = "api.twitter.com"; - client.auth_token = authData.auth_token; - client.auth_token_secret = authData.auth_token_secret; - - return client.get("/1.1/account/verify_credentials.json").then(function (data) { - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); - }); -} - -// Returns a promise that fulfills iff this app id is valid. -function validateAppId() { - return Promise.resolve(); -} - -function handleMultipleConfigurations(authData, options) { - if (Array.isArray(options)) { - (function () { - var consumer_key = authData.consumer_key; - if (!consumer_key) { - logger.error('Twitter Auth', 'Multiple twitter configurations are available, by no consumer_key was sent by the client.'); - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); - } - options = options.filter(function (option) { - return option.consumer_key == consumer_key; - }); - - if (options.length == 0) { - logger.error('Twitter Auth', 'Cannot find a configuration for the provided consumer_key'); - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); - } - options = options[0]; - })(); - } - return options; -} - -module.exports = { - validateAppId: validateAppId, - validateAuthData: validateAuthData, - handleMultipleConfigurations: handleMultipleConfigurations -}; \ No newline at end of file diff --git a/lib/batch.js b/lib/batch.js deleted file mode 100644 index 85554bac69..0000000000 --- a/lib/batch.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict'; - -var Parse = require('parse/node').Parse; - -// These methods handle batch requests. -var batchPath = '/batch'; - -// Mounts a batch-handler onto a PromiseRouter. -function mountOnto(router) { - router.route('POST', batchPath, function (req) { - return handleBatch(router, req); - }); -} - -// Returns a promise for a {response} object. -// TODO: pass along auth correctly -function handleBatch(router, req) { - if (!req.body.requests instanceof Array) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'requests must be an array'); - } - - // The batch paths are all from the root of our domain. - // That means they include the API prefix, that the API is mounted - // to. However, our promise router does not route the api prefix. So - // we need to figure out the API prefix, so that we can strip it - // from all the subrequests. - if (!req.originalUrl.endsWith(batchPath)) { - throw 'internal routing problem - expected url to end with batch'; - } - var apiPrefixLength = req.originalUrl.length - batchPath.length; - var apiPrefix = req.originalUrl.slice(0, apiPrefixLength); - - var promises = []; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = req.body.requests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var restRequest = _step.value; - - // The routablePath is the path minus the api prefix - if (restRequest.path.slice(0, apiPrefixLength) != apiPrefix) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route batch path ' + restRequest.path); - } - var routablePath = restRequest.path.slice(apiPrefixLength); - - // Use the router to figure out what handler to use - var match = router.match(restRequest.method, routablePath); - if (!match) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route ' + restRequest.method + ' ' + routablePath); - } - - // Construct a request that we can send to a handler - var request = { - body: restRequest.body, - params: match.params, - config: req.config, - auth: req.auth, - info: req.info - }; - - promises.push(match.handler(request).then(function (response) { - return { success: response.response }; - }, function (error) { - return { error: { code: error.code, error: error.message } }; - })); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - return Promise.all(promises).then(function (results) { - return { response: results }; - }); -} - -module.exports = { - mountOnto: mountOnto -}; \ No newline at end of file diff --git a/lib/cache.js b/lib/cache.js deleted file mode 100644 index 51641150c3..0000000000 --- a/lib/cache.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppCache = undefined; - -var _InMemoryCache = require('./Adapters/Cache/InMemoryCache'); - -var AppCache = exports.AppCache = new _InMemoryCache.InMemoryCache({ ttl: NaN }); -exports.default = AppCache; \ No newline at end of file diff --git a/lib/cli/cli-definitions.js b/lib/cli/cli-definitions.js deleted file mode 100644 index 925168d383..0000000000 --- a/lib/cli/cli-definitions.js +++ /dev/null @@ -1,214 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -function numberParser(key) { - return function (opt) { - opt = parseInt(opt); - if (!Number.isInteger(opt)) { - throw new Error('The ' + key + ' is invalid'); - } - return opt; - }; -} - -function objectParser(opt) { - if ((typeof opt === 'undefined' ? 'undefined' : _typeof(opt)) == 'object') { - return opt; - } - return JSON.parse(opt); -} - -function moduleOrObjectParser(opt) { - if ((typeof opt === 'undefined' ? 'undefined' : _typeof(opt)) == 'object') { - return opt; - } - try { - return JSON.parse(opt); - } catch (e) {} - return opt; -} - -function booleanParser(opt) { - if (opt == true || opt == "true" || opt == "1") { - return true; - } - return false; -} - -exports.default = { - "appId": { - env: "PARSE_SERVER_APPLICATION_ID", - help: "Your Parse Application ID", - required: true - }, - "masterKey": { - env: "PARSE_SERVER_MASTER_KEY", - help: "Your Parse Master Key", - required: true - }, - "port": { - env: "PORT", - help: "The port to run the ParseServer. defaults to 1337.", - default: 1337, - action: numberParser("port") - }, - "databaseURI": { - env: "PARSE_SERVER_DATABASE_URI", - help: "The full URI to your mongodb database" - }, - "databaseOptions": { - env: "PARSE_SERVER_DATABASE_OPTIONS", - help: "Options to pass to the mongodb client", - action: objectParser - }, - "collectionPrefix": { - env: "PARSE_SERVER_COLLECTION_PREFIX", - help: 'A collection prefix for the classes' - }, - "serverURL": { - env: "PARSE_SERVER_URL", - help: "URL to your parse server with http:// or https://." - }, - "publicServerURL": { - env: "PARSE_PUBLIC_SERVER_URL", - help: "Public URL to your parse server with http:// or https://." - }, - "clientKey": { - env: "PARSE_SERVER_CLIENT_KEY", - help: "Key for iOS, MacOS, tvOS clients" - }, - "javascriptKey": { - env: "PARSE_SERVER_JAVASCRIPT_KEY", - help: "Key for the Javascript SDK" - }, - "restAPIKey": { - env: "PARSE_SERVER_REST_API_KEY", - help: "Key for REST calls" - }, - "dotNetKey": { - env: "PARSE_SERVER_DOT_NET_KEY", - help: "Key for Unity and .Net SDK" - }, - "webhookKey": { - env: "PARSE_SERVER_WEBHOOK_KEY", - help: "Key sent with outgoing webhook calls" - }, - "cloud": { - env: "PARSE_SERVER_CLOUD_CODE_MAIN", - help: "Full path to your cloud code main.js" - }, - "push": { - env: "PARSE_SERVER_PUSH", - help: "Configuration for push, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Push", - action: objectParser - }, - "oauth": { - env: "PARSE_SERVER_OAUTH_PROVIDERS", - help: "Configuration for your oAuth providers, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#oauth", - action: objectParser - }, - "fileKey": { - env: "PARSE_SERVER_FILE_KEY", - help: "Key for your files" - }, - "facebookAppIds": { - env: "PARSE_SERVER_FACEBOOK_APP_IDS", - help: "Comma separated list for your facebook app Ids", - type: "list", - action: function action(opt) { - return opt.split(","); - } - }, - "enableAnonymousUsers": { - env: "PARSE_SERVER_ENABLE_ANON_USERS", - help: "Enable (or disable) anon users, defaults to true", - action: booleanParser - }, - "allowClientClassCreation": { - env: "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION", - help: "Enable (or disable) client class creation, defaults to true", - action: booleanParser - }, - "mountPath": { - env: "PARSE_SERVER_MOUNT_PATH", - help: "Mount path for the server, defaults to /parse", - default: "/parse" - }, - "filesAdapter": { - env: "PARSE_SERVER_FILES_ADAPTER", - help: "Adapter module for the files sub-system", - action: moduleOrObjectParser - }, - "emailAdapter": { - env: "PARSE_SERVER_EMAIL_ADAPTER", - help: "Adapter module for the email sending", - action: moduleOrObjectParser - }, - "verifyUserEmails": { - env: "PARSE_SERVER_VERIFY_USER_EMAILS", - help: "Enable (or disable) user email validation, defaults to false", - action: booleanParser - }, - "preventLoginWithUnverifiedEmail": { - env: "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL", - help: "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false", - action: booleanParser - }, - "emailVerifyTokenValidityDuration": { - env: "PARSE_SERVER_EMAIL_VERIFY_TOKEN_VALIDITY_DURATION", - help: "Email verification token validity duration", - action: numberParser("emailVerifyTokenValidityDuration") - }, - "appName": { - env: "PARSE_SERVER_APP_NAME", - help: "Sets the app name" - }, - "loggerAdapter": { - env: "PARSE_SERVER_LOGGER_ADAPTER", - help: "Adapter module for the logging sub-system", - action: moduleOrObjectParser - }, - "liveQuery": { - env: "PARSE_SERVER_LIVE_QUERY_OPTIONS", - help: "liveQuery options", - action: objectParser - }, - "customPages": { - env: "PARSE_SERVER_CUSTOM_PAGES", - help: "custom pages for pasword validation and reset", - action: objectParser - }, - "maxUploadSize": { - env: "PARSE_SERVER_MAX_UPLOAD_SIZE", - help: "Max file size for uploads.", - default: "20mb" - }, - "sessionLength": { - env: "PARSE_SERVER_SESSION_LENGTH", - help: "Session duration, defaults to 1 year", - action: numberParser("sessionLength") - }, - "verbose": { - env: "VERBOSE", - help: "Set the logging to verbose" - }, - "jsonLogs": { - env: "JSON_LOGS", - help: "Log as structured JSON objects" - }, - "revokeSessionOnPasswordReset": { - env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET", - help: "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.", - action: booleanParser - }, - "schemaCacheTTL": { - env: "PARSE_SERVER_SCHEMA_CACHE_TTL", - help: "The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 0; disabled.", - action: numberParser("schemaCacheTTL") - } -}; \ No newline at end of file diff --git a/lib/cli/parse-server.js b/lib/cli/parse-server.js deleted file mode 100644 index acd9a20a47..0000000000 --- a/lib/cli/parse-server.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -var _express = require('express'); - -var _express2 = _interopRequireDefault(_express); - -var _index = require('../index'); - -var _cliDefinitions = require('./cli-definitions'); - -var _cliDefinitions2 = _interopRequireDefault(_cliDefinitions); - -var _commander = require('./utils/commander'); - -var _commander2 = _interopRequireDefault(_commander); - -var _colors = require('colors'); - -var _colors2 = _interopRequireDefault(_colors); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -_commander2.default.loadDefinitions(_cliDefinitions2.default); - -_commander2.default.usage('[options] '); - -_commander2.default.on('--help', function () { - console.log(' Get Started guide:'); - console.log(''); - console.log(' Please have a look at the get started guide!'); - console.log(' https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide'); - console.log(''); - console.log(''); - console.log(' Usage with npm start'); - console.log(''); - console.log(' $ npm start -- path/to/config.json'); - console.log(' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); - console.log(' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); - console.log(''); - console.log(''); - console.log(' Usage:'); - console.log(''); - console.log(' $ parse-server path/to/config.json'); - console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); - console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); - console.log(''); -}); - -_commander2.default.parse(process.argv, process.env); - -var options = _commander2.default.getOptions(); - -if (!options.serverURL) { - options.serverURL = 'http://localhost:' + options.port + options.mountPath; -} - -if (!options.appId || !options.masterKey || !options.serverURL) { - _commander2.default.outputHelp(); - console.error(""); - console.error(_colors2.default.red("ERROR: appId and masterKey are required")); - console.error(""); - process.exit(1); -} - -var app = (0, _express2.default)(); -var api = new _index.ParseServer(options); -app.use(options.mountPath, api); - -var server = app.listen(options.port, function () { - - for (var key in options) { - var value = options[key]; - if (key == "masterKey") { - value = "***REDACTED***"; - } - console.log(key + ': ' + value); - } - console.log(''); - console.log('parse-server running on ' + options.serverURL); -}); - -var handleShutdown = function handleShutdown() { - console.log('Termination signal received. Shutting down.'); - server.close(function () { - process.exit(0); - }); -}; -process.on('SIGTERM', handleShutdown); -process.on('SIGINT', handleShutdown); \ No newline at end of file diff --git a/lib/cli/utils/commander.js b/lib/cli/utils/commander.js deleted file mode 100644 index 0935290b52..0000000000 --- a/lib/cli/utils/commander.js +++ /dev/null @@ -1,148 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _commander = require('commander'); - -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var _definitions = void 0; -var _reverseDefinitions = void 0; -var _defaults = void 0; - -_commander.Command.prototype.loadDefinitions = function (definitions) { - _definitions = definitions; - - Object.keys(definitions).reduce(function (program, opt) { - if (_typeof(definitions[opt]) == "object") { - var additionalOptions = definitions[opt]; - if (additionalOptions.required === true) { - return program.option('--' + opt + ' <' + opt + '>', additionalOptions.help, additionalOptions.action); - } else { - return program.option('--' + opt + ' [' + opt + ']', additionalOptions.help, additionalOptions.action); - } - } - return program.option('--' + opt + ' [' + opt + ']'); - }, this); - - _defaults = Object.keys(definitions).reduce(function (defs, opt) { - if (_definitions[opt].default) { - defs[opt] = _definitions[opt].default; - } - return defs; - }, {}); - - _reverseDefinitions = Object.keys(definitions).reduce(function (object, key) { - var value = definitions[key]; - if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) == "object") { - value = value.env; - } - if (value) { - object[value] = key; - } - return object; - }, {}); - - /* istanbul ignore next */ - this.on('--help', function () { - console.log(' Configure From Environment:'); - console.log(''); - Object.keys(_reverseDefinitions).forEach(function (key) { - console.log(' $ ' + key + '=\'' + _reverseDefinitions[key] + '\''); - }); - console.log(''); - }); -}; - -function parseEnvironment() { - var env = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - return Object.keys(_reverseDefinitions).reduce(function (options, key) { - if (env[key]) { - var originalKey = _reverseDefinitions[key]; - var action = function action(option) { - return option; - }; - if (_typeof(_definitions[originalKey]) === "object") { - action = _definitions[originalKey].action || action; - } - options[_reverseDefinitions[key]] = action(env[key]); - } - return options; - }, {}); -} - -function parseConfigFile(program) { - var options = {}; - if (program.args.length > 0) { - var jsonPath = program.args[0]; - jsonPath = _path2.default.resolve(jsonPath); - var jsonConfig = require(jsonPath); - if (jsonConfig.apps) { - if (jsonConfig.apps.length > 1) { - throw 'Multiple apps are not supported'; - } - options = jsonConfig.apps[0]; - } else { - options = jsonConfig; - } - Object.keys(options).forEach(function (key) { - var value = options[key]; - if (!_definitions[key]) { - throw 'error: unknown option ' + key; - } - var action = _definitions[key].action; - if (action) { - options[key] = action(value); - } - }); - console.log('Configuration loaded from ' + jsonPath); - } - return options; -} - -_commander.Command.prototype.setValuesIfNeeded = function (options) { - var _this = this; - - Object.keys(options).forEach(function (key) { - if (!_this[key]) { - _this[key] = options[key]; - } - }); -}; - -_commander.Command.prototype._parse = _commander.Command.prototype.parse; - -_commander.Command.prototype.parse = function (args, env) { - this._parse(args); - // Parse the environment first - var envOptions = parseEnvironment(env); - var fromFile = parseConfigFile(this); - // Load the env if not passed from command line - this.setValuesIfNeeded(envOptions); - // Load from file to override - this.setValuesIfNeeded(fromFile); - // Last set the defaults - this.setValuesIfNeeded(_defaults); -}; - -_commander.Command.prototype.getOptions = function () { - var _this2 = this; - - return Object.keys(_definitions).reduce(function (options, key) { - if (typeof _this2[key] !== 'undefined') { - options[key] = _this2[key]; - } - return options; - }, {}); -}; - -exports.default = new _commander.Command(); \ No newline at end of file diff --git a/lib/cloud-code/HTTPResponse.js b/lib/cloud-code/HTTPResponse.js deleted file mode 100644 index 551a3621c1..0000000000 --- a/lib/cloud-code/HTTPResponse.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var HTTPResponse = function HTTPResponse(response, body) { - var _this = this; - - _classCallCheck(this, HTTPResponse); - - var _text = void 0, - _data = void 0; - this.status = response.statusCode; - this.headers = response.headers || {}; - this.cookies = this.headers["set-cookie"]; - - if (typeof body == 'string') { - _text = body; - } else if (Buffer.isBuffer(body)) { - this.buffer = body; - } else if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) == 'object') { - _data = body; - } - - var getText = function getText() { - if (!_text && _this.buffer) { - _text = _this.buffer.toString('utf-8'); - } else if (!_text && _data) { - _text = JSON.stringify(_data); - } - return _text; - }; - - var getData = function getData() { - if (!_data) { - try { - _data = JSON.parse(getText()); - } catch (e) {} - } - return _data; - }; - - Object.defineProperty(this, 'body', { - get: function get() { - return body; - } - }); - - Object.defineProperty(this, 'text', { - enumerable: true, - get: getText - }); - - Object.defineProperty(this, 'data', { - enumerable: true, - get: getData - }); -}; - -exports.default = HTTPResponse; \ No newline at end of file diff --git a/lib/cloud-code/Parse.Cloud.js b/lib/cloud-code/Parse.Cloud.js deleted file mode 100644 index 437d3cb1ca..0000000000 --- a/lib/cloud-code/Parse.Cloud.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -var _node = require('parse/node'); - -var _triggers = require('../triggers'); - -var triggers = _interopRequireWildcard(_triggers); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function validateClassNameForTriggers(className) { - var restrictedClassNames = ['_Session']; - if (restrictedClassNames.indexOf(className) != -1) { - throw 'Triggers are not supported for ' + className + ' class.'; - } - return className; -} - -function getClassName(parseClass) { - if (parseClass && parseClass.className) { - return validateClassNameForTriggers(parseClass.className); - } - return validateClassNameForTriggers(parseClass); -} - -var ParseCloud = {}; -ParseCloud.define = function (functionName, handler, validationHandler) { - triggers.addFunction(functionName, handler, validationHandler, _node.Parse.applicationId); -}; - -ParseCloud.beforeSave = function (parseClass, handler) { - var className = getClassName(parseClass); - triggers.addTrigger(triggers.Types.beforeSave, className, handler, _node.Parse.applicationId); -}; - -ParseCloud.beforeDelete = function (parseClass, handler) { - var className = getClassName(parseClass); - triggers.addTrigger(triggers.Types.beforeDelete, className, handler, _node.Parse.applicationId); -}; - -ParseCloud.afterSave = function (parseClass, handler) { - var className = getClassName(parseClass); - triggers.addTrigger(triggers.Types.afterSave, className, handler, _node.Parse.applicationId); -}; - -ParseCloud.afterDelete = function (parseClass, handler) { - var className = getClassName(parseClass); - triggers.addTrigger(triggers.Types.afterDelete, className, handler, _node.Parse.applicationId); -}; - -ParseCloud._removeHook = function (category, name, type, applicationId) { - applicationId = applicationId || _node.Parse.applicationId; - triggers._unregister(applicationId, category, name, type); -}; - -ParseCloud._removeAllHooks = function () { - triggers._unregisterAll(); -}; - -ParseCloud.httpRequest = require("./httpRequest"); - -module.exports = ParseCloud; \ No newline at end of file diff --git a/lib/cloud-code/httpRequest.js b/lib/cloud-code/httpRequest.js deleted file mode 100644 index 7f0136eed2..0000000000 --- a/lib/cloud-code/httpRequest.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _request = require('request'); - -var _request2 = _interopRequireDefault(_request); - -var _node = require('parse/node'); - -var _node2 = _interopRequireDefault(_node); - -var _HTTPResponse = require('./HTTPResponse'); - -var _HTTPResponse2 = _interopRequireDefault(_HTTPResponse); - -var _querystring = require('querystring'); - -var _querystring2 = _interopRequireDefault(_querystring); - -var _logger = require('../logger'); - -var _logger2 = _interopRequireDefault(_logger); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var encodeBody = function encodeBody(_ref) { - var body = _ref.body; - var _ref$headers = _ref.headers; - var headers = _ref$headers === undefined ? {} : _ref$headers; - - if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) !== 'object') { - return { body: body, headers: headers }; - } - var contentTypeKeys = Object.keys(headers).filter(function (key) { - return key.match(/content-type/i) != null; - }); - - if (contentTypeKeys.length == 0) { - // no content type - // As per https://parse.com/docs/cloudcode/guide#cloud-code-advanced-sending-a-post-request the default encoding is supposedly x-www-form-urlencoded - - body = _querystring2.default.stringify(body); - headers['Content-Type'] = 'application/x-www-form-urlencoded'; - } else { - /* istanbul ignore next */ - if (contentTypeKeys.length > 1) { - _logger2.default.error('Parse.Cloud.httpRequest', 'multiple content-type headers are set.'); - } - // There maybe many, we'll just take the 1st one - var contentType = contentTypeKeys[0]; - if (headers[contentType].match(/application\/json/i)) { - body = JSON.stringify(body); - } else if (headers[contentType].match(/application\/x-www-form-urlencoded/i)) { - body = _querystring2.default.stringify(body); - } - } - return { body: body, headers: headers }; -}; - -module.exports = function (options) { - var promise = new _node2.default.Promise(); - var callbacks = { - success: options.success, - error: options.error - }; - delete options.success; - delete options.error; - delete options.uri; // not supported - options = Object.assign(options, encodeBody(options)); - // set follow redirects to false by default - options.followRedirect = options.followRedirects == true; - // support params options - if (_typeof(options.params) === 'object') { - options.qs = options.params; - } else if (typeof options.params === 'string') { - options.qs = _querystring2.default.parse(options.params); - } - // force the response as a buffer - options.encoding = null; - - (0, _request2.default)(options, function (error, response, body) { - if (error) { - if (callbacks.error) { - callbacks.error(error); - } - return promise.reject(error); - } - var httpResponse = new _HTTPResponse2.default(response, body); - - // Consider <200 && >= 400 as errors - if (httpResponse.status < 200 || httpResponse.status >= 400) { - if (callbacks.error) { - callbacks.error(httpResponse); - } - return promise.reject(httpResponse); - } else { - if (callbacks.success) { - callbacks.success(httpResponse); - } - return promise.resolve(httpResponse); - } - }); - return promise; -}; - -module.exports.encodeBody = encodeBody; \ No newline at end of file diff --git a/lib/cryptoUtils.js b/lib/cryptoUtils.js deleted file mode 100644 index ae36817935..0000000000 --- a/lib/cryptoUtils.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.randomHexString = randomHexString; -exports.randomString = randomString; -exports.newObjectId = newObjectId; -exports.newToken = newToken; -exports.md5Hash = md5Hash; - -var _crypto = require('crypto'); - -// Returns a new random hex string of the given even size. -function randomHexString(size) { - if (size === 0) { - throw new Error('Zero-length randomHexString is useless.'); - } - if (size % 2 !== 0) { - throw new Error('randomHexString size must be divisible by 2.'); - } - return (0, _crypto.randomBytes)(size / 2).toString('hex'); -} - -// Returns a new random alphanumeric string of the given size. -// -// Note: to simplify implementation, the result has slight modulo bias, -// because chars length of 62 doesn't divide the number of all bytes -// (256) evenly. Such bias is acceptable for most cases when the output -// length is long enough and doesn't need to be uniform. - - -function randomString(size) { - if (size === 0) { - throw new Error('Zero-length randomString is useless.'); - } - var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789'; - var objectId = ''; - var bytes = (0, _crypto.randomBytes)(size); - for (var i = 0; i < bytes.length; ++i) { - objectId += chars[bytes.readUInt8(i) % chars.length]; - } - return objectId; -} - -// Returns a new random alphanumeric string suitable for object ID. -function newObjectId() { - //TODO: increase length to better protect against collisions. - return randomString(10); -} - -// Returns a new random hex string suitable for secure tokens. -function newToken() { - return randomHexString(32); -} - -function md5Hash(string) { - return (0, _crypto.createHash)('md5').update(string).digest('hex'); -} \ No newline at end of file diff --git a/lib/deprecated.js b/lib/deprecated.js deleted file mode 100644 index 3371f7cc51..0000000000 --- a/lib/deprecated.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.useExternal = useExternal; -function useExternal(name, moduleName) { - return function () { - throw name + " is not provided by parse-server anymore; please install " + moduleName; - }; -} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 63935f1d6a..0000000000 --- a/lib/index.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ParseServer = exports.logger = exports.TestUtils = exports.InMemoryCacheAdapter = exports.FileSystemAdapter = exports.GCSAdapter = exports.S3Adapter = undefined; - -var _ParseServer2 = require('./ParseServer'); - -var _ParseServer3 = _interopRequireDefault(_ParseServer2); - -var _logger = require('./logger'); - -var _logger2 = _interopRequireDefault(_logger); - -var _parseServerS3Adapter = require('parse-server-s3-adapter'); - -var _parseServerS3Adapter2 = _interopRequireDefault(_parseServerS3Adapter); - -var _parseServerFsAdapter = require('parse-server-fs-adapter'); - -var _parseServerFsAdapter2 = _interopRequireDefault(_parseServerFsAdapter); - -var _InMemoryCacheAdapter = require('./Adapters/Cache/InMemoryCacheAdapter'); - -var _InMemoryCacheAdapter2 = _interopRequireDefault(_InMemoryCacheAdapter); - -var _TestUtils = require('./TestUtils'); - -var _TestUtils2 = _interopRequireDefault(_TestUtils); - -var _deprecated = require('./deprecated'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// Factory function -var _ParseServer = function _ParseServer(options) { - var server = new _ParseServer3.default(options); - return server.app; -}; -// Mount the create liveQueryServer -_ParseServer.createLiveQueryServer = _ParseServer3.default.createLiveQueryServer; - -var GCSAdapter = (0, _deprecated.useExternal)('GCSAdapter', 'parse-server-gcs-adapter'); - -exports.default = _ParseServer3.default; -exports.S3Adapter = _parseServerS3Adapter2.default; -exports.GCSAdapter = GCSAdapter; -exports.FileSystemAdapter = _parseServerFsAdapter2.default; -exports.InMemoryCacheAdapter = _InMemoryCacheAdapter2.default; -exports.TestUtils = _TestUtils2.default; -exports.logger = _logger2.default; -exports.ParseServer = _ParseServer; \ No newline at end of file diff --git a/lib/logger.js b/lib/logger.js deleted file mode 100644 index 939ce57d34..0000000000 --- a/lib/logger.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.logger = undefined; -exports.configureLogger = configureLogger; -exports.addGroup = addGroup; - -var _winston = require('winston'); - -var _winston2 = _interopRequireDefault(_winston); - -var _fs = require('fs'); - -var _fs2 = _interopRequireDefault(_fs); - -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -var _winstonDailyRotateFile = require('winston-daily-rotate-file'); - -var _winstonDailyRotateFile2 = _interopRequireDefault(_winstonDailyRotateFile); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var LOGS_FOLDER = './logs/'; - -if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { - LOGS_FOLDER = './test_logs/'; -} - -LOGS_FOLDER = process.env.PARSE_SERVER_LOGS_FOLDER || LOGS_FOLDER; -var JSON_LOGS = process.env.JSON_LOGS || false; - -var currentLogsFolder = LOGS_FOLDER; - -function generateTransports(level) { - var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - var transports = [new _winstonDailyRotateFile2.default(Object.assign({ - filename: 'parse-server.info', - dirname: currentLogsFolder, - name: 'parse-server', - level: level - }, options)), new _winstonDailyRotateFile2.default(Object.assign({ - filename: 'parse-server.err', - dirname: currentLogsFolder, - name: 'parse-server-error', - level: 'error' - }), options)]; - if (!process.env.TESTING || process.env.VERBOSE) { - transports = [new _winston2.default.transports.Console(Object.assign({ - colorize: true, - level: level - }, options))].concat(transports); - } - return transports; -} - -var logger = new _winston2.default.Logger(); - -function configureLogger(_ref) { - var logsFolder = _ref.logsFolder; - var jsonLogs = _ref.jsonLogs; - var _ref$level = _ref.level; - var level = _ref$level === undefined ? _winston2.default.level : _ref$level; - - _winston2.default.level = level; - logsFolder = logsFolder || currentLogsFolder; - - if (!_path2.default.isAbsolute(logsFolder)) { - logsFolder = _path2.default.resolve(process.cwd(), logsFolder); - } - try { - _fs2.default.mkdirSync(logsFolder); - } catch (exception) { - // Ignore, assume the folder already exists - } - currentLogsFolder = logsFolder; - - var options = {}; - if (jsonLogs) { - options.json = true; - options.stringify = true; - } - var transports = generateTransports(level, options); - logger.configure({ - transports: transports - }); -} - -configureLogger({ logsFolder: LOGS_FOLDER, jsonLogs: JSON_LOGS }); - -function addGroup(groupName) { - var level = _winston2.default.level; - var transports = generateTransports().concat(new _winstonDailyRotateFile2.default({ - filename: groupName, - dirname: currentLogsFolder, - name: groupName, - level: level - })); - - _winston2.default.loggers.add(groupName, { - transports: transports - }); - return _winston2.default.loggers.get(groupName); -} - -exports.logger = logger; -exports.default = logger; \ No newline at end of file diff --git a/lib/middlewares.js b/lib/middlewares.js deleted file mode 100644 index f6f07be96e..0000000000 --- a/lib/middlewares.js +++ /dev/null @@ -1,292 +0,0 @@ -'use strict'; - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -var _logger = require('./logger'); - -var _logger2 = _interopRequireDefault(_logger); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var Parse = require('parse/node').Parse; - -var auth = require('./Auth'); -var Config = require('./Config'); -var ClientSDK = require('./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 -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) { - info.appId = basicAuth.appId; - 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 || !_cache2.default.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 && _cache2.default.get(req.body._ApplicationId) && (!info.masterKey || _cache2.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; - // 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 = _cache2.default.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. - var 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 auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken }).then(function (auth) { - if (auth) { - req.auth = auth; - next(); - } - }).catch(function (error) { - if (error instanceof Parse.Error) { - next(error); - return; - } else { - // TODO: Determine the correct error scenario. - _logger2.default.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(); -} - -var allowCrossDomain = 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(); - } -}; - -var allowMethodOverride = 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(); -}; - -var handleParseErrors = 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 { - _logger2.default.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); -}; - -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) { - var 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"}'); -} - -module.exports = { - allowCrossDomain: allowCrossDomain, - allowMethodOverride: allowMethodOverride, - handleParseErrors: handleParseErrors, - handleParseHeaders: handleParseHeaders, - enforceMasterKeyAccess: enforceMasterKeyAccess, - promiseEnforceMasterKeyAccess: promiseEnforceMasterKeyAccess -}; \ No newline at end of file diff --git a/lib/password.js b/lib/password.js deleted file mode 100644 index d050f6e306..0000000000 --- a/lib/password.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -// Tools for encrypting and decrypting passwords. -// Basically promise-friendly wrappers for bcrypt. -var bcrypt = require('bcrypt-nodejs'); - -// Returns a promise for a hashed password string. -function hash(password) { - return new Promise(function (fulfill, reject) { - bcrypt.hash(password, null, null, function (err, hashedPassword) { - if (err) { - reject(err); - } else { - fulfill(hashedPassword); - } - }); - }); -} - -// Returns a promise for whether this password compares to equal this -// hashed password. -function compare(password, hashedPassword) { - return new Promise(function (fulfill, reject) { - // Cannot bcrypt compare when one is undefined - if (!password || !hashedPassword) { - return fulfill(false); - } - bcrypt.compare(password, hashedPassword, function (err, success) { - if (err) { - reject(err); - } else { - fulfill(success); - } - }); - }); -} - -module.exports = { - hash: hash, - compare: compare -}; \ No newline at end of file diff --git a/lib/pushStatusHandler.js b/lib/pushStatusHandler.js deleted file mode 100644 index 3ca1e7485f..0000000000 --- a/lib/pushStatusHandler.js +++ /dev/null @@ -1,130 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.flatten = flatten; -exports.default = pushStatusHandler; - -var _cryptoUtils = require('./cryptoUtils'); - -var _logger = require('./logger'); - -var PUSH_STATUS_COLLECTION = '_PushStatus'; - -function flatten(array) { - return array.reduce(function (memo, element) { - if (Array.isArray(element)) { - memo = memo.concat(flatten(element)); - } else { - memo = memo.concat(element); - } - return memo; - }, []); -} - -function pushStatusHandler(config) { - - var initialPromise = void 0; - var pushStatus = void 0; - var objectId = (0, _cryptoUtils.newObjectId)(); - var database = config.database; - var lastPromise = void 0; - var setInitial = function setInitial() { - var body = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var where = arguments[1]; - var options = arguments.length <= 2 || arguments[2] === undefined ? { source: 'rest' } : arguments[2]; - - var now = new Date(); - var data = body.data || {}; - var payloadString = JSON.stringify(data); - var object = { - objectId: objectId, - createdAt: now, - pushTime: now.toISOString(), - query: JSON.stringify(where), - payload: payloadString, - source: options.source, - title: options.title, - expiry: body.expiration_time, - status: "pending", - numSent: 0, - pushHash: (0, _cryptoUtils.md5Hash)(data.alert || ''), - // lockdown! - ACL: {} - }; - - lastPromise = database.create(PUSH_STATUS_COLLECTION, object).then(function () { - pushStatus = { - objectId: objectId - }; - return Promise.resolve(pushStatus); - }); - return lastPromise; - }; - - var setRunning = function setRunning(installations) { - _logger.logger.verbose('sending push to %d installations', installations.length); - lastPromise = lastPromise.then(function () { - return database.update(PUSH_STATUS_COLLECTION, { status: "pending", objectId: objectId }, { status: "running", updatedAt: new Date() }); - }); - return lastPromise; - }; - - var complete = function complete(results) { - var update = { - status: 'succeeded', - updatedAt: new Date(), - numSent: 0, - numFailed: 0 - }; - if (Array.isArray(results)) { - results = flatten(results); - results.reduce(function (memo, result) { - // Cannot handle that - if (!result || !result.device || !result.device.deviceType) { - return memo; - } - var deviceType = result.device.deviceType; - if (result.transmitted) { - memo.numSent++; - memo.sentPerType = memo.sentPerType || {}; - memo.sentPerType[deviceType] = memo.sentPerType[deviceType] || 0; - memo.sentPerType[deviceType]++; - } else { - memo.numFailed++; - memo.failedPerType = memo.failedPerType || {}; - memo.failedPerType[deviceType] = memo.failedPerType[deviceType] || 0; - memo.failedPerType[deviceType]++; - } - return memo; - }, update); - } - _logger.logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed); - lastPromise = lastPromise.then(function () { - return database.update(PUSH_STATUS_COLLECTION, { status: "running", objectId: objectId }, update); - }); - return lastPromise; - }; - - var fail = function fail(err) { - var update = { - errorMessage: JSON.stringify(err), - status: 'failed', - updatedAt: new Date() - }; - _logger.logger.info('warning: error while sending push', err); - lastPromise = lastPromise.then(function () { - return database.update(PUSH_STATUS_COLLECTION, { objectId: objectId }, update); - }); - return lastPromise; - }; - - return Object.freeze({ - objectId: objectId, - setInitial: setInitial, - setRunning: setRunning, - complete: complete, - fail: fail - }); -} \ No newline at end of file diff --git a/lib/requiredParameter.js b/lib/requiredParameter.js deleted file mode 100644 index 6b8272e016..0000000000 --- a/lib/requiredParameter.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -exports.default = function (errorMessage) { - throw errorMessage; -}; \ No newline at end of file diff --git a/lib/rest.js b/lib/rest.js deleted file mode 100644 index a4fe205098..0000000000 --- a/lib/rest.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -var _Auth = require('./Auth'); - -var _Auth2 = _interopRequireDefault(_Auth); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// This file contains helpers for running operations in REST format. -// The goal is that handlers that explicitly handle an express route -// should just be shallow wrappers around things in this file, but -// these functions should not explicitly depend on the request -// object. -// This means that one of these handlers can support multiple -// routes. That's useful for the routes that do really similar -// things. - -var Parse = require('parse/node').Parse; - - -var RestQuery = require('./RestQuery'); -var RestWrite = require('./RestWrite'); -var triggers = require('./triggers'); - -// Returns a promise for an object with optional keys 'results' and 'count'. -function find(config, auth, className, restWhere, restOptions, clientSDK) { - enforceRoleSecurity('find', className, auth); - var query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK); - return query.execute(); -} - -// get is just like find but only queries an objectId. -var get = function get(config, auth, className, objectId, restOptions, clientSDK) { - enforceRoleSecurity('get', className, auth); - var query = new RestQuery(config, auth, className, { objectId: objectId }, restOptions, clientSDK); - return query.execute(); -}; - -// Returns a promise that doesn't resolve to any useful value. -function del(config, auth, className, objectId, clientSDK) { - if (typeof objectId !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad objectId'); - } - - if (className === '_User' && !auth.couldUpdateUserId(objectId)) { - throw new Parse.Error(Parse.Error.SESSION_MISSING, 'insufficient auth to delete user'); - } - - enforceRoleSecurity('delete', className, auth); - - var inflatedObject; - - return Promise.resolve().then(function () { - if (triggers.getTrigger(className, triggers.Types.beforeDelete, config.applicationId) || triggers.getTrigger(className, triggers.Types.afterDelete, config.applicationId) || config.liveQueryController && config.liveQueryController.hasLiveQuery(className) || className == '_Session') { - return find(config, _Auth2.default.master(config), className, { objectId: objectId }).then(function (response) { - if (response && response.results && response.results.length) { - response.results[0].className = className; - - var cacheAdapter = config.cacheController; - cacheAdapter.user.del(response.results[0].sessionToken); - inflatedObject = Parse.Object.fromJSON(response.results[0]); - // Notify LiveQuery server if possible - config.liveQueryController.onAfterDelete(inflatedObject.className, inflatedObject); - return triggers.maybeRunTrigger(triggers.Types.beforeDelete, auth, inflatedObject, null, config); - } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.'); - }); - } - return Promise.resolve({}); - }).then(function () { - if (!auth.isMaster) { - return auth.getUserRoles(); - } else { - return; - } - }).then(function () { - var options = {}; - if (!auth.isMaster) { - options.acl = ['*']; - if (auth.user) { - options.acl.push(auth.user.id); - options.acl = options.acl.concat(auth.userRoles); - } - } - - return config.database.destroy(className, { - objectId: objectId - }, options); - }).then(function () { - triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config); - return; - }); -} - -// Returns a promise for a {response, status, location} object. -function create(config, auth, className, restObject, clientSDK) { - enforceRoleSecurity('create', className, auth); - var write = new RestWrite(config, auth, className, null, restObject, null, clientSDK); - return write.execute(); -} - -// Returns a promise that contains the fields of the update that the -// REST API is supposed to return. -// Usually, this is just updatedAt. -function update(config, auth, className, objectId, restObject, clientSDK) { - enforceRoleSecurity('update', className, auth); - - return Promise.resolve().then(function () { - if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) || triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) || config.liveQueryController && config.liveQueryController.hasLiveQuery(className)) { - return find(config, _Auth2.default.master(config), className, { objectId: objectId }); - } - return Promise.resolve({}); - }).then(function (response) { - var originalRestObject; - if (response && response.results && response.results.length) { - originalRestObject = response.results[0]; - } - - var write = new RestWrite(config, auth, className, { objectId: objectId }, restObject, originalRestObject, clientSDK); - return write.execute(); - }); -} - -// Disallowing access to the _Role collection except by master key -function enforceRoleSecurity(method, className, auth) { - if (className === '_Installation' && !auth.isMaster) { - if (method === 'delete' || method === 'find') { - var error = 'Clients aren\'t allowed to perform the ' + method + ' operation on the installation collection.'; - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); - } - } -} - -module.exports = { - create: create, - del: del, - find: find, - get: get, - update: update -}; \ No newline at end of file diff --git a/lib/testing-routes.js b/lib/testing-routes.js deleted file mode 100644 index d6dd25deda..0000000000 --- a/lib/testing-routes.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -var _middlewares = require('./middlewares'); - -var middlewares = _interopRequireWildcard(_middlewares); - -var _index = require('./index'); - -var _node = require('parse/node'); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// testing-routes.js -var express = require('express'), - cryptoUtils = require('./cryptoUtils'); - -var router = express.Router(); - -// creates a unique app in the cache, with a collection prefix -function createApp(req, res) { - var appId = cryptoUtils.randomHexString(32); - - (0, _index.ParseServer)({ - databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase', - appId: appId, - masterKey: 'master', - serverURL: _node.Parse.serverURL, - collectionPrefix: appId - }); - var keys = { - 'application_id': appId, - 'client_key': 'unused', - 'windows_key': 'unused', - 'javascript_key': 'unused', - 'webhook_key': 'unused', - 'rest_api_key': 'unused', - 'master_key': 'master' - }; - res.status(200).send(keys); -} - -// deletes all collections that belong to the app -function clearApp(req, res) { - if (!req.auth.isMaster) { - return res.status(401).send({ "error": "unauthorized" }); - } - return req.config.database.deleteEverything().then(function () { - res.status(200).send({}); - }); -} - -// deletes all collections and drops the app from cache -function dropApp(req, res) { - if (!req.auth.isMaster) { - return res.status(401).send({ "error": "unauthorized" }); - } - return req.config.database.deleteEverything().then(function () { - _cache2.default.del(req.config.applicationId); - res.status(200).send({}); - }); -} - -// Lets just return a success response and see what happens. -function notImplementedYet(req, res) { - res.status(200).send({}); -} - -router.post('/rest_clear_app', middlewares.handleParseHeaders, clearApp); -router.post('/rest_block', middlewares.handleParseHeaders, notImplementedYet); -router.post('/rest_mock_v8_client', middlewares.handleParseHeaders, notImplementedYet); -router.post('/rest_unmock_v8_client', middlewares.handleParseHeaders, notImplementedYet); -router.post('/rest_verify_analytics', middlewares.handleParseHeaders, notImplementedYet); -router.post('/rest_create_app', createApp); -router.post('/rest_drop_app', middlewares.handleParseHeaders, dropApp); -router.post('/rest_configure_app', middlewares.handleParseHeaders, notImplementedYet); - -module.exports = { - router: router -}; \ No newline at end of file diff --git a/lib/triggers.js b/lib/triggers.js deleted file mode 100644 index 6efedb1cd7..0000000000 --- a/lib/triggers.js +++ /dev/null @@ -1,253 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Types = undefined; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; // triggers.js - - -exports.addFunction = addFunction; -exports.addTrigger = addTrigger; -exports.removeFunction = removeFunction; -exports.removeTrigger = removeTrigger; -exports._unregister = _unregister; -exports._unregisterAll = _unregisterAll; -exports.getTrigger = getTrigger; -exports.triggerExists = triggerExists; -exports.getFunction = getFunction; -exports.getValidator = getValidator; -exports.getRequestObject = getRequestObject; -exports.getResponseObject = getResponseObject; -exports.maybeRunTrigger = maybeRunTrigger; -exports.inflate = inflate; - -var _node = require('parse/node'); - -var _node2 = _interopRequireDefault(_node); - -var _cache = require('./cache'); - -var _cache2 = _interopRequireDefault(_cache); - -var _logger = require('./logger'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var Types = exports.Types = { - beforeSave: 'beforeSave', - afterSave: 'afterSave', - beforeDelete: 'beforeDelete', - afterDelete: 'afterDelete' -}; - -var baseStore = function baseStore() { - var Validators = {}; - var Functions = {}; - var Triggers = Object.keys(Types).reduce(function (base, key) { - base[key] = {}; - return base; - }, {}); - - return Object.freeze({ - Functions: Functions, - Validators: Validators, - Triggers: Triggers - }); -}; - -var _triggerStore = {}; - -function addFunction(functionName, handler, validationHandler, applicationId) { - applicationId = applicationId || _node2.default.applicationId; - _triggerStore[applicationId] = _triggerStore[applicationId] || baseStore(); - _triggerStore[applicationId].Functions[functionName] = handler; - _triggerStore[applicationId].Validators[functionName] = validationHandler; -} - -function addTrigger(type, className, handler, applicationId) { - applicationId = applicationId || _node2.default.applicationId; - _triggerStore[applicationId] = _triggerStore[applicationId] || baseStore(); - _triggerStore[applicationId].Triggers[type][className] = handler; -} - -function removeFunction(functionName, applicationId) { - applicationId = applicationId || _node2.default.applicationId; - delete _triggerStore[applicationId].Functions[functionName]; -} - -function removeTrigger(type, className, applicationId) { - applicationId = applicationId || _node2.default.applicationId; - delete _triggerStore[applicationId].Triggers[type][className]; -} - -function _unregister(appId, category, className, type) { - if (type) { - removeTrigger(className, type, appId); - delete _triggerStore[appId][category][className][type]; - } else { - delete _triggerStore[appId][category][className]; - } -} - -function _unregisterAll() { - Object.keys(_triggerStore).forEach(function (appId) { - return delete _triggerStore[appId]; - }); -} - -function getTrigger(className, triggerType, applicationId) { - if (!applicationId) { - throw "Missing ApplicationID"; - } - var manager = _triggerStore[applicationId]; - if (manager && manager.Triggers && manager.Triggers[triggerType] && manager.Triggers[triggerType][className]) { - return manager.Triggers[triggerType][className]; - } - return undefined; -}; - -function triggerExists(className, type, applicationId) { - return getTrigger(className, type, applicationId) != undefined; -} - -function getFunction(functionName, applicationId) { - var manager = _triggerStore[applicationId]; - if (manager && manager.Functions) { - return manager.Functions[functionName]; - }; - return undefined; -} - -function getValidator(functionName, applicationId) { - var manager = _triggerStore[applicationId]; - if (manager && manager.Validators) { - return manager.Validators[functionName]; - }; - return undefined; -} - -function getRequestObject(triggerType, auth, parseObject, originalParseObject, config) { - var request = { - triggerName: triggerType, - object: parseObject, - master: false, - log: config.loggerController && config.loggerController.adapter - }; - - if (originalParseObject) { - request.original = originalParseObject; - } - - if (!auth) { - return request; - } - if (auth.isMaster) { - request['master'] = true; - } - if (auth.user) { - request['user'] = auth.user; - } - if (auth.installationId) { - request['installationId'] = auth.installationId; - } - return request; -} - -// Creates the response object, and uses the request object to pass data -// The API will call this with REST API formatted objects, this will -// transform them to Parse.Object instances expected by Cloud Code. -// Any changes made to the object in a beforeSave will be included. -function getResponseObject(request, resolve, reject) { - return { - success: function success(response) { - // Use the JSON response - if (response && !request.object.equals(response) && request.triggerName === Types.beforeSave) { - return resolve(response); - } - response = {}; - if (request.triggerName === Types.beforeSave) { - response['object'] = request.object._getSaveJSON(); - } - return resolve(response); - }, - error: function error(code, message) { - if (!message) { - message = code; - code = _node2.default.Error.SCRIPT_FAILED; - } - var scriptError = new _node2.default.Error(code, message); - return reject(scriptError); - } - }; -}; - -function logTrigger(triggerType, className, input) { - if (triggerType.indexOf('after') != 0) { - return; - } - _logger.logger.info(triggerType + ' triggered for ' + className + '\nInput: ' + JSON.stringify(input), { - className: className, - triggerType: triggerType, - input: input - }); -} - -function logTriggerSuccess(triggerType, className, input, result) { - _logger.logger.info(triggerType + ' triggered for ' + className + '\nInput: ' + JSON.stringify(input) + '\nResult: ' + JSON.stringify(result), { - className: className, - triggerType: triggerType, - input: input, - result: result - }); -} - -function logTriggerError(triggerType, className, input, error) { - _logger.logger.error(triggerType + ' failed for ' + className + '\nInput: ' + JSON.stringify(input) + 'Error: ' + JSON.stringify(error), { - className: className, - triggerType: triggerType, - input: input, - error: error - }); -} - -// To be used as part of the promise chain when saving/deleting an object -// Will resolve successfully if no trigger is configured -// Resolves to an object, empty or containing an object key. A beforeSave -// trigger will set the object key to the rest format object to save. -// originalParseObject is optional, we only need that for befote/afterSave functions -function maybeRunTrigger(triggerType, auth, parseObject, originalParseObject, config) { - if (!parseObject) { - return Promise.resolve({}); - } - return new Promise(function (resolve, reject) { - var trigger = getTrigger(parseObject.className, triggerType, config.applicationId); - if (!trigger) return resolve(); - var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config); - var response = getResponseObject(request, function (object) { - logTriggerSuccess(triggerType, parseObject.className, parseObject.toJSON(), object); - resolve(object); - }, function (error) { - logTriggerError(triggerType, parseObject.className, parseObject.toJSON(), error); - reject(error); - }); - // Force the current Parse app before the trigger - _node2.default.applicationId = config.applicationId; - _node2.default.javascriptKey = config.javascriptKey || ''; - _node2.default.masterKey = config.masterKey; - // For the afterSuccess / afterDelete - logTrigger(triggerType, parseObject.className, parseObject.toJSON()); - trigger(request, response); - }); -}; - -// Converts a REST-format object to a Parse.Object -// data is either className or an object -function inflate(data, restObject) { - var copy = (typeof data === 'undefined' ? 'undefined' : _typeof(data)) == 'object' ? data : { className: data }; - for (var key in restObject) { - copy[key] = restObject[key]; - } - return _node2.default.Object.fromJSON(copy); -} \ No newline at end of file diff --git a/lib/vendor/mongodbUrl.js b/lib/vendor/mongodbUrl.js deleted file mode 100644 index fa60ea0c5c..0000000000 --- a/lib/vendor/mongodbUrl.js +++ /dev/null @@ -1,928 +0,0 @@ -// A slightly patched version of node's url module, with support for mongodb:// -// uris. -// -// See https://github.com/nodejs/node/blob/master/LICENSE for licensing -// information - -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var punycode = require('punycode'); - -exports.parse = urlParse; -exports.resolve = urlResolve; -exports.resolveObject = urlResolveObject; -exports.format = urlFormat; - -exports.Url = Url; - -function Url() { - this.protocol = null; - this.slashes = null; - this.auth = null; - this.host = null; - this.port = null; - this.hostname = null; - this.hash = null; - this.search = null; - this.query = null; - this.pathname = null; - this.path = null; - this.href = null; -} - -// Reference: RFC 3986, RFC 1808, RFC 2396 - -// define these here so at least they only have to be -// compiled once on the first module load. -var protocolPattern = /^([a-z0-9.+-]+:)/i; -var portPattern = /:[0-9]*$/; - -// Special case for a simple path URL -var simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; - -var hostnameMaxLen = 255; -// protocols that can allow "unsafe" and "unwise" chars. -var unsafeProtocol = { - 'javascript': true, - 'javascript:': true -}; -// protocols that never have a hostname. -var hostlessProtocol = { - 'javascript': true, - 'javascript:': true -}; -// protocols that always contain a // bit. -var slashedProtocol = { - 'http': true, - 'http:': true, - 'https': true, - 'https:': true, - 'ftp': true, - 'ftp:': true, - 'gopher': true, - 'gopher:': true, - 'file': true, - 'file:': true -}; -var querystring = require('querystring'); - -/* istanbul ignore next: improve coverage */ -function urlParse(url, parseQueryString, slashesDenoteHost) { - if (url instanceof Url) return url; - - var u = new Url(); - u.parse(url, parseQueryString, slashesDenoteHost); - return u; -} - -/* istanbul ignore next: improve coverage */ -Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { - if (typeof url !== 'string') { - throw new TypeError('Parameter "url" must be a string, not ' + (typeof url === 'undefined' ? 'undefined' : _typeof(url))); - } - - // Copy chrome, IE, opera backslash-handling behavior. - // Back slashes before the query string get converted to forward slashes - // See: https://code.google.com/p/chromium/issues/detail?id=25916 - var hasHash = false; - var start = -1; - var end = -1; - var rest = ''; - var lastPos = 0; - var i = 0; - for (var inWs = false, split = false; i < url.length; ++i) { - var code = url.charCodeAt(i); - - // Find first and last non-whitespace characters for trimming - var isWs = code === 32 /* */ || code === 9 /*\t*/ || code === 13 /*\r*/ || code === 10 /*\n*/ || code === 12 /*\f*/ || code === 160 /*\u00A0*/ || code === 65279 /*\uFEFF*/; - if (start === -1) { - if (isWs) continue; - lastPos = start = i; - } else { - if (inWs) { - if (!isWs) { - end = -1; - inWs = false; - } - } else if (isWs) { - end = i; - inWs = true; - } - } - - // Only convert backslashes while we haven't seen a split character - if (!split) { - switch (code) { - case 35: - // '#' - hasHash = true; - // Fall through - case 63: - // '?' - split = true; - break; - case 92: - // '\\' - if (i - lastPos > 0) rest += url.slice(lastPos, i); - rest += '/'; - lastPos = i + 1; - break; - } - } else if (!hasHash && code === 35 /*#*/) { - hasHash = true; - } - } - - // Check if string was non-empty (including strings with only whitespace) - if (start !== -1) { - if (lastPos === start) { - // We didn't convert any backslashes - - if (end === -1) { - if (start === 0) rest = url;else rest = url.slice(start); - } else { - rest = url.slice(start, end); - } - } else if (end === -1 && lastPos < url.length) { - // We converted some backslashes and have only part of the entire string - rest += url.slice(lastPos); - } else if (end !== -1 && lastPos < end) { - // We converted some backslashes and have only part of the entire string - rest += url.slice(lastPos, end); - } - } - - if (!slashesDenoteHost && !hasHash) { - // Try fast path regexp - var simplePath = simplePathPattern.exec(rest); - if (simplePath) { - this.path = rest; - this.href = rest; - this.pathname = simplePath[1]; - if (simplePath[2]) { - this.search = simplePath[2]; - if (parseQueryString) { - this.query = querystring.parse(this.search.slice(1)); - } else { - this.query = this.search.slice(1); - } - } else if (parseQueryString) { - this.search = ''; - this.query = {}; - } - return this; - } - } - - var proto = protocolPattern.exec(rest); - if (proto) { - proto = proto[0]; - var lowerProto = proto.toLowerCase(); - this.protocol = lowerProto; - rest = rest.slice(proto.length); - } - - // figure out if it's got a host - // user@server is *always* interpreted as a hostname, and url - // resolution will treat //foo/bar as host=foo,path=bar because that's - // how the browser resolves relative URLs. - if (slashesDenoteHost || proto || /^\/\/[^@\/]+@[^@\/]+/.test(rest)) { - var slashes = rest.charCodeAt(0) === 47 /*/*/ && rest.charCodeAt(1) === 47 /*/*/; - if (slashes && !(proto && hostlessProtocol[proto])) { - rest = rest.slice(2); - this.slashes = true; - } - } - - if (!hostlessProtocol[proto] && (slashes || proto && !slashedProtocol[proto])) { - - // there's a hostname. - // the first instance of /, ?, ;, or # ends the host. - // - // If there is an @ in the hostname, then non-host chars *are* allowed - // to the left of the last @ sign, unless some host-ending character - // comes *before* the @-sign. - // URLs are obnoxious. - // - // ex: - // http://a@b@c/ => user:a@b host:c - // http://a@b?@c => user:a host:b path:/?@c - - // v0.12 TODO(isaacs): This is not quite how Chrome does things. - // Review our test case against browsers more comprehensively. - - var hostEnd = -1; - var atSign = -1; - var nonHost = -1; - for (i = 0; i < rest.length; ++i) { - switch (rest.charCodeAt(i)) { - case 9: // '\t' - case 10: // '\n' - case 13: // '\r' - case 32: // ' ' - case 34: // '"' - case 37: // '%' - case 39: // '\'' - case 59: // ';' - case 60: // '<' - case 62: // '>' - case 92: // '\\' - case 94: // '^' - case 96: // '`' - case 123: // '{' - case 124: // '|' - case 125: - // '}' - // Characters that are never ever allowed in a hostname from RFC 2396 - if (nonHost === -1) nonHost = i; - break; - case 35: // '#' - case 47: // '/' - case 63: - // '?' - // Find the first instance of any host-ending characters - if (nonHost === -1) nonHost = i; - hostEnd = i; - break; - case 64: - // '@' - // At this point, either we have an explicit point where the - // auth portion cannot go past, or the last @ char is the decider. - atSign = i; - nonHost = -1; - break; - } - if (hostEnd !== -1) break; - } - start = 0; - if (atSign !== -1) { - this.auth = decodeURIComponent(rest.slice(0, atSign)); - start = atSign + 1; - } - if (nonHost === -1) { - this.host = rest.slice(start); - rest = ''; - } else { - this.host = rest.slice(start, nonHost); - rest = rest.slice(nonHost); - } - - // pull out port. - this.parseHost(); - - // we've indicated that there is a hostname, - // so even if it's empty, it has to be present. - if (typeof this.hostname !== 'string') this.hostname = ''; - - var hostname = this.hostname; - - // if hostname begins with [ and ends with ] - // assume that it's an IPv6 address. - var ipv6Hostname = hostname.charCodeAt(0) === 91 /*[*/ && hostname.charCodeAt(hostname.length - 1) === 93 /*]*/; - - // validate a little. - if (!ipv6Hostname) { - var result = validateHostname(this, rest, hostname); - if (result !== undefined) rest = result; - } - - if (this.hostname.length > hostnameMaxLen) { - this.hostname = ''; - } else { - // hostnames are always lower case. - this.hostname = this.hostname.toLowerCase(); - } - - if (!ipv6Hostname) { - // IDNA Support: Returns a punycoded representation of "domain". - // It only converts parts of the domain name that - // have non-ASCII characters, i.e. it doesn't matter if - // you call it with a domain that already is ASCII-only. - this.hostname = punycode.toASCII(this.hostname); - } - - var p = this.port ? ':' + this.port : ''; - var h = this.hostname || ''; - this.host = h + p; - - // strip [ and ] from the hostname - // the host field still retains them, though - if (ipv6Hostname) { - this.hostname = this.hostname.slice(1, -1); - if (rest[0] !== '/') { - rest = '/' + rest; - } - } - } - - // now rest is set to the post-host stuff. - // chop off any delim chars. - if (!unsafeProtocol[lowerProto]) { - // First, make 100% sure that any "autoEscape" chars get - // escaped, even if encodeURIComponent doesn't think they - // need to be. - var _result = autoEscapeStr(rest); - if (_result !== undefined) rest = _result; - } - - var questionIdx = -1; - var hashIdx = -1; - for (i = 0; i < rest.length; ++i) { - var _code = rest.charCodeAt(i); - if (_code === 35 /*#*/) { - this.hash = rest.slice(i); - hashIdx = i; - break; - } else if (_code === 63 /*?*/ && questionIdx === -1) { - questionIdx = i; - } - } - - if (questionIdx !== -1) { - if (hashIdx === -1) { - this.search = rest.slice(questionIdx); - this.query = rest.slice(questionIdx + 1); - } else { - this.search = rest.slice(questionIdx, hashIdx); - this.query = rest.slice(questionIdx + 1, hashIdx); - } - if (parseQueryString) { - this.query = querystring.parse(this.query); - } - } else if (parseQueryString) { - // no query string, but parseQueryString still requested - this.search = ''; - this.query = {}; - } - - var firstIdx = questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx) ? questionIdx : hashIdx; - if (firstIdx === -1) { - if (rest.length > 0) this.pathname = rest; - } else if (firstIdx > 0) { - this.pathname = rest.slice(0, firstIdx); - } - if (slashedProtocol[lowerProto] && this.hostname && !this.pathname) { - this.pathname = '/'; - } - - // to support http.request - if (this.pathname || this.search) { - var _p = this.pathname || ''; - var s = this.search || ''; - this.path = _p + s; - } - - // finally, reconstruct the href based on what has been validated. - this.href = this.format(); - return this; -}; - -/* istanbul ignore next: improve coverage */ -function validateHostname(self, rest, hostname) { - for (var i = 0, lastPos; i <= hostname.length; ++i) { - var code; - if (i < hostname.length) code = hostname.charCodeAt(i); - if (code === 46 /*.*/ || i === hostname.length) { - if (i - lastPos > 0) { - if (i - lastPos > 63) { - self.hostname = hostname.slice(0, lastPos + 63); - return '/' + hostname.slice(lastPos + 63) + rest; - } - } - lastPos = i + 1; - continue; - } else if (code >= 48 /*0*/ && code <= 57 /*9*/ || code >= 97 /*a*/ && code <= 122 /*z*/ || code === 45 /*-*/ || code >= 65 /*A*/ && code <= 90 /*Z*/ || code === 43 /*+*/ || code === 95 /*_*/ || - /* BEGIN MONGO URI PATCH */ - code === 44 /*,*/ || code === 58 /*:*/ || - /* END MONGO URI PATCH */ - code > 127) { - continue; - } - // Invalid host character - self.hostname = hostname.slice(0, i); - if (i < hostname.length) return '/' + hostname.slice(i) + rest; - break; - } -} - -/* istanbul ignore next: improve coverage */ -function autoEscapeStr(rest) { - var newRest = ''; - var lastPos = 0; - for (var i = 0; i < rest.length; ++i) { - // Automatically escape all delimiters and unwise characters from RFC 2396 - // Also escape single quotes in case of an XSS attack - switch (rest.charCodeAt(i)) { - case 9: - // '\t' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%09'; - lastPos = i + 1; - break; - case 10: - // '\n' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%0A'; - lastPos = i + 1; - break; - case 13: - // '\r' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%0D'; - lastPos = i + 1; - break; - case 32: - // ' ' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%20'; - lastPos = i + 1; - break; - case 34: - // '"' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%22'; - lastPos = i + 1; - break; - case 39: - // '\'' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%27'; - lastPos = i + 1; - break; - case 60: - // '<' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%3C'; - lastPos = i + 1; - break; - case 62: - // '>' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%3E'; - lastPos = i + 1; - break; - case 92: - // '\\' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%5C'; - lastPos = i + 1; - break; - case 94: - // '^' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%5E'; - lastPos = i + 1; - break; - case 96: - // '`' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%60'; - lastPos = i + 1; - break; - case 123: - // '{' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%7B'; - lastPos = i + 1; - break; - case 124: - // '|' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%7C'; - lastPos = i + 1; - break; - case 125: - // '}' - if (i - lastPos > 0) newRest += rest.slice(lastPos, i); - newRest += '%7D'; - lastPos = i + 1; - break; - } - } - if (lastPos === 0) return; - if (lastPos < rest.length) return newRest + rest.slice(lastPos);else return newRest; -} - -// format a parsed object into a url string -/* istanbul ignore next: improve coverage */ -function urlFormat(obj) { - // ensure it's an object, and not a string url. - // If it's an obj, this is a no-op. - // this way, you can call url_format() on strings - // to clean up potentially wonky urls. - if (typeof obj === 'string') obj = urlParse(obj);else if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object' || obj === null) throw new TypeError('Parameter "urlObj" must be an object, not ' + obj === null ? 'null' : typeof obj === 'undefined' ? 'undefined' : _typeof(obj));else if (!(obj instanceof Url)) return Url.prototype.format.call(obj); - - return obj.format(); -} - -/* istanbul ignore next: improve coverage */ -Url.prototype.format = function () { - var auth = this.auth || ''; - if (auth) { - auth = encodeAuth(auth); - auth += '@'; - } - - var protocol = this.protocol || ''; - var pathname = this.pathname || ''; - var hash = this.hash || ''; - var host = false; - var query = ''; - - if (this.host) { - host = auth + this.host; - } else if (this.hostname) { - host = auth + (this.hostname.indexOf(':') === -1 ? this.hostname : '[' + this.hostname + ']'); - if (this.port) { - host += ':' + this.port; - } - } - - if (this.query !== null && _typeof(this.query) === 'object') query = querystring.stringify(this.query); - - var search = this.search || query && '?' + query || ''; - - if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /*:*/) protocol += ':'; - - var newPathname = ''; - var lastPos = 0; - for (var i = 0; i < pathname.length; ++i) { - switch (pathname.charCodeAt(i)) { - case 35: - // '#' - if (i - lastPos > 0) newPathname += pathname.slice(lastPos, i); - newPathname += '%23'; - lastPos = i + 1; - break; - case 63: - // '?' - if (i - lastPos > 0) newPathname += pathname.slice(lastPos, i); - newPathname += '%3F'; - lastPos = i + 1; - break; - } - } - if (lastPos > 0) { - if (lastPos !== pathname.length) pathname = newPathname + pathname.slice(lastPos);else pathname = newPathname; - } - - // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. - // unless they had them to begin with. - if (this.slashes || (!protocol || slashedProtocol[protocol]) && host !== false) { - host = '//' + (host || ''); - if (pathname && pathname.charCodeAt(0) !== 47 /*/*/) pathname = '/' + pathname; - } else if (!host) { - host = ''; - } - - search = search.replace('#', '%23'); - - if (hash && hash.charCodeAt(0) !== 35 /*#*/) hash = '#' + hash; - if (search && search.charCodeAt(0) !== 63 /*?*/) search = '?' + search; - - return protocol + host + pathname + search + hash; -}; - -/* istanbul ignore next: improve coverage */ -function urlResolve(source, relative) { - return urlParse(source, false, true).resolve(relative); -} - -/* istanbul ignore next: improve coverage */ -Url.prototype.resolve = function (relative) { - return this.resolveObject(urlParse(relative, false, true)).format(); -}; - -/* istanbul ignore next: improve coverage */ -function urlResolveObject(source, relative) { - if (!source) return relative; - return urlParse(source, false, true).resolveObject(relative); -} - -/* istanbul ignore next: improve coverage */ -Url.prototype.resolveObject = function (relative) { - if (typeof relative === 'string') { - var rel = new Url(); - rel.parse(relative, false, true); - relative = rel; - } - - var result = new Url(); - var tkeys = Object.keys(this); - for (var tk = 0; tk < tkeys.length; tk++) { - var tkey = tkeys[tk]; - result[tkey] = this[tkey]; - } - - // hash is always overridden, no matter what. - // even href="" will remove it. - result.hash = relative.hash; - - // if the relative url is empty, then there's nothing left to do here. - if (relative.href === '') { - result.href = result.format(); - return result; - } - - // hrefs like //foo/bar always cut to the protocol. - if (relative.slashes && !relative.protocol) { - // take everything except the protocol from relative - var rkeys = Object.keys(relative); - for (var rk = 0; rk < rkeys.length; rk++) { - var rkey = rkeys[rk]; - if (rkey !== 'protocol') result[rkey] = relative[rkey]; - } - - //urlParse appends trailing / to urls like http://www.example.com - if (slashedProtocol[result.protocol] && result.hostname && !result.pathname) { - result.path = result.pathname = '/'; - } - - result.href = result.format(); - return result; - } - - if (relative.protocol && relative.protocol !== result.protocol) { - // if it's a known url protocol, then changing - // the protocol does weird things - // first, if it's not file:, then we MUST have a host, - // and if there was a path - // to begin with, then we MUST have a path. - // if it is file:, then the host is dropped, - // because that's known to be hostless. - // anything else is assumed to be absolute. - if (!slashedProtocol[relative.protocol]) { - var keys = Object.keys(relative); - for (var v = 0; v < keys.length; v++) { - var k = keys[v]; - result[k] = relative[k]; - } - result.href = result.format(); - return result; - } - - result.protocol = relative.protocol; - if (!relative.host && !/^file:?$/.test(relative.protocol) && !hostlessProtocol[relative.protocol]) { - var _relPath = (relative.pathname || '').split('/'); - while (_relPath.length && !(relative.host = _relPath.shift())) {} - if (!relative.host) relative.host = ''; - if (!relative.hostname) relative.hostname = ''; - if (_relPath[0] !== '') _relPath.unshift(''); - if (_relPath.length < 2) _relPath.unshift(''); - result.pathname = _relPath.join('/'); - } else { - result.pathname = relative.pathname; - } - result.search = relative.search; - result.query = relative.query; - result.host = relative.host || ''; - result.auth = relative.auth; - result.hostname = relative.hostname || relative.host; - result.port = relative.port; - // to support http.request - if (result.pathname || result.search) { - var p = result.pathname || ''; - var s = result.search || ''; - result.path = p + s; - } - result.slashes = result.slashes || relative.slashes; - result.href = result.format(); - return result; - } - - var isSourceAbs = result.pathname && result.pathname.charAt(0) === '/'; - var isRelAbs = relative.host || relative.pathname && relative.pathname.charAt(0) === '/'; - var mustEndAbs = isRelAbs || isSourceAbs || result.host && relative.pathname; - var removeAllDots = mustEndAbs; - var srcPath = result.pathname && result.pathname.split('/') || []; - var relPath = relative.pathname && relative.pathname.split('/') || []; - var psychotic = result.protocol && !slashedProtocol[result.protocol]; - - // if the url is a non-slashed url, then relative - // links like ../.. should be able - // to crawl up to the hostname, as well. This is strange. - // result.protocol has already been set by now. - // Later on, put the first path part into the host field. - if (psychotic) { - result.hostname = ''; - result.port = null; - if (result.host) { - if (srcPath[0] === '') srcPath[0] = result.host;else srcPath.unshift(result.host); - } - result.host = ''; - if (relative.protocol) { - relative.hostname = null; - relative.port = null; - if (relative.host) { - if (relPath[0] === '') relPath[0] = relative.host;else relPath.unshift(relative.host); - } - relative.host = null; - } - mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); - } - - if (isRelAbs) { - // it's absolute. - result.host = relative.host || relative.host === '' ? relative.host : result.host; - result.hostname = relative.hostname || relative.hostname === '' ? relative.hostname : result.hostname; - result.search = relative.search; - result.query = relative.query; - srcPath = relPath; - // fall through to the dot-handling below. - } else if (relPath.length) { - // it's relative - // throw away the existing file, and take the new path instead. - if (!srcPath) srcPath = []; - srcPath.pop(); - srcPath = srcPath.concat(relPath); - result.search = relative.search; - result.query = relative.query; - } else if (relative.search !== null && relative.search !== undefined) { - // just pull out the search. - // like href='?foo'. - // Put this after the other two cases because it simplifies the booleans - if (psychotic) { - result.hostname = result.host = srcPath.shift(); - //occasionally the auth can get stuck only in host - //this especially happens in cases like - //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; - if (authInHost) { - result.auth = authInHost.shift(); - result.host = result.hostname = authInHost.shift(); - } - } - result.search = relative.search; - result.query = relative.query; - //to support http.request - if (result.pathname !== null || result.search !== null) { - result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : ''); - } - result.href = result.format(); - return result; - } - - if (!srcPath.length) { - // no path at all. easy. - // we've already handled the other stuff above. - result.pathname = null; - //to support http.request - if (result.search) { - result.path = '/' + result.search; - } else { - result.path = null; - } - result.href = result.format(); - return result; - } - - // if a url ENDs in . or .., then it must get a trailing slash. - // however, if it ends in anything else non-slashy, - // then it must NOT get a trailing slash. - var last = srcPath.slice(-1)[0]; - var hasTrailingSlash = (result.host || relative.host || srcPath.length > 1) && (last === '.' || last === '..') || last === ''; - - // strip single dots, resolve double dots to parent dir - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = srcPath.length; i >= 0; i--) { - last = srcPath[i]; - if (last === '.') { - spliceOne(srcPath, i); - } else if (last === '..') { - spliceOne(srcPath, i); - up++; - } else if (up) { - spliceOne(srcPath, i); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (!mustEndAbs && !removeAllDots) { - for (; up--; up) { - srcPath.unshift('..'); - } - } - - if (mustEndAbs && srcPath[0] !== '' && (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { - srcPath.unshift(''); - } - - if (hasTrailingSlash && srcPath.join('/').substr(-1) !== '/') { - srcPath.push(''); - } - - var isAbsolute = srcPath[0] === '' || srcPath[0] && srcPath[0].charAt(0) === '/'; - - // put the host back - if (psychotic) { - result.hostname = result.host = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; - //occasionally the auth can get stuck only in host - //this especially happens in cases like - //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var _authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; - if (_authInHost) { - result.auth = _authInHost.shift(); - result.host = result.hostname = _authInHost.shift(); - } - } - - mustEndAbs = mustEndAbs || result.host && srcPath.length; - - if (mustEndAbs && !isAbsolute) { - srcPath.unshift(''); - } - - if (!srcPath.length) { - result.pathname = null; - result.path = null; - } else { - result.pathname = srcPath.join('/'); - } - - //to support request.http - if (result.pathname !== null || result.search !== null) { - result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : ''); - } - result.auth = relative.auth || result.auth; - result.slashes = result.slashes || relative.slashes; - result.href = result.format(); - return result; -}; - -/* istanbul ignore next: improve coverage */ -Url.prototype.parseHost = function () { - var host = this.host; - var port = portPattern.exec(host); - if (port) { - port = port[0]; - if (port !== ':') { - this.port = port.slice(1); - } - host = host.slice(0, host.length - port.length); - } - if (host) this.hostname = host; -}; - -// About 1.5x faster than the two-arg version of Array#splice(). -/* istanbul ignore next: improve coverage */ -function spliceOne(list, index) { - for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) { - list[i] = list[k]; - }list.pop(); -} - -var hexTable = new Array(256); -for (var i = 0; i < 256; ++i) { - hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); -} /* istanbul ignore next: improve coverage */ -function encodeAuth(str) { - // faster encodeURIComponent alternative for encoding auth uri components - var out = ''; - var lastPos = 0; - for (var i = 0; i < str.length; ++i) { - var c = str.charCodeAt(i); - - // These characters do not need escaping: - // ! - . _ ~ - // ' ( ) * : - // digits - // alpha (uppercase) - // alpha (lowercase) - if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E || c >= 0x27 && c <= 0x2A || c >= 0x30 && c <= 0x3A || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A) { - continue; - } - - if (i - lastPos > 0) out += str.slice(lastPos, i); - - lastPos = i + 1; - - // Other ASCII characters - if (c < 0x80) { - out += hexTable[c]; - continue; - } - - // Multi-byte characters ... - if (c < 0x800) { - out += hexTable[0xC0 | c >> 6] + hexTable[0x80 | c & 0x3F]; - continue; - } - if (c < 0xD800 || c >= 0xE000) { - out += hexTable[0xE0 | c >> 12] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | c & 0x3F]; - continue; - } - // Surrogate pair - ++i; - var c2; - if (i < str.length) c2 = str.charCodeAt(i) & 0x3FF;else c2 = 0; - c = 0x10000 + ((c & 0x3FF) << 10 | c2); - out += hexTable[0xF0 | c >> 18] + hexTable[0x80 | c >> 12 & 0x3F] + hexTable[0x80 | c >> 6 & 0x3F] + hexTable[0x80 | c & 0x3F]; - } - if (lastPos === 0) return str; - if (lastPos < str.length) return out + str.slice(lastPos); - return out; -} \ No newline at end of file