From 6ae9c153c2bbf1ddf868cf07682e73c8c7fd1d1f Mon Sep 17 00:00:00 2001 From: Manuel Trezza Date: Sat, 18 Apr 2020 18:09:42 +0200 Subject: [PATCH 1/3] added failing test --- spec/CloudCode.spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index bbd51d2a99..48ac7c2db5 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -2911,4 +2911,17 @@ describe('afterLogin hook', () => { await Parse.User.logIn('testuser', 'p@ssword'); done(); }); + + it('should have access to context as save argument', async () => { + // Declare triggers + Parse.Cloud.beforeSave('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + Parse.Cloud.afterSave('TestObject', (req) => { + expect(req.context.a).toEqual('a'); + }); + // Save object + const obj = new TestObject(); + await obj.save(null, { context: { a: 'a' } }); + }); }); From dad815d00e4d93c1d1657cfc4bcd4452724087a0 Mon Sep 17 00:00:00 2001 From: Manuel Trezza Date: Sat, 18 Apr 2020 18:21:03 +0200 Subject: [PATCH 2/3] added parsing of context in REST save request --- src/RestWrite.js | 187 +++++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 89 deletions(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index 38a795bb4d..a68bda94f4 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -53,6 +53,11 @@ function RestWrite( } if (!query) { + // Parse context + if (data._context && data._context instanceof Object) { + this.context = data._context; + delete data._context; + } if (this.config.allowCustomObjectId) { if ( Object.prototype.hasOwnProperty.call(data, 'objectId') && @@ -105,7 +110,7 @@ function RestWrite( // write, in order. // Returns a promise for a {response, status, location} object. // status and location are optional. -RestWrite.prototype.execute = function() { +RestWrite.prototype.execute = function () { return Promise.resolve() .then(() => { return this.getUserAndRoleACL(); @@ -131,7 +136,7 @@ RestWrite.prototype.execute = function() { .then(() => { return this.validateSchema(); }) - .then(schemaController => { + .then((schemaController) => { this.validSchemaController = schemaController; return this.setRequiredFieldsIfNeeded(); }) @@ -165,7 +170,7 @@ RestWrite.prototype.execute = function() { }; // Uses the Auth object to get the list of roles, adds the user id -RestWrite.prototype.getUserAndRoleACL = function() { +RestWrite.prototype.getUserAndRoleACL = function () { if (this.auth.isMaster) { return Promise.resolve(); } @@ -173,7 +178,7 @@ RestWrite.prototype.getUserAndRoleACL = function() { this.runOptions.acl = ['*']; if (this.auth.user) { - return this.auth.getUserRoles().then(roles => { + return this.auth.getUserRoles().then((roles) => { this.runOptions.acl = this.runOptions.acl.concat(roles, [ this.auth.user.id, ]); @@ -185,7 +190,7 @@ RestWrite.prototype.getUserAndRoleACL = function() { }; // Validates this operation against the allowClientClassCreation config. -RestWrite.prototype.validateClientClassCreation = function() { +RestWrite.prototype.validateClientClassCreation = function () { if ( this.config.allowClientClassCreation === false && !this.auth.isMaster && @@ -193,8 +198,8 @@ RestWrite.prototype.validateClientClassCreation = function() { ) { return this.config.database .loadSchema() - .then(schemaController => schemaController.hasClass(this.className)) - .then(hasClass => { + .then((schemaController) => schemaController.hasClass(this.className)) + .then((hasClass) => { if (hasClass !== true) { throw new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, @@ -210,7 +215,7 @@ RestWrite.prototype.validateClientClassCreation = function() { }; // Validates this operation against the schema. -RestWrite.prototype.validateSchema = function() { +RestWrite.prototype.validateSchema = function () { return this.config.database.validateObject( this.className, this.data, @@ -221,7 +226,7 @@ RestWrite.prototype.validateSchema = function() { // Runs any beforeSave triggers against this operation. // Any change leads to our data being mutated. -RestWrite.prototype.runBeforeSaveTrigger = function() { +RestWrite.prototype.runBeforeSaveTrigger = function () { if (this.response) { return; } @@ -274,7 +279,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function() { ); } // In the case that there is no permission for the operation, it throws an error - return databasePromise.then(result => { + return databasePromise.then((result) => { if (!result || result.length <= 0) { throw new Parse.Error( Parse.Error.OBJECT_NOT_FOUND, @@ -293,7 +298,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function() { this.context ); }) - .then(response => { + .then((response) => { if (response && response.object) { this.storage.fieldsChangedByTrigger = _.reduce( response.object, @@ -314,7 +319,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function() { }); }; -RestWrite.prototype.runBeforeLoginTrigger = async function(userData) { +RestWrite.prototype.runBeforeLoginTrigger = async function (userData) { // Avoid doing any setup for triggers if there is no 'beforeLogin' trigger if ( !triggers.triggerExists( @@ -341,11 +346,11 @@ RestWrite.prototype.runBeforeLoginTrigger = async function(userData) { ); }; -RestWrite.prototype.setRequiredFieldsIfNeeded = function() { +RestWrite.prototype.setRequiredFieldsIfNeeded = function () { if (this.data) { - return this.validSchemaController.getAllClasses().then(allClasses => { + return this.validSchemaController.getAllClasses().then((allClasses) => { const schema = allClasses.find( - oneClass => oneClass.className === this.className + (oneClass) => oneClass.className === this.className ); const setRequiredFieldIfNeeded = (fieldName, setDefault) => { if ( @@ -394,12 +399,12 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function() { ); } if (schema) { - Object.keys(schema.fields).forEach(fieldName => { + Object.keys(schema.fields).forEach((fieldName) => { setRequiredFieldIfNeeded(fieldName, true); }); } } else if (schema) { - Object.keys(this.data).forEach(fieldName => { + Object.keys(this.data).forEach((fieldName) => { setRequiredFieldIfNeeded(fieldName, false); }); } @@ -411,7 +416,7 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function() { // 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() { +RestWrite.prototype.validateAuthData = function () { if (this.className !== '_User') { return; } @@ -472,8 +477,8 @@ RestWrite.prototype.validateAuthData = function() { ); }; -RestWrite.prototype.handleAuthDataValidation = function(authData) { - const validations = Object.keys(authData).map(provider => { +RestWrite.prototype.handleAuthDataValidation = function (authData) { + const validations = Object.keys(authData).map((provider) => { if (authData[provider] === null) { return Promise.resolve(); } @@ -491,7 +496,7 @@ RestWrite.prototype.handleAuthDataValidation = function(authData) { return Promise.all(validations); }; -RestWrite.prototype.findUsersWithAuthData = function(authData) { +RestWrite.prototype.findUsersWithAuthData = function (authData) { const providers = Object.keys(authData); const query = providers .reduce((memo, provider) => { @@ -504,7 +509,7 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) { memo.push(query); return memo; }, []) - .filter(q => { + .filter((q) => { return typeof q !== 'undefined'; }); @@ -516,11 +521,11 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) { return findPromise; }; -RestWrite.prototype.filteredObjectsByACL = function(objects) { +RestWrite.prototype.filteredObjectsByACL = function (objects) { if (this.auth.isMaster) { return objects; } - return objects.filter(object => { + return objects.filter((object) => { if (!object.ACL) { return true; // legacy users that have no ACL field on them } @@ -529,9 +534,9 @@ RestWrite.prototype.filteredObjectsByACL = function(objects) { }); }; -RestWrite.prototype.handleAuthData = function(authData) { +RestWrite.prototype.handleAuthData = function (authData) { let results; - return this.findUsersWithAuthData(authData).then(async r => { + return this.findUsersWithAuthData(authData).then(async (r) => { results = this.filteredObjectsByACL(r); if (results.length == 1) { @@ -539,7 +544,7 @@ RestWrite.prototype.handleAuthData = function(authData) { const userResult = results[0]; const mutatedAuthData = {}; - Object.keys(authData).forEach(provider => { + Object.keys(authData).forEach((provider) => { const providerData = authData[provider]; const userAuthData = userResult.authData[provider]; if (!_.isEqual(providerData, userAuthData)) { @@ -589,7 +594,7 @@ RestWrite.prototype.handleAuthData = function(authData) { // If we're not logging in, but just updating the current user, we can safely skip that part if (this.response) { // Assign the new authData in the response - Object.keys(mutatedAuthData).forEach(provider => { + Object.keys(mutatedAuthData).forEach((provider) => { this.response.response.authData[provider] = mutatedAuthData[provider]; }); @@ -633,7 +638,7 @@ RestWrite.prototype.handleAuthData = function(authData) { }; // The non-third-party parts of User transformation -RestWrite.prototype.transformUser = function() { +RestWrite.prototype.transformUser = function () { var promise = Promise.resolve(); if (this.className !== '_User') { @@ -657,8 +662,8 @@ RestWrite.prototype.transformUser = function() { }, }) .execute() - .then(results => { - results.results.forEach(session => + .then((results) => { + results.results.forEach((session) => this.config.cacheController.user.del(session.sessionToken) ); }); @@ -681,10 +686,12 @@ RestWrite.prototype.transformUser = function() { } return this._validatePasswordPolicy().then(() => { - return passwordCrypto.hash(this.data.password).then(hashedPassword => { - this.data._hashed_password = hashedPassword; - delete this.data.password; - }); + return passwordCrypto + .hash(this.data.password) + .then((hashedPassword) => { + this.data._hashed_password = hashedPassword; + delete this.data.password; + }); }); }) .then(() => { @@ -695,7 +702,7 @@ RestWrite.prototype.transformUser = function() { }); }; -RestWrite.prototype._validateUserName = function() { +RestWrite.prototype._validateUserName = function () { // Check for username uniqueness if (!this.data.username) { if (!this.query) { @@ -722,7 +729,7 @@ RestWrite.prototype._validateUserName = function() { {}, this.validSchemaController ) - .then(results => { + .then((results) => { if (results.length > 0) { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, @@ -745,7 +752,7 @@ RestWrite.prototype._validateUserName = function() { Given that this lookup should be a relatively low use case and that the case sensitive unique index will be used by the db for the query, this is an adequate solution. */ -RestWrite.prototype._validateEmail = function() { +RestWrite.prototype._validateEmail = function () { if (!this.data.email || this.data.email.__op === 'Delete') { return Promise.resolve(); } @@ -770,7 +777,7 @@ RestWrite.prototype._validateEmail = function() { {}, this.validSchemaController ) - .then(results => { + .then((results) => { if (results.length > 0) { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, @@ -790,14 +797,14 @@ RestWrite.prototype._validateEmail = function() { }); }; -RestWrite.prototype._validatePasswordPolicy = function() { +RestWrite.prototype._validatePasswordPolicy = function () { if (!this.config.passwordPolicy) return Promise.resolve(); return this._validatePasswordRequirements().then(() => { return this._validatePasswordHistory(); }); }; -RestWrite.prototype._validatePasswordRequirements = function() { +RestWrite.prototype._validatePasswordRequirements = function () { // check if the password conforms to the defined password policy if configured // If we specified a custom error in our configuration use it. // Example: "Passwords must include a Capital Letter, Lowercase Letter, and a number." @@ -835,7 +842,7 @@ RestWrite.prototype._validatePasswordRequirements = function() { // retrieve the User object using objectId during password reset return this.config.database .find('_User', { objectId: this.objectId() }) - .then(results => { + .then((results) => { if (results.length != 1) { throw undefined; } @@ -853,7 +860,7 @@ RestWrite.prototype._validatePasswordRequirements = function() { return Promise.resolve(); }; -RestWrite.prototype._validatePasswordHistory = function() { +RestWrite.prototype._validatePasswordHistory = function () { // check whether password is repeating from specified history if (this.query && this.config.passwordPolicy.maxPasswordHistory) { return this.config.database @@ -862,7 +869,7 @@ RestWrite.prototype._validatePasswordHistory = function() { { objectId: this.objectId() }, { keys: ['_password_history', '_hashed_password'] } ) - .then(results => { + .then((results) => { if (results.length != 1) { throw undefined; } @@ -876,8 +883,8 @@ RestWrite.prototype._validatePasswordHistory = function() { oldPasswords.push(user.password); const newPassword = this.data.password; // compare the new password hash with all old password hashes - const promises = oldPasswords.map(function(hash) { - return passwordCrypto.compare(newPassword, hash).then(result => { + const promises = oldPasswords.map(function (hash) { + return passwordCrypto.compare(newPassword, hash).then((result) => { if (result) // reject if there is a match return Promise.reject('REPEAT_PASSWORD'); @@ -889,7 +896,7 @@ RestWrite.prototype._validatePasswordHistory = function() { .then(() => { return Promise.resolve(); }) - .catch(err => { + .catch((err) => { if (err === 'REPEAT_PASSWORD') // a match was found return Promise.reject( @@ -905,7 +912,7 @@ RestWrite.prototype._validatePasswordHistory = function() { return Promise.resolve(); }; -RestWrite.prototype.createSessionTokenIfNeeded = function() { +RestWrite.prototype.createSessionTokenIfNeeded = function () { if (this.className !== '_User') { return; } @@ -928,7 +935,7 @@ RestWrite.prototype.createSessionTokenIfNeeded = function() { return this.createSessionToken(); }; -RestWrite.prototype.createSessionToken = async function() { +RestWrite.prototype.createSessionToken = async function () { // cloud installationId from Cloud Code, // never create session tokens from there. if (this.auth.installationId && this.auth.installationId === 'cloud') { @@ -952,7 +959,7 @@ RestWrite.prototype.createSessionToken = async function() { }; // Delete email reset tokens if user is changing password or email. -RestWrite.prototype.deleteEmailResetTokenIfNeeded = function() { +RestWrite.prototype.deleteEmailResetTokenIfNeeded = function () { if (this.className !== '_User' || this.query === null) { // null query means create return; @@ -967,7 +974,7 @@ RestWrite.prototype.deleteEmailResetTokenIfNeeded = function() { } }; -RestWrite.prototype.destroyDuplicatedSessions = function() { +RestWrite.prototype.destroyDuplicatedSessions = function () { // Only for _Session, and at creation time if (this.className != '_Session' || this.query) { return; @@ -993,7 +1000,7 @@ RestWrite.prototype.destroyDuplicatedSessions = function() { }; // Handles any followup logic -RestWrite.prototype.handleFollowup = function() { +RestWrite.prototype.handleFollowup = function () { if ( this.storage && this.storage['clearSessions'] && @@ -1027,7 +1034,7 @@ RestWrite.prototype.handleFollowup = function() { // Handles the _Session class specialness. // Does nothing if this isn't an _Session object. -RestWrite.prototype.handleSession = function() { +RestWrite.prototype.handleSession = function () { if (this.response || this.className !== '_Session') { return; } @@ -1078,7 +1085,7 @@ RestWrite.prototype.handleSession = function() { additionalSessionData, }); - return createSession().then(results => { + return createSession().then((results) => { if (!results.response) { throw new Parse.Error( Parse.Error.INTERNAL_SERVER_ERROR, @@ -1100,7 +1107,7 @@ RestWrite.prototype.handleSession = function() { // 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() { +RestWrite.prototype.handleInstallation = function () { if (this.response || this.className !== '_Installation') { return; } @@ -1187,8 +1194,8 @@ RestWrite.prototype.handleInstallation = function() { {} ); }) - .then(results => { - results.forEach(result => { + .then((results) => { + results.forEach((result) => { if ( this.query && this.query.objectId && @@ -1294,14 +1301,16 @@ RestWrite.prototype.handleInstallation = function() { if (this.data.appIdentifier) { delQuery['appIdentifier'] = this.data.appIdentifier; } - this.config.database.destroy('_Installation', delQuery).catch(err => { - if (err.code == Parse.Error.OBJECT_NOT_FOUND) { - // no deletions were made. Can be ignored. - return; - } - // rethrow the error - throw err; - }); + this.config.database + .destroy('_Installation', delQuery) + .catch((err) => { + if (err.code == Parse.Error.OBJECT_NOT_FOUND) { + // no deletions were made. Can be ignored. + return; + } + // rethrow the error + throw err; + }); return; } } else { @@ -1318,7 +1327,7 @@ RestWrite.prototype.handleInstallation = function() { .then(() => { return deviceTokenMatches[0]['objectId']; }) - .catch(err => { + .catch((err) => { if (err.code == Parse.Error.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored return; @@ -1361,7 +1370,7 @@ RestWrite.prototype.handleInstallation = function() { } this.config.database .destroy('_Installation', delQuery) - .catch(err => { + .catch((err) => { if (err.code == Parse.Error.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored. return; @@ -1375,7 +1384,7 @@ RestWrite.prototype.handleInstallation = function() { } } }) - .then(objId => { + .then((objId) => { if (objId) { this.query = { objectId: objId }; delete this.data.objectId; @@ -1389,7 +1398,7 @@ RestWrite.prototype.handleInstallation = function() { // 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() { +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( @@ -1399,7 +1408,7 @@ RestWrite.prototype.expandFilesForExistingObjects = function() { } }; -RestWrite.prototype.runDatabaseOperation = function() { +RestWrite.prototype.runDatabaseOperation = function () { if (this.response) { return; } @@ -1465,7 +1474,7 @@ RestWrite.prototype.runDatabaseOperation = function() { { objectId: this.objectId() }, { keys: ['_password_history', '_hashed_password'] } ) - .then(results => { + .then((results) => { if (results.length != 1) { throw undefined; } @@ -1501,7 +1510,7 @@ RestWrite.prototype.runDatabaseOperation = function() { false, this.validSchemaController ) - .then(response => { + .then((response) => { response.updatedAt = this.updatedAt; this._updateResponseWithData(response, this.data); this.response = { response }; @@ -1537,7 +1546,7 @@ RestWrite.prototype.runDatabaseOperation = function() { false, this.validSchemaController ) - .catch(error => { + .catch((error) => { if ( this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE @@ -1581,7 +1590,7 @@ RestWrite.prototype.runDatabaseOperation = function() { }, { limit: 1 } ) - .then(results => { + .then((results) => { if (results.length > 0) { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, @@ -1594,7 +1603,7 @@ RestWrite.prototype.runDatabaseOperation = function() { { limit: 1 } ); }) - .then(results => { + .then((results) => { if (results.length > 0) { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, @@ -1607,7 +1616,7 @@ RestWrite.prototype.runDatabaseOperation = function() { ); }); }) - .then(response => { + .then((response) => { response.objectId = this.data.objectId; response.createdAt = this.data.createdAt; @@ -1625,7 +1634,7 @@ RestWrite.prototype.runDatabaseOperation = function() { }; // Returns nothing - doesn't wait for the trigger. -RestWrite.prototype.runAfterSaveTrigger = function() { +RestWrite.prototype.runAfterSaveTrigger = function () { if (!this.response || !this.response.response) { return; } @@ -1662,7 +1671,7 @@ RestWrite.prototype.runAfterSaveTrigger = function() { this.response.status || 200 ); - this.config.database.loadSchema().then(schemaController => { + this.config.database.loadSchema().then((schemaController) => { // Notifiy LiveQueryServer if possible const perms = schemaController.getClassLevelPermissions( updatedObject.className @@ -1685,18 +1694,18 @@ RestWrite.prototype.runAfterSaveTrigger = function() { this.config, this.context ) - .then(result => { + .then((result) => { if (result && typeof result === 'object') { this.response.response = result; } }) - .catch(function(err) { + .catch(function (err) { logger.warn('afterSave caught an error', err); }); }; // A helper to figure out what location this operation happens at. -RestWrite.prototype.location = function() { +RestWrite.prototype.location = function () { var middle = this.className === '_User' ? '/users/' : '/classes/' + this.className + '/'; return this.config.mount + middle + this.data.objectId; @@ -1704,12 +1713,12 @@ RestWrite.prototype.location = function() { // 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() { +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() { +RestWrite.prototype.sanitizedData = function () { const data = Object.keys(this.data).reduce((data, key) => { // Regexp comes from Parse.Object.prototype.validate if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) { @@ -1721,9 +1730,9 @@ RestWrite.prototype.sanitizedData = function() { }; // Returns an updated copy of the object -RestWrite.prototype.buildUpdatedObject = function(extraData) { +RestWrite.prototype.buildUpdatedObject = function (extraData) { const updatedObject = triggers.inflate(extraData, this.originalData); - Object.keys(this.data).reduce(function(data, key) { + Object.keys(this.data).reduce(function (data, key) { if (key.indexOf('.') > 0) { // subdocument key with dot notation ('x.y':v => 'x':{'y':v}) const splittedKey = key.split('.'); @@ -1743,11 +1752,11 @@ RestWrite.prototype.buildUpdatedObject = function(extraData) { return updatedObject; }; -RestWrite.prototype.cleanUserAuthData = function() { +RestWrite.prototype.cleanUserAuthData = function () { if (this.response && this.response.response && this.className === '_User') { const user = this.response.response; if (user.authData) { - Object.keys(user.authData).forEach(provider => { + Object.keys(user.authData).forEach((provider) => { if (user.authData[provider] === null) { delete user.authData[provider]; } @@ -1759,12 +1768,12 @@ RestWrite.prototype.cleanUserAuthData = function() { } }; -RestWrite.prototype._updateResponseWithData = function(response, data) { +RestWrite.prototype._updateResponseWithData = function (response, data) { if (_.isEmpty(this.storage.fieldsChangedByTrigger)) { return response; } const clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK); - this.storage.fieldsChangedByTrigger.forEach(fieldName => { + this.storage.fieldsChangedByTrigger.forEach((fieldName) => { const dataValue = data[fieldName]; if (!Object.prototype.hasOwnProperty.call(response, fieldName)) { From 60b2f1ceb37a533b898ce21611cf2ab11918977d Mon Sep 17 00:00:00 2001 From: Manuel Trezza Date: Sat, 18 Apr 2020 18:23:43 +0200 Subject: [PATCH 3/3] undo lint changes --- src/RestWrite.js | 182 +++++++++++++++++++++++------------------------ 1 file changed, 89 insertions(+), 93 deletions(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index a68bda94f4..84a67ec6c2 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -110,7 +110,7 @@ function RestWrite( // write, in order. // Returns a promise for a {response, status, location} object. // status and location are optional. -RestWrite.prototype.execute = function () { +RestWrite.prototype.execute = function() { return Promise.resolve() .then(() => { return this.getUserAndRoleACL(); @@ -136,7 +136,7 @@ RestWrite.prototype.execute = function () { .then(() => { return this.validateSchema(); }) - .then((schemaController) => { + .then(schemaController => { this.validSchemaController = schemaController; return this.setRequiredFieldsIfNeeded(); }) @@ -170,7 +170,7 @@ RestWrite.prototype.execute = function () { }; // Uses the Auth object to get the list of roles, adds the user id -RestWrite.prototype.getUserAndRoleACL = function () { +RestWrite.prototype.getUserAndRoleACL = function() { if (this.auth.isMaster) { return Promise.resolve(); } @@ -178,7 +178,7 @@ RestWrite.prototype.getUserAndRoleACL = function () { this.runOptions.acl = ['*']; if (this.auth.user) { - return this.auth.getUserRoles().then((roles) => { + return this.auth.getUserRoles().then(roles => { this.runOptions.acl = this.runOptions.acl.concat(roles, [ this.auth.user.id, ]); @@ -190,7 +190,7 @@ RestWrite.prototype.getUserAndRoleACL = function () { }; // Validates this operation against the allowClientClassCreation config. -RestWrite.prototype.validateClientClassCreation = function () { +RestWrite.prototype.validateClientClassCreation = function() { if ( this.config.allowClientClassCreation === false && !this.auth.isMaster && @@ -198,8 +198,8 @@ RestWrite.prototype.validateClientClassCreation = function () { ) { return this.config.database .loadSchema() - .then((schemaController) => schemaController.hasClass(this.className)) - .then((hasClass) => { + .then(schemaController => schemaController.hasClass(this.className)) + .then(hasClass => { if (hasClass !== true) { throw new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, @@ -215,7 +215,7 @@ RestWrite.prototype.validateClientClassCreation = function () { }; // Validates this operation against the schema. -RestWrite.prototype.validateSchema = function () { +RestWrite.prototype.validateSchema = function() { return this.config.database.validateObject( this.className, this.data, @@ -226,7 +226,7 @@ RestWrite.prototype.validateSchema = function () { // Runs any beforeSave triggers against this operation. // Any change leads to our data being mutated. -RestWrite.prototype.runBeforeSaveTrigger = function () { +RestWrite.prototype.runBeforeSaveTrigger = function() { if (this.response) { return; } @@ -279,7 +279,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { ); } // In the case that there is no permission for the operation, it throws an error - return databasePromise.then((result) => { + return databasePromise.then(result => { if (!result || result.length <= 0) { throw new Parse.Error( Parse.Error.OBJECT_NOT_FOUND, @@ -298,7 +298,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { this.context ); }) - .then((response) => { + .then(response => { if (response && response.object) { this.storage.fieldsChangedByTrigger = _.reduce( response.object, @@ -319,7 +319,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { }); }; -RestWrite.prototype.runBeforeLoginTrigger = async function (userData) { +RestWrite.prototype.runBeforeLoginTrigger = async function(userData) { // Avoid doing any setup for triggers if there is no 'beforeLogin' trigger if ( !triggers.triggerExists( @@ -346,11 +346,11 @@ RestWrite.prototype.runBeforeLoginTrigger = async function (userData) { ); }; -RestWrite.prototype.setRequiredFieldsIfNeeded = function () { +RestWrite.prototype.setRequiredFieldsIfNeeded = function() { if (this.data) { - return this.validSchemaController.getAllClasses().then((allClasses) => { + return this.validSchemaController.getAllClasses().then(allClasses => { const schema = allClasses.find( - (oneClass) => oneClass.className === this.className + oneClass => oneClass.className === this.className ); const setRequiredFieldIfNeeded = (fieldName, setDefault) => { if ( @@ -399,12 +399,12 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () { ); } if (schema) { - Object.keys(schema.fields).forEach((fieldName) => { + Object.keys(schema.fields).forEach(fieldName => { setRequiredFieldIfNeeded(fieldName, true); }); } } else if (schema) { - Object.keys(this.data).forEach((fieldName) => { + Object.keys(this.data).forEach(fieldName => { setRequiredFieldIfNeeded(fieldName, false); }); } @@ -416,7 +416,7 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () { // 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 () { +RestWrite.prototype.validateAuthData = function() { if (this.className !== '_User') { return; } @@ -477,8 +477,8 @@ RestWrite.prototype.validateAuthData = function () { ); }; -RestWrite.prototype.handleAuthDataValidation = function (authData) { - const validations = Object.keys(authData).map((provider) => { +RestWrite.prototype.handleAuthDataValidation = function(authData) { + const validations = Object.keys(authData).map(provider => { if (authData[provider] === null) { return Promise.resolve(); } @@ -496,7 +496,7 @@ RestWrite.prototype.handleAuthDataValidation = function (authData) { return Promise.all(validations); }; -RestWrite.prototype.findUsersWithAuthData = function (authData) { +RestWrite.prototype.findUsersWithAuthData = function(authData) { const providers = Object.keys(authData); const query = providers .reduce((memo, provider) => { @@ -509,7 +509,7 @@ RestWrite.prototype.findUsersWithAuthData = function (authData) { memo.push(query); return memo; }, []) - .filter((q) => { + .filter(q => { return typeof q !== 'undefined'; }); @@ -521,11 +521,11 @@ RestWrite.prototype.findUsersWithAuthData = function (authData) { return findPromise; }; -RestWrite.prototype.filteredObjectsByACL = function (objects) { +RestWrite.prototype.filteredObjectsByACL = function(objects) { if (this.auth.isMaster) { return objects; } - return objects.filter((object) => { + return objects.filter(object => { if (!object.ACL) { return true; // legacy users that have no ACL field on them } @@ -534,9 +534,9 @@ RestWrite.prototype.filteredObjectsByACL = function (objects) { }); }; -RestWrite.prototype.handleAuthData = function (authData) { +RestWrite.prototype.handleAuthData = function(authData) { let results; - return this.findUsersWithAuthData(authData).then(async (r) => { + return this.findUsersWithAuthData(authData).then(async r => { results = this.filteredObjectsByACL(r); if (results.length == 1) { @@ -544,7 +544,7 @@ RestWrite.prototype.handleAuthData = function (authData) { const userResult = results[0]; const mutatedAuthData = {}; - Object.keys(authData).forEach((provider) => { + Object.keys(authData).forEach(provider => { const providerData = authData[provider]; const userAuthData = userResult.authData[provider]; if (!_.isEqual(providerData, userAuthData)) { @@ -594,7 +594,7 @@ RestWrite.prototype.handleAuthData = function (authData) { // If we're not logging in, but just updating the current user, we can safely skip that part if (this.response) { // Assign the new authData in the response - Object.keys(mutatedAuthData).forEach((provider) => { + Object.keys(mutatedAuthData).forEach(provider => { this.response.response.authData[provider] = mutatedAuthData[provider]; }); @@ -638,7 +638,7 @@ RestWrite.prototype.handleAuthData = function (authData) { }; // The non-third-party parts of User transformation -RestWrite.prototype.transformUser = function () { +RestWrite.prototype.transformUser = function() { var promise = Promise.resolve(); if (this.className !== '_User') { @@ -662,8 +662,8 @@ RestWrite.prototype.transformUser = function () { }, }) .execute() - .then((results) => { - results.results.forEach((session) => + .then(results => { + results.results.forEach(session => this.config.cacheController.user.del(session.sessionToken) ); }); @@ -686,12 +686,10 @@ RestWrite.prototype.transformUser = function () { } return this._validatePasswordPolicy().then(() => { - return passwordCrypto - .hash(this.data.password) - .then((hashedPassword) => { - this.data._hashed_password = hashedPassword; - delete this.data.password; - }); + return passwordCrypto.hash(this.data.password).then(hashedPassword => { + this.data._hashed_password = hashedPassword; + delete this.data.password; + }); }); }) .then(() => { @@ -702,7 +700,7 @@ RestWrite.prototype.transformUser = function () { }); }; -RestWrite.prototype._validateUserName = function () { +RestWrite.prototype._validateUserName = function() { // Check for username uniqueness if (!this.data.username) { if (!this.query) { @@ -729,7 +727,7 @@ RestWrite.prototype._validateUserName = function () { {}, this.validSchemaController ) - .then((results) => { + .then(results => { if (results.length > 0) { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, @@ -752,7 +750,7 @@ RestWrite.prototype._validateUserName = function () { Given that this lookup should be a relatively low use case and that the case sensitive unique index will be used by the db for the query, this is an adequate solution. */ -RestWrite.prototype._validateEmail = function () { +RestWrite.prototype._validateEmail = function() { if (!this.data.email || this.data.email.__op === 'Delete') { return Promise.resolve(); } @@ -777,7 +775,7 @@ RestWrite.prototype._validateEmail = function () { {}, this.validSchemaController ) - .then((results) => { + .then(results => { if (results.length > 0) { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, @@ -797,14 +795,14 @@ RestWrite.prototype._validateEmail = function () { }); }; -RestWrite.prototype._validatePasswordPolicy = function () { +RestWrite.prototype._validatePasswordPolicy = function() { if (!this.config.passwordPolicy) return Promise.resolve(); return this._validatePasswordRequirements().then(() => { return this._validatePasswordHistory(); }); }; -RestWrite.prototype._validatePasswordRequirements = function () { +RestWrite.prototype._validatePasswordRequirements = function() { // check if the password conforms to the defined password policy if configured // If we specified a custom error in our configuration use it. // Example: "Passwords must include a Capital Letter, Lowercase Letter, and a number." @@ -842,7 +840,7 @@ RestWrite.prototype._validatePasswordRequirements = function () { // retrieve the User object using objectId during password reset return this.config.database .find('_User', { objectId: this.objectId() }) - .then((results) => { + .then(results => { if (results.length != 1) { throw undefined; } @@ -860,7 +858,7 @@ RestWrite.prototype._validatePasswordRequirements = function () { return Promise.resolve(); }; -RestWrite.prototype._validatePasswordHistory = function () { +RestWrite.prototype._validatePasswordHistory = function() { // check whether password is repeating from specified history if (this.query && this.config.passwordPolicy.maxPasswordHistory) { return this.config.database @@ -869,7 +867,7 @@ RestWrite.prototype._validatePasswordHistory = function () { { objectId: this.objectId() }, { keys: ['_password_history', '_hashed_password'] } ) - .then((results) => { + .then(results => { if (results.length != 1) { throw undefined; } @@ -883,8 +881,8 @@ RestWrite.prototype._validatePasswordHistory = function () { oldPasswords.push(user.password); const newPassword = this.data.password; // compare the new password hash with all old password hashes - const promises = oldPasswords.map(function (hash) { - return passwordCrypto.compare(newPassword, hash).then((result) => { + const promises = oldPasswords.map(function(hash) { + return passwordCrypto.compare(newPassword, hash).then(result => { if (result) // reject if there is a match return Promise.reject('REPEAT_PASSWORD'); @@ -896,7 +894,7 @@ RestWrite.prototype._validatePasswordHistory = function () { .then(() => { return Promise.resolve(); }) - .catch((err) => { + .catch(err => { if (err === 'REPEAT_PASSWORD') // a match was found return Promise.reject( @@ -912,7 +910,7 @@ RestWrite.prototype._validatePasswordHistory = function () { return Promise.resolve(); }; -RestWrite.prototype.createSessionTokenIfNeeded = function () { +RestWrite.prototype.createSessionTokenIfNeeded = function() { if (this.className !== '_User') { return; } @@ -935,7 +933,7 @@ RestWrite.prototype.createSessionTokenIfNeeded = function () { return this.createSessionToken(); }; -RestWrite.prototype.createSessionToken = async function () { +RestWrite.prototype.createSessionToken = async function() { // cloud installationId from Cloud Code, // never create session tokens from there. if (this.auth.installationId && this.auth.installationId === 'cloud') { @@ -959,7 +957,7 @@ RestWrite.prototype.createSessionToken = async function () { }; // Delete email reset tokens if user is changing password or email. -RestWrite.prototype.deleteEmailResetTokenIfNeeded = function () { +RestWrite.prototype.deleteEmailResetTokenIfNeeded = function() { if (this.className !== '_User' || this.query === null) { // null query means create return; @@ -974,7 +972,7 @@ RestWrite.prototype.deleteEmailResetTokenIfNeeded = function () { } }; -RestWrite.prototype.destroyDuplicatedSessions = function () { +RestWrite.prototype.destroyDuplicatedSessions = function() { // Only for _Session, and at creation time if (this.className != '_Session' || this.query) { return; @@ -1000,7 +998,7 @@ RestWrite.prototype.destroyDuplicatedSessions = function () { }; // Handles any followup logic -RestWrite.prototype.handleFollowup = function () { +RestWrite.prototype.handleFollowup = function() { if ( this.storage && this.storage['clearSessions'] && @@ -1034,7 +1032,7 @@ RestWrite.prototype.handleFollowup = function () { // Handles the _Session class specialness. // Does nothing if this isn't an _Session object. -RestWrite.prototype.handleSession = function () { +RestWrite.prototype.handleSession = function() { if (this.response || this.className !== '_Session') { return; } @@ -1085,7 +1083,7 @@ RestWrite.prototype.handleSession = function () { additionalSessionData, }); - return createSession().then((results) => { + return createSession().then(results => { if (!results.response) { throw new Parse.Error( Parse.Error.INTERNAL_SERVER_ERROR, @@ -1107,7 +1105,7 @@ RestWrite.prototype.handleSession = function () { // 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 () { +RestWrite.prototype.handleInstallation = function() { if (this.response || this.className !== '_Installation') { return; } @@ -1194,8 +1192,8 @@ RestWrite.prototype.handleInstallation = function () { {} ); }) - .then((results) => { - results.forEach((result) => { + .then(results => { + results.forEach(result => { if ( this.query && this.query.objectId && @@ -1301,16 +1299,14 @@ RestWrite.prototype.handleInstallation = function () { if (this.data.appIdentifier) { delQuery['appIdentifier'] = this.data.appIdentifier; } - this.config.database - .destroy('_Installation', delQuery) - .catch((err) => { - if (err.code == Parse.Error.OBJECT_NOT_FOUND) { - // no deletions were made. Can be ignored. - return; - } - // rethrow the error - throw err; - }); + this.config.database.destroy('_Installation', delQuery).catch(err => { + if (err.code == Parse.Error.OBJECT_NOT_FOUND) { + // no deletions were made. Can be ignored. + return; + } + // rethrow the error + throw err; + }); return; } } else { @@ -1327,7 +1323,7 @@ RestWrite.prototype.handleInstallation = function () { .then(() => { return deviceTokenMatches[0]['objectId']; }) - .catch((err) => { + .catch(err => { if (err.code == Parse.Error.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored return; @@ -1370,7 +1366,7 @@ RestWrite.prototype.handleInstallation = function () { } this.config.database .destroy('_Installation', delQuery) - .catch((err) => { + .catch(err => { if (err.code == Parse.Error.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored. return; @@ -1384,7 +1380,7 @@ RestWrite.prototype.handleInstallation = function () { } } }) - .then((objId) => { + .then(objId => { if (objId) { this.query = { objectId: objId }; delete this.data.objectId; @@ -1398,7 +1394,7 @@ RestWrite.prototype.handleInstallation = function () { // 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 () { +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( @@ -1408,7 +1404,7 @@ RestWrite.prototype.expandFilesForExistingObjects = function () { } }; -RestWrite.prototype.runDatabaseOperation = function () { +RestWrite.prototype.runDatabaseOperation = function() { if (this.response) { return; } @@ -1474,7 +1470,7 @@ RestWrite.prototype.runDatabaseOperation = function () { { objectId: this.objectId() }, { keys: ['_password_history', '_hashed_password'] } ) - .then((results) => { + .then(results => { if (results.length != 1) { throw undefined; } @@ -1510,7 +1506,7 @@ RestWrite.prototype.runDatabaseOperation = function () { false, this.validSchemaController ) - .then((response) => { + .then(response => { response.updatedAt = this.updatedAt; this._updateResponseWithData(response, this.data); this.response = { response }; @@ -1546,7 +1542,7 @@ RestWrite.prototype.runDatabaseOperation = function () { false, this.validSchemaController ) - .catch((error) => { + .catch(error => { if ( this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE @@ -1590,7 +1586,7 @@ RestWrite.prototype.runDatabaseOperation = function () { }, { limit: 1 } ) - .then((results) => { + .then(results => { if (results.length > 0) { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, @@ -1603,7 +1599,7 @@ RestWrite.prototype.runDatabaseOperation = function () { { limit: 1 } ); }) - .then((results) => { + .then(results => { if (results.length > 0) { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, @@ -1616,7 +1612,7 @@ RestWrite.prototype.runDatabaseOperation = function () { ); }); }) - .then((response) => { + .then(response => { response.objectId = this.data.objectId; response.createdAt = this.data.createdAt; @@ -1634,7 +1630,7 @@ RestWrite.prototype.runDatabaseOperation = function () { }; // Returns nothing - doesn't wait for the trigger. -RestWrite.prototype.runAfterSaveTrigger = function () { +RestWrite.prototype.runAfterSaveTrigger = function() { if (!this.response || !this.response.response) { return; } @@ -1671,7 +1667,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () { this.response.status || 200 ); - this.config.database.loadSchema().then((schemaController) => { + this.config.database.loadSchema().then(schemaController => { // Notifiy LiveQueryServer if possible const perms = schemaController.getClassLevelPermissions( updatedObject.className @@ -1694,18 +1690,18 @@ RestWrite.prototype.runAfterSaveTrigger = function () { this.config, this.context ) - .then((result) => { + .then(result => { if (result && typeof result === 'object') { this.response.response = result; } }) - .catch(function (err) { + .catch(function(err) { logger.warn('afterSave caught an error', err); }); }; // A helper to figure out what location this operation happens at. -RestWrite.prototype.location = function () { +RestWrite.prototype.location = function() { var middle = this.className === '_User' ? '/users/' : '/classes/' + this.className + '/'; return this.config.mount + middle + this.data.objectId; @@ -1713,12 +1709,12 @@ RestWrite.prototype.location = function () { // 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 () { +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 () { +RestWrite.prototype.sanitizedData = function() { const data = Object.keys(this.data).reduce((data, key) => { // Regexp comes from Parse.Object.prototype.validate if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) { @@ -1730,9 +1726,9 @@ RestWrite.prototype.sanitizedData = function () { }; // Returns an updated copy of the object -RestWrite.prototype.buildUpdatedObject = function (extraData) { +RestWrite.prototype.buildUpdatedObject = function(extraData) { const updatedObject = triggers.inflate(extraData, this.originalData); - Object.keys(this.data).reduce(function (data, key) { + Object.keys(this.data).reduce(function(data, key) { if (key.indexOf('.') > 0) { // subdocument key with dot notation ('x.y':v => 'x':{'y':v}) const splittedKey = key.split('.'); @@ -1752,11 +1748,11 @@ RestWrite.prototype.buildUpdatedObject = function (extraData) { return updatedObject; }; -RestWrite.prototype.cleanUserAuthData = function () { +RestWrite.prototype.cleanUserAuthData = function() { if (this.response && this.response.response && this.className === '_User') { const user = this.response.response; if (user.authData) { - Object.keys(user.authData).forEach((provider) => { + Object.keys(user.authData).forEach(provider => { if (user.authData[provider] === null) { delete user.authData[provider]; } @@ -1768,12 +1764,12 @@ RestWrite.prototype.cleanUserAuthData = function () { } }; -RestWrite.prototype._updateResponseWithData = function (response, data) { +RestWrite.prototype._updateResponseWithData = function(response, data) { if (_.isEmpty(this.storage.fieldsChangedByTrigger)) { return response; } const clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK); - this.storage.fieldsChangedByTrigger.forEach((fieldName) => { + this.storage.fieldsChangedByTrigger.forEach(fieldName => { const dataValue = data[fieldName]; if (!Object.prototype.hasOwnProperty.call(response, fieldName)) {