BVB Source Codes

parse-server Show RestWrite.js Source code

Return Download parse-server: download RestWrite.js Source code - Download parse-server Source code - Type:.js
  1. // A RestWrite encapsulates everything we need to run an operation
  2. // that writes to the database.
  3. // This could be either a "create" or an "update".
  4.  
  5. var SchemaController = require('./Controllers/SchemaController');
  6. var deepcopy = require('deepcopy');
  7.  
  8. var Auth = require('./Auth');
  9. var cryptoUtils = require('./cryptoUtils');
  10. var passwordCrypto = require('./password');
  11. var Parse = require('parse/node');
  12. var triggers = require('./triggers');
  13. var ClientSDK = require('./ClientSDK');
  14. import RestQuery from './RestQuery';
  15. import _         from 'lodash';
  16.  
  17. // query and data are both provided in REST API format. So data
  18. // types are encoded by plain old objects.
  19. // If query is null, this is a "create" and the data in data should be
  20. // created.
  21. // Otherwise this is an "update" - the object matching the query
  22. // should get updated with data.
  23. // RestWrite will handle objectId, createdAt, and updatedAt for
  24. // everything. It also knows to use triggers and special modifications
  25. // for the _User class.
  26. function RestWrite(config, auth, className, query, data, originalData, clientSDK) {
  27.   this.config = config;
  28.   this.auth = auth;
  29.   this.className = className;
  30.   this.clientSDK = clientSDK;
  31.   this.storage = {};
  32.   this.runOptions = {};
  33.   if (!query && data.objectId) {
  34.     throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
  35.   }
  36.  
  37.   // When the operation is complete, this.response may have several
  38.   // fields.
  39.   // response: the actual data to be returned
  40.   // status: the http status code. if not present, treated like a 200
  41.   // location: the location header. if not present, no location header
  42.   this.response = null;
  43.  
  44.   // Processing this operation may mutate our data, so we operate on a
  45.   // copy
  46.   this.query = deepcopy(query);
  47.   this.data = deepcopy(data);
  48.   // We never change originalData, so we do not need a deep copy
  49.   this.originalData = originalData;
  50.  
  51.   // The timestamp we'll use for this whole operation
  52.   this.updatedAt = Parse._encode(new Date()).iso;
  53. }
  54.  
  55. // A convenient method to perform all the steps of processing the
  56. // write, in order.
  57. // Returns a promise for a {response, status, location} object.
  58. // status and location are optional.
  59. RestWrite.prototype.execute = function() {
  60.   return Promise.resolve().then(() => {
  61.     return this.getUserAndRoleACL();
  62.   }).then(() => {
  63.     return this.validateClientClassCreation();
  64.   }).then(() => {
  65.     return this.handleInstallation();
  66.   }).then(() => {
  67.     return this.handleSession();
  68.   }).then(() => {
  69.     return this.validateAuthData();
  70.   }).then(() => {
  71.     return this.runBeforeTrigger();
  72.   }).then(() => {
  73.     return this.validateSchema();
  74.   }).then(() => {
  75.     return this.setRequiredFieldsIfNeeded();
  76.   }).then(() => {
  77.     return this.transformUser();
  78.   }).then(() => {
  79.     return this.expandFilesForExistingObjects();
  80.   }).then(() => {
  81.     return this.runDatabaseOperation();
  82.   }).then(() => {
  83.     return this.createSessionTokenIfNeeded();
  84.   }).then(() => {
  85.     return this.handleFollowup();
  86.   }).then(() => {
  87.     return this.runAfterTrigger();
  88.   }).then(() => {
  89.     return this.cleanUserAuthData();
  90.   }).then(() => {
  91.     return this.response;
  92.   })
  93. };
  94.  
  95. // Uses the Auth object to get the list of roles, adds the user id
  96. RestWrite.prototype.getUserAndRoleACL = function() {
  97.   if (this.auth.isMaster) {
  98.     return Promise.resolve();
  99.   }
  100.  
  101.   this.runOptions.acl = ['*'];
  102.  
  103.   if (this.auth.user) {
  104.     return this.auth.getUserRoles().then((roles) => {
  105.       roles.push(this.auth.user.id);
  106.       this.runOptions.acl = this.runOptions.acl.concat(roles);
  107.       return;
  108.     });
  109.   } else {
  110.     return Promise.resolve();
  111.   }
  112. };
  113.  
  114. // Validates this operation against the allowClientClassCreation config.
  115. RestWrite.prototype.validateClientClassCreation = function() {
  116.   if (this.config.allowClientClassCreation === false && !this.auth.isMaster
  117.       && SchemaController.systemClasses.indexOf(this.className) === -1) {
  118.     return this.config.database.loadSchema()
  119.       .then(schemaController => schemaController.hasClass(this.className))
  120.       .then(hasClass => {
  121.         if (hasClass !== true) {
  122.           throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
  123.                                 'This user is not allowed to access ' +
  124.                                 'non-existent class: ' + this.className);
  125.         }
  126.       });
  127.   } else {
  128.     return Promise.resolve();
  129.   }
  130. };
  131.  
  132. // Validates this operation against the schema.
  133. RestWrite.prototype.validateSchema = function() {
  134.   return this.config.database.validateObject(this.className, this.data, this.query, this.runOptions);
  135. };
  136.  
  137. // Runs any beforeSave triggers against this operation.
  138. // Any change leads to our data being mutated.
  139. RestWrite.prototype.runBeforeTrigger = function() {
  140.   if (this.response) {
  141.     return;
  142.   }
  143.  
  144.   // Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class.
  145.   if (!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)) {
  146.     return Promise.resolve();
  147.   }
  148.  
  149.   // Cloud code gets a bit of extra data for its objects
  150.   var extraData = {className: this.className};
  151.   if (this.query && this.query.objectId) {
  152.     extraData.objectId = this.query.objectId;
  153.   }
  154.  
  155.   let originalObject = null;
  156.   const updatedObject = triggers.inflate(extraData, this.originalData);
  157.   if (this.query && this.query.objectId) {
  158.     // This is an update for existing object.
  159.     originalObject = triggers.inflate(extraData, this.originalData);
  160.   }
  161.   updatedObject.set(this.sanitizedData());
  162.  
  163.   return Promise.resolve().then(() => {
  164.     return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config);
  165.   }).then((response) => {
  166.     if (response && response.object) {
  167.       this.storage.fieldsChangedByTrigger = _.reduce(response.object, (result, value, key) => {
  168.         if (!_.isEqual(this.data[key], value)) {
  169.           result.push(key);
  170.         }
  171.         return result;
  172.       }, []);
  173.       this.data = response.object;
  174.       // We should delete the objectId for an update write
  175.       if (this.query && this.query.objectId) {
  176.         delete this.data.objectId
  177.       }
  178.     }
  179.   });
  180. };
  181.  
  182. RestWrite.prototype.setRequiredFieldsIfNeeded = function() {
  183.   if (this.data) {
  184.     // Add default fields
  185.     this.data.updatedAt = this.updatedAt;
  186.     if (!this.query) {
  187.       this.data.createdAt = this.updatedAt;
  188.  
  189.       // Only assign new objectId if we are creating new object
  190.       if (!this.data.objectId) {
  191.         this.data.objectId = cryptoUtils.newObjectId();
  192.       }
  193.     }
  194.   }
  195.   return Promise.resolve();
  196. };
  197.  
  198. // Transforms auth data for a user object.
  199. // Does nothing if this isn't a user object.
  200. // Returns a promise for when we're done if it can't finish this tick.
  201. RestWrite.prototype.validateAuthData = function() {
  202.   if (this.className !== '_User') {
  203.     return;
  204.   }
  205.  
  206.   if (!this.query && !this.data.authData) {
  207.     if (typeof this.data.username !== 'string' || _.isEmpty(this.data.username)) {
  208.       throw new Parse.Error(Parse.Error.USERNAME_MISSING,
  209.                             'bad or missing username');
  210.     }
  211.     if (typeof this.data.password !== 'string' || _.isEmpty(this.data.password)) {
  212.       throw new Parse.Error(Parse.Error.PASSWORD_MISSING,
  213.                             'password is required');
  214.     }
  215.   }
  216.  
  217.   if (!this.data.authData || !Object.keys(this.data.authData).length) {
  218.     return;
  219.   }
  220.  
  221.   var authData = this.data.authData;
  222.   var providers = Object.keys(authData);
  223.   if (providers.length > 0) {
  224.     const canHandleAuthData = providers.reduce((canHandle, provider) => {
  225.       var providerAuthData = authData[provider];
  226.       var hasToken = (providerAuthData && providerAuthData.id);
  227.       return canHandle && (hasToken || providerAuthData == null);
  228.     }, true);
  229.     if (canHandleAuthData) {
  230.       return this.handleAuthData(authData);
  231.     }
  232.   }
  233.   throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
  234.                           'This authentication method is unsupported.');
  235. };
  236.  
  237. RestWrite.prototype.handleAuthDataValidation = function(authData) {
  238.   const validations = Object.keys(authData).map((provider) => {
  239.     if (authData[provider] === null) {
  240.       return Promise.resolve();
  241.     }
  242.     const validateAuthData = this.config.authDataManager.getValidatorForProvider(provider);
  243.     if (!validateAuthData) {
  244.       throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
  245.                             'This authentication method is unsupported.');
  246.     }
  247.     return validateAuthData(authData[provider]);
  248.   });
  249.   return Promise.all(validations);
  250. }
  251.  
  252. RestWrite.prototype.findUsersWithAuthData = function(authData) {
  253.   const providers = Object.keys(authData);
  254.   const query = providers.reduce((memo, provider) => {
  255.     if (!authData[provider]) {
  256.       return memo;
  257.     }
  258.     const queryKey = `authData.${provider}.id`;
  259.     const query = {};
  260.     query[queryKey] = authData[provider].id;
  261.     memo.push(query);
  262.     return memo;
  263.   }, []).filter((q) => {
  264.     return typeof q !== 'undefined';
  265.   });
  266.  
  267.   let findPromise = Promise.resolve([]);
  268.   if (query.length > 0) {
  269.     findPromise = this.config.database.find(
  270.         this.className,
  271.         {'$or': query}, {})
  272.   }
  273.  
  274.   return findPromise;
  275. }
  276.  
  277.  
  278. RestWrite.prototype.handleAuthData = function(authData) {
  279.   let results;
  280.   return this.findUsersWithAuthData(authData).then((r) => {
  281.     results = r;
  282.     if (results.length > 1) {
  283.       // More than 1 user with the passed id's
  284.       throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
  285.                               'this auth is already used');
  286.     }
  287.  
  288.     this.storage['authProvider'] = Object.keys(authData).join(',');
  289.  
  290.     if (results.length > 0) {
  291.       const userResult = results[0];
  292.       const mutatedAuthData = {};
  293.       Object.keys(authData).forEach((provider) => {
  294.         const providerData = authData[provider];
  295.         const userAuthData = userResult.authData[provider];
  296.         if (!_.isEqual(providerData, userAuthData)) {
  297.           mutatedAuthData[provider] = providerData;
  298.         }
  299.       });
  300.       const hasMutatedAuthData = Object.keys(mutatedAuthData).length !== 0;
  301.       if (!this.query) {
  302.         // Login with auth data
  303.         delete results[0].password;
  304.  
  305.         // need to set the objectId first otherwise location has trailing undefined
  306.         this.data.objectId = userResult.objectId;
  307.  
  308.         // Determine if authData was updated
  309.  
  310.         this.response = {
  311.           response: userResult,
  312.           location: this.location()
  313.         };
  314.  
  315.         // If we didn't change the auth data, just keep going
  316.         if (!hasMutatedAuthData) {
  317.           return;
  318.         }
  319.         // We have authData that is updated on login
  320.         // that can happen when token are refreshed,
  321.         // We should update the token and let the user in
  322.         // We should only check the mutated keys
  323.         return this.handleAuthDataValidation(mutatedAuthData).then(() => {
  324.           // Assign the new authData in the response
  325.           Object.keys(mutatedAuthData).forEach((provider) => {
  326.             this.response.response.authData[provider] = mutatedAuthData[provider];
  327.           });
  328.           // Run the DB update directly, as 'master'
  329.           // Just update the authData part
  330.           return this.config.database.update(this.className, {objectId: this.data.objectId}, {authData: mutatedAuthData}, {});
  331.         });
  332.       } else if (this.query && this.query.objectId) {
  333.         // Trying to update auth data but users
  334.         // are different
  335.         if (userResult.objectId !== this.query.objectId) {
  336.           throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
  337.                               'this auth is already used');
  338.         }
  339.         // No auth data was mutated, just keep going
  340.         if (!hasMutatedAuthData) {
  341.           return;
  342.         }
  343.       }
  344.     }
  345.     return this.handleAuthDataValidation(authData);
  346.   });
  347. }
  348.  
  349.  
  350. // The non-third-party parts of User transformation
  351. RestWrite.prototype.transformUser = function() {
  352.   var promise = Promise.resolve();
  353.  
  354.   if (this.className !== '_User') {
  355.     return promise;
  356.   }
  357.  
  358.   if (!this.auth.isMaster && "emailVerified" in this.data) {
  359.     const error = `Clients aren't allowed to manually update email verification.`
  360.    throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
  361.  }
  362.  
  363.  // Do not cleanup session if objectId is not set
  364.  if (this.query && this.objectId()) {
  365.    // If we're updating a _User object, we need to clear out the cache for that user. Find all their
  366.     // session tokens, and remove them from the cache.
  367.     promise = new RestQuery(this.config, Auth.master(this.config), '_Session', {
  368.       user: {
  369.         __type: "Pointer",
  370.         className: "_User",
  371.         objectId: this.objectId(),
  372.       }
  373.     }).execute()
  374.       .then(results => {
  375.         results.results.forEach(session => this.config.cacheController.user.del(session.sessionToken));
  376.       });
  377.   }
  378.  
  379.   return promise.then(() => {
  380.     // Transform the password
  381.     if (this.data.password === undefined) { // ignore only if undefined. should proceed if empty ('')
  382.       return Promise.resolve();
  383.     }
  384.  
  385.     if (this.query) {
  386.       this.storage['clearSessions'] = true;
  387.       // Generate a new session only if the user requested
  388.       if (!this.auth.isMaster) {
  389.         this.storage['generateNewSession'] = true;
  390.       }
  391.     }
  392.  
  393.     return this._validatePasswordPolicy().then(() => {
  394.       return passwordCrypto.hash(this.data.password).then((hashedPassword) => {
  395.         this.data._hashed_password = hashedPassword;
  396.         delete this.data.password;
  397.       });
  398.     });
  399.  
  400.   }).then(() => {
  401.     return this._validateUserName();
  402.   }).then(() => {
  403.     return this._validateEmail();
  404.   });
  405. };
  406.  
  407. RestWrite.prototype._validateUserName = function () {
  408.   // Check for username uniqueness
  409.   if (!this.data.username) {
  410.     if (!this.query) {
  411.       this.data.username = cryptoUtils.randomString(25);
  412.       this.responseShouldHaveUsername = true;
  413.     }
  414.     return Promise.resolve();
  415.   }
  416.   // We need to a find to check for duplicate username in case they are missing the unique index on usernames
  417.   // TODO: Check if there is a unique index, and if so, skip this query.
  418.   return this.config.database.find(
  419.     this.className,
  420.     {username: this.data.username, objectId: {'$ne': this.objectId()}},
  421.     {limit: 1}
  422.   ).then(results => {
  423.     if (results.length > 0) {
  424.       throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.');
  425.     }
  426.     return;
  427.   });
  428. };
  429.  
  430. RestWrite.prototype._validateEmail = function() {
  431.   if (!this.data.email || this.data.email.__op === 'Delete') {
  432.     return Promise.resolve();
  433.   }
  434.   // Validate basic email address format
  435.   if (!this.data.email.match(/^.+@.+$/)) {
  436.     return Promise.reject(new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.'));
  437.   }
  438.   // Same problem for email as above for username
  439.   return this.config.database.find(
  440.     this.className,
  441.     {email: this.data.email, objectId: {'$ne': this.objectId()}},
  442.     {limit: 1}
  443.   ).then(results => {
  444.     if (results.length > 0) {
  445.       throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.');
  446.     }
  447.     if (
  448.       !this.data.authData ||
  449.       !Object.keys(this.data.authData).length ||
  450.       Object.keys(this.data.authData).length === 1 && Object.keys(this.data.authData)[0] === 'anonymous'
  451.     ) {
  452.       // We updated the email, send a new validation
  453.       this.storage['sendVerificationEmail'] = true;
  454.       this.config.userController.setEmailVerifyToken(this.data);
  455.     }
  456.   });
  457. };
  458.  
  459. RestWrite.prototype._validatePasswordPolicy = function() {
  460.   if (!this.config.passwordPolicy)
  461.     return Promise.resolve();
  462.   return this._validatePasswordRequirements().then(() => {
  463.     return this._validatePasswordHistory();
  464.   });
  465. };
  466.  
  467.  
  468. RestWrite.prototype._validatePasswordRequirements = function() {
  469.   // check if the password conforms to the defined password policy if configured
  470.   const policyError = 'Password does not meet the Password Policy requirements.';
  471.  
  472.   // check whether the password meets the password strength requirements
  473.   if (this.config.passwordPolicy.patternValidator && !this.config.passwordPolicy.patternValidator(this.data.password) ||
  474.     this.config.passwordPolicy.validatorCallback && !this.config.passwordPolicy.validatorCallback(this.data.password)) {
  475.     return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, policyError));
  476.   }
  477.  
  478.   // check whether password contain username
  479.   if (this.config.passwordPolicy.doNotAllowUsername === true) {
  480.     if (this.data.username) { // username is not passed during password reset
  481.       if (this.data.password.indexOf(this.data.username) >= 0)
  482.         return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, policyError));
  483.     } else { // retrieve the User object using objectId during password reset
  484.       return this.config.database.find('_User', {objectId: this.objectId()})
  485.         .then(results => {
  486.           if (results.length != 1) {
  487.             throw undefined;
  488.           }
  489.           if (this.data.password.indexOf(results[0].username) >= 0)
  490.             return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, policyError));
  491.           return Promise.resolve();
  492.         });
  493.     }
  494.   }
  495.   return Promise.resolve();
  496. };
  497.  
  498. RestWrite.prototype._validatePasswordHistory = function() {
  499.   // check whether password is repeating from specified history
  500.   if (this.query && this.config.passwordPolicy.maxPasswordHistory) {
  501.     return this.config.database.find('_User', {objectId: this.objectId()}, {keys: ["_password_history", "_hashed_password"]})
  502.       .then(results => {
  503.         if (results.length != 1) {
  504.           throw undefined;
  505.         }
  506.         const user = results[0];
  507.         let oldPasswords = [];
  508.         if (user._password_history)
  509.           oldPasswords = _.take(user._password_history, this.config.passwordPolicy.maxPasswordHistory - 1);
  510.         oldPasswords.push(user.password);
  511.         const newPassword = this.data.password;
  512.         // compare the new password hash with all old password hashes
  513.         const promises = oldPasswords.map(function (hash) {
  514.           return passwordCrypto.compare(newPassword, hash).then((result) => {
  515.             if (result) // reject if there is a match
  516.               return Promise.reject("REPEAT_PASSWORD");
  517.             return Promise.resolve();
  518.           })
  519.         });
  520.         // wait for all comparisons to complete
  521.         return Promise.all(promises).then(() => {
  522.           return Promise.resolve();
  523.         }).catch(err => {
  524.           if (err === "REPEAT_PASSWORD") // a match was found
  525.             return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, `New password should not be the same as last ${this.config.passwordPolicy.maxPasswordHistory} passwords.`));
  526.           throw err;
  527.         });
  528.       });
  529.   }
  530.   return Promise.resolve();
  531. };
  532.  
  533. RestWrite.prototype.createSessionTokenIfNeeded = function() {
  534.   if (this.className !== '_User') {
  535.     return;
  536.   }
  537.   if (this.query) {
  538.     return;
  539.   }
  540.   return this.createSessionToken();
  541. }
  542.  
  543. RestWrite.prototype.createSessionToken = function() {
  544.   // cloud installationId from Cloud Code,
  545.   // never create session tokens from there.
  546.   if (this.auth.installationId && this.auth.installationId === 'cloud') {
  547.     return;
  548.   }
  549.   var token = 'r:' + cryptoUtils.newToken();
  550.  
  551.   var expiresAt = this.config.generateSessionExpiresAt();
  552.   var sessionData = {
  553.     sessionToken: token,
  554.     user: {
  555.       __type: 'Pointer',
  556.       className: '_User',
  557.       objectId: this.objectId()
  558.     },
  559.     createdWith: {
  560.       'action': 'signup',
  561.       'authProvider': this.storage['authProvider'] || 'password'
  562.     },
  563.     restricted: false,
  564.     installationId: this.auth.installationId,
  565.     expiresAt: Parse._encode(expiresAt)
  566.   };
  567.   if (this.response && this.response.response) {
  568.     this.response.response.sessionToken = token;
  569.   }
  570.   var create = new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData);
  571.   return create.execute();
  572. }
  573.  
  574. // Handles any followup logic
  575. RestWrite.prototype.handleFollowup = function() {
  576.   if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) {
  577.     var sessionQuery = {
  578.       user: {
  579.         __type: 'Pointer',
  580.         className: '_User',
  581.         objectId: this.objectId()
  582.       }
  583.     };
  584.     delete this.storage['clearSessions'];
  585.     return this.config.database.destroy('_Session', sessionQuery)
  586.     .then(this.handleFollowup.bind(this));
  587.   }
  588.  
  589.   if (this.storage && this.storage['generateNewSession']) {
  590.     delete this.storage['generateNewSession'];
  591.     return this.createSessionToken()
  592.     .then(this.handleFollowup.bind(this));
  593.   }
  594.  
  595.   if (this.storage && this.storage['sendVerificationEmail']) {
  596.     delete this.storage['sendVerificationEmail'];
  597.     // Fire and forget!
  598.     this.config.userController.sendVerificationEmail(this.data);
  599.     return this.handleFollowup.bind(this);
  600.   }
  601. };
  602.  
  603. // Handles the _Session class specialness.
  604. // Does nothing if this isn't an installation object.
  605. RestWrite.prototype.handleSession = function() {
  606.   if (this.response || this.className !== '_Session') {
  607.     return;
  608.   }
  609.  
  610.   if (!this.auth.user && !this.auth.isMaster) {
  611.     throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
  612.                           'Session token required.');
  613.   }
  614.  
  615.   // TODO: Verify proper error to throw
  616.   if (this.data.ACL) {
  617.     throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot set ' +
  618.                           'ACL on a Session.');
  619.   }
  620.  
  621.   if (!this.query && !this.auth.isMaster) {
  622.     var token = 'r:' + cryptoUtils.newToken();
  623.     var expiresAt = this.config.generateSessionExpiresAt();
  624.     var sessionData = {
  625.       sessionToken: token,
  626.       user: {
  627.         __type: 'Pointer',
  628.         className: '_User',
  629.         objectId: this.auth.user.id
  630.       },
  631.       createdWith: {
  632.         'action': 'create'
  633.       },
  634.       restricted: true,
  635.       expiresAt: Parse._encode(expiresAt)
  636.     };
  637.     for (var key in this.data) {
  638.       if (key == 'objectId') {
  639.         continue;
  640.       }
  641.       sessionData[key] = this.data[key];
  642.     }
  643.     var create = new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData);
  644.     return create.execute().then((results) => {
  645.       if (!results.response) {
  646.         throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR,
  647.                               'Error creating session.');
  648.       }
  649.       sessionData['objectId'] = results.response['objectId'];
  650.       this.response = {
  651.         status: 201,
  652.         location: results.location,
  653.         response: sessionData
  654.       };
  655.     });
  656.   }
  657. };
  658.  
  659. // Handles the _Installation class specialness.
  660. // Does nothing if this isn't an installation object.
  661. // If an installation is found, this can mutate this.query and turn a create
  662. // into an update.
  663. // Returns a promise for when we're done if it can't finish this tick.
  664. RestWrite.prototype.handleInstallation = function() {
  665.   if (this.response || this.className !== '_Installation') {
  666.     return;
  667.   }
  668.  
  669.   if (!this.query && !this.data.deviceToken && !this.data.installationId && !this.auth.installationId) {
  670.     throw new Parse.Error(135,
  671.                           'at least one ID field (deviceToken, installationId) ' +
  672.                           'must be specified in this operation');
  673.   }
  674.  
  675.   // If the device token is 64 characters long, we assume it is for iOS
  676.   // and lowercase it.
  677.   if (this.data.deviceToken && this.data.deviceToken.length == 64) {
  678.     this.data.deviceToken = this.data.deviceToken.toLowerCase();
  679.   }
  680.  
  681.   // We lowercase the installationId if present
  682.   if (this.data.installationId) {
  683.     this.data.installationId = this.data.installationId.toLowerCase();
  684.   }
  685.  
  686.   let installationId = this.data.installationId;
  687.  
  688.   // If data.installationId is not set and we're not master, we can lookup in auth
  689.   if (!installationId && !this.auth.isMaster) {
  690.     installationId = this.auth.installationId;
  691.   }
  692.  
  693.   if (installationId) {
  694.     installationId = installationId.toLowerCase();
  695.   }
  696.  
  697.   // Updating _Installation but not updating anything critical
  698.   if (this.query && !this.data.deviceToken
  699.                   && !installationId && !this.data.deviceType) {
  700.     return;
  701.   }
  702.  
  703.   var promise = Promise.resolve();
  704.  
  705.   var idMatch; // Will be a match on either objectId or installationId
  706.   var objectIdMatch;
  707.   var installationIdMatch;
  708.   var deviceTokenMatches = [];
  709.  
  710.   // Instead of issuing 3 reads, let's do it with one OR.
  711.   const orQueries = [];
  712.   if (this.query && this.query.objectId) {
  713.     orQueries.push({
  714.       objectId: this.query.objectId
  715.     });
  716.   }
  717.   if (installationId) {
  718.     orQueries.push({
  719.       'installationId': installationId
  720.     });
  721.   }
  722.   if (this.data.deviceToken) {
  723.     orQueries.push({'deviceToken': this.data.deviceToken});
  724.   }
  725.  
  726.   if (orQueries.length == 0) {
  727.     return;
  728.   }
  729.  
  730.   promise = promise.then(() => {
  731.     return this.config.database.find('_Installation', {
  732.       '$or': orQueries
  733.     }, {});
  734.   }).then((results) => {
  735.     results.forEach((result) => {
  736.       if (this.query && this.query.objectId && result.objectId == this.query.objectId) {
  737.         objectIdMatch = result;
  738.       }
  739.       if (result.installationId == installationId) {
  740.         installationIdMatch = result;
  741.       }
  742.       if (result.deviceToken == this.data.deviceToken) {
  743.         deviceTokenMatches.push(result);
  744.       }
  745.     });
  746.  
  747.     // Sanity checks when running a query
  748.     if (this.query && this.query.objectId) {
  749.       if (!objectIdMatch) {
  750.         throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
  751.                                 'Object not found for update.');
  752.       }
  753.       if (this.data.installationId && objectIdMatch.installationId &&
  754.           this.data.installationId !== objectIdMatch.installationId) {
  755.         throw new Parse.Error(136,
  756.                                 'installationId may not be changed in this ' +
  757.                                 'operation');
  758.       }
  759.       if (this.data.deviceToken && objectIdMatch.deviceToken &&
  760.           this.data.deviceToken !== objectIdMatch.deviceToken &&
  761.           !this.data.installationId && !objectIdMatch.installationId) {
  762.         throw new Parse.Error(136,
  763.                                 'deviceToken may not be changed in this ' +
  764.                                 'operation');
  765.       }
  766.       if (this.data.deviceType && this.data.deviceType &&
  767.           this.data.deviceType !== objectIdMatch.deviceType) {
  768.         throw new Parse.Error(136,
  769.                                 'deviceType may not be changed in this ' +
  770.                                 'operation');
  771.       }
  772.     }
  773.  
  774.     if (this.query && this.query.objectId && objectIdMatch) {
  775.       idMatch = objectIdMatch;
  776.     }
  777.  
  778.     if (installationId && installationIdMatch) {
  779.       idMatch = installationIdMatch;
  780.     }
  781.     // need to specify deviceType only if it's new
  782.     if (!this.query && !this.data.deviceType && !idMatch) {
  783.       throw new Parse.Error(135,
  784.                             'deviceType must be specified in this operation');
  785.     }
  786.  
  787.   }).then(() => {
  788.     if (!idMatch) {
  789.       if (!deviceTokenMatches.length) {
  790.         return;
  791.       } else if (deviceTokenMatches.length == 1 &&
  792.         (!deviceTokenMatches[0]['installationId'] || !installationId)
  793.       ) {
  794.         // Single match on device token but none on installationId, and either
  795.         // the passed object or the match is missing an installationId, so we
  796.         // can just return the match.
  797.         return deviceTokenMatches[0]['objectId'];
  798.       } else if (!this.data.installationId) {
  799.         throw new Parse.Error(132,
  800.                               'Must specify installationId when deviceToken ' +
  801.                               'matches multiple Installation objects');
  802.       } else {
  803.         // Multiple device token matches and we specified an installation ID,
  804.         // or a single match where both the passed and matching objects have
  805.         // an installation ID. Try cleaning out old installations that match
  806.         // the deviceToken, and return nil to signal that a new object should
  807.         // be created.
  808.         var delQuery = {
  809.           'deviceToken': this.data.deviceToken,
  810.           'installationId': {
  811.             '$ne': installationId
  812.           }
  813.         };
  814.         if (this.data.appIdentifier) {
  815.           delQuery['appIdentifier'] = this.data.appIdentifier;
  816.         }
  817.         this.config.database.destroy('_Installation', delQuery)
  818.           .catch(err => {
  819.             if (err.code == Parse.Error.OBJECT_NOT_FOUND) {
  820.               // no deletions were made. Can be ignored.
  821.               return;
  822.             }
  823.             // rethrow the error
  824.             throw err;
  825.           });
  826.         return;
  827.       }
  828.     } else {
  829.       if (deviceTokenMatches.length == 1 &&
  830.         !deviceTokenMatches[0]['installationId']) {
  831.         // Exactly one device token match and it doesn't have an installation
  832.         // ID. This is the one case where we want to merge with the existing
  833.         // object.
  834.         const delQuery = {objectId: idMatch.objectId};
  835.         return this.config.database.destroy('_Installation', delQuery)
  836.           .then(() => {
  837.             return deviceTokenMatches[0]['objectId'];
  838.           })
  839.           .catch(err => {
  840.             if (err.code == Parse.Error.OBJECT_NOT_FOUND) {
  841.               // no deletions were made. Can be ignored
  842.               return;
  843.             }
  844.             // rethrow the error
  845.             throw err;
  846.           });
  847.       } else {
  848.         if (this.data.deviceToken &&
  849.           idMatch.deviceToken != this.data.deviceToken) {
  850.           // We're setting the device token on an existing installation, so
  851.           // we should try cleaning out old installations that match this
  852.           // device token.
  853.           const delQuery = {
  854.             'deviceToken': this.data.deviceToken,
  855.           };
  856.           // We have a unique install Id, use that to preserve
  857.           // the interesting installation
  858.           if (this.data.installationId) {
  859.             delQuery['installationId'] = {
  860.               '$ne': this.data.installationId
  861.             }
  862.           } else if (idMatch.objectId && this.data.objectId
  863.                     && idMatch.objectId == this.data.objectId) {
  864.             // we passed an objectId, preserve that instalation
  865.             delQuery['objectId'] = {
  866.               '$ne': idMatch.objectId
  867.             }
  868.           } else {
  869.             // What to do here? can't really clean up everything...
  870.             return idMatch.objectId;
  871.           }
  872.           if (this.data.appIdentifier) {
  873.             delQuery['appIdentifier'] = this.data.appIdentifier;
  874.           }
  875.           this.config.database.destroy('_Installation', delQuery)
  876.             .catch(err => {
  877.               if (err.code == Parse.Error.OBJECT_NOT_FOUND) {
  878.                 // no deletions were made. Can be ignored.
  879.                 return;
  880.               }
  881.               // rethrow the error
  882.               throw err;
  883.             });
  884.         }
  885.         // In non-merge scenarios, just return the installation match id
  886.         return idMatch.objectId;
  887.       }
  888.     }
  889.   }).then((objId) => {
  890.     if (objId) {
  891.       this.query = {objectId: objId};
  892.       delete this.data.objectId;
  893.       delete this.data.createdAt;
  894.     }
  895.     // TODO: Validate ops (add/remove on channels, $inc on badge, etc.)
  896.   });
  897.   return promise;
  898. };
  899.  
  900. // If we short-circuted the object response - then we need to make sure we expand all the files,
  901. // since this might not have a query, meaning it won't return the full result back.
  902. // TODO: (nlutsenko) This should die when we move to per-class based controllers on _Session/_User
  903. RestWrite.prototype.expandFilesForExistingObjects = function() {
  904.   // Check whether we have a short-circuited response - only then run expansion.
  905.   if (this.response && this.response.response) {
  906.     this.config.filesController.expandFilesInObject(this.config, this.response.response);
  907.   }
  908. };
  909.  
  910. RestWrite.prototype.runDatabaseOperation = function() {
  911.   if (this.response) {
  912.     return;
  913.   }
  914.  
  915.   if (this.className === '_Role') {
  916.     this.config.cacheController.role.clear();
  917.   }
  918.  
  919.   if (this.className === '_User' &&
  920.       this.query &&
  921.       !this.auth.couldUpdateUserId(this.query.objectId)) {
  922.     throw new Parse.Error(Parse.Error.SESSION_MISSING, `Cannot modify user ${this.query.objectId}.`);
  923.   }
  924.  
  925.   if (this.className === '_Product' && this.data.download) {
  926.     this.data.downloadName = this.data.download.name;
  927.   }
  928.  
  929.   // TODO: Add better detection for ACL, ensuring a user can't be locked from
  930.   //       their own user record.
  931.   if (this.data.ACL && this.data.ACL['*unresolved']) {
  932.     throw new Parse.Error(Parse.Error.INVALID_ACL, 'Invalid ACL.');
  933.   }
  934.  
  935.   if (this.query) {
  936.     // Force the user to not lockout
  937.     // Matched with parse.com
  938.     if (this.className === '_User' && this.data.ACL) {
  939.       this.data.ACL[this.query.objectId] = { read: true, write: true };
  940.     }
  941.     // update password timestamp if user password is being changed
  942.     if (this.className === '_User' && this.data._hashed_password && this.config.passwordPolicy && this.config.passwordPolicy.maxPasswordAge) {
  943.       this.data._password_changed_at = Parse._encode(new Date());
  944.     }
  945.     // Ignore createdAt when update
  946.     delete this.data.createdAt;
  947.  
  948.     let defer = Promise.resolve();
  949.     // if password history is enabled then save the current password to history
  950.     if (this.className === '_User' && this.data._hashed_password && this.config.passwordPolicy && this.config.passwordPolicy.maxPasswordHistory) {
  951.       defer = this.config.database.find('_User', {objectId: this.objectId()}, {keys: ["_password_history", "_hashed_password"]}).then(results => {
  952.         if (results.length != 1) {
  953.           throw undefined;
  954.         }
  955.         const user = results[0];
  956.         let oldPasswords = [];
  957.         if (user._password_history) {
  958.           oldPasswords = _.take(user._password_history, this.config.passwordPolicy.maxPasswordHistory);
  959.         }
  960.         //n-1 passwords go into history including last password
  961.         while (oldPasswords.length > this.config.passwordPolicy.maxPasswordHistory - 2) {
  962.           oldPasswords.shift();
  963.         }
  964.         oldPasswords.push(user.password);
  965.         this.data._password_history = oldPasswords;
  966.       });
  967.     }
  968.  
  969.     return defer.then(() => {
  970.       // Run an update
  971.       return this.config.database.update(this.className, this.query, this.data, this.runOptions)
  972.       .then(response => {
  973.         response.updatedAt = this.updatedAt;
  974.         this._updateResponseWithData(response, this.data);
  975.         this.response = { response };
  976.       });
  977.     });
  978.   } else {
  979.     // Set the default ACL and password timestamp for the new _User
  980.     if (this.className === '_User') {
  981.       var ACL = this.data.ACL;
  982.       // default public r/w ACL
  983.       if (!ACL) {
  984.         ACL = {};
  985.         ACL['*'] = { read: true, write: false };
  986.       }
  987.       // make sure the user is not locked down
  988.       ACL[this.data.objectId] = { read: true, write: true };
  989.       this.data.ACL = ACL;
  990.       // password timestamp to be used when password expiry policy is enforced
  991.       if (this.config.passwordPolicy && this.config.passwordPolicy.maxPasswordAge) {
  992.         this.data._password_changed_at = Parse._encode(new Date());
  993.       }
  994.     }
  995.  
  996.     // Run a create
  997.     return this.config.database.create(this.className, this.data, this.runOptions)
  998.     .catch(error => {
  999.       if (this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) {
  1000.         throw error;
  1001.       }
  1002.       // If this was a failed user creation due to username or email already taken, we need to
  1003.       // check whether it was username or email and return the appropriate error.
  1004.  
  1005.       // TODO: See if we can later do this without additional queries by using named indexes.
  1006.       return this.config.database.find(
  1007.         this.className,
  1008.         { username: this.data.username, objectId: {'$ne': this.objectId()} },
  1009.         { limit: 1 }
  1010.       )
  1011.       .then(results => {
  1012.         if (results.length > 0) {
  1013.           throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.');
  1014.         }
  1015.         return this.config.database.find(
  1016.           this.className,
  1017.           { email: this.data.email, objectId: {'$ne': this.objectId()} },
  1018.           { limit: 1 }
  1019.         );
  1020.       })
  1021.       .then(results => {
  1022.         if (results.length > 0) {
  1023.           throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.');
  1024.         }
  1025.         throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
  1026.       });
  1027.     })
  1028.     .then(response => {
  1029.       response.objectId = this.data.objectId;
  1030.       response.createdAt = this.data.createdAt;
  1031.  
  1032.       if (this.responseShouldHaveUsername) {
  1033.         response.username = this.data.username;
  1034.       }
  1035.       this._updateResponseWithData(response, this.data);
  1036.       this.response = {
  1037.         status: 201,
  1038.         response,
  1039.         location: this.location()
  1040.       };
  1041.     });
  1042.   }
  1043. };
  1044.  
  1045. // Returns nothing - doesn't wait for the trigger.
  1046. RestWrite.prototype.runAfterTrigger = function() {
  1047.   if (!this.response || !this.response.response) {
  1048.     return;
  1049.   }
  1050.  
  1051.   // Avoid doing any setup for triggers if there is no 'afterSave' trigger for this class.
  1052.   const hasAfterSaveHook = triggers.triggerExists(this.className, triggers.Types.afterSave, this.config.applicationId);
  1053.   const hasLiveQuery = this.config.liveQueryController.hasLiveQuery(this.className);
  1054.   if (!hasAfterSaveHook && !hasLiveQuery) {
  1055.     return Promise.resolve();
  1056.   }
  1057.  
  1058.   var extraData = {className: this.className};
  1059.   if (this.query && this.query.objectId) {
  1060.     extraData.objectId = this.query.objectId;
  1061.   }
  1062.  
  1063.   // Build the original object, we only do this for a update write.
  1064.   let originalObject;
  1065.   if (this.query && this.query.objectId) {
  1066.     originalObject = triggers.inflate(extraData, this.originalData);
  1067.   }
  1068.  
  1069.   // Build the inflated object, different from beforeSave, originalData is not empty
  1070.   // since developers can change data in the beforeSave.
  1071.   const updatedObject = triggers.inflate(extraData, this.originalData);
  1072.   updatedObject.set(this.sanitizedData());
  1073.   updatedObject._handleSaveResponse(this.response.response, this.response.status || 200);
  1074.  
  1075.   // Notifiy LiveQueryServer if possible
  1076.   this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject);
  1077.  
  1078.   // Run afterSave trigger
  1079.   return triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config);
  1080. };
  1081.  
  1082. // A helper to figure out what location this operation happens at.
  1083. RestWrite.prototype.location = function() {
  1084.   var middle = (this.className === '_User' ? '/users/' :
  1085.                 '/classes/' + this.className + '/');
  1086.   return this.config.mount + middle + this.data.objectId;
  1087. };
  1088.  
  1089. // A helper to get the object id for this operation.
  1090. // Because it could be either on the query or on the data
  1091. RestWrite.prototype.objectId = function() {
  1092.   return this.data.objectId || this.query.objectId;
  1093. };
  1094.  
  1095. // Returns a copy of the data and delete bad keys (_auth_data, _hashed_password...)
  1096. RestWrite.prototype.sanitizedData = function() {
  1097.   const data = Object.keys(this.data).reduce((data, key) => {
  1098.     // Regexp comes from Parse.Object.prototype.validate
  1099.     if (!(/^[A-Za-z][0-9A-Za-z_]*$/).test(key)) {
  1100.       delete data[key];
  1101.     }
  1102.     return data;
  1103.   }, deepcopy(this.data));
  1104.   return Parse._decode(undefined, data);
  1105. }
  1106.  
  1107. RestWrite.prototype.cleanUserAuthData = function() {
  1108.   if (this.response && this.response.response && this.className === '_User') {
  1109.     const user = this.response.response;
  1110.     if (user.authData) {
  1111.       Object.keys(user.authData).forEach((provider) => {
  1112.         if (user.authData[provider] === null) {
  1113.           delete user.authData[provider];
  1114.         }
  1115.       });
  1116.       if (Object.keys(user.authData).length == 0) {
  1117.         delete user.authData;
  1118.       }
  1119.     }
  1120.   }
  1121. };
  1122.  
  1123. RestWrite.prototype._updateResponseWithData = function(response, data) {
  1124.   if (_.isEmpty(this.storage.fieldsChangedByTrigger)) {
  1125.     return response;
  1126.   }
  1127.   const clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK);
  1128.   this.storage.fieldsChangedByTrigger.forEach(fieldName => {
  1129.     const dataValue = data[fieldName];
  1130.     const responseValue = response[fieldName];
  1131.  
  1132.     response[fieldName] = responseValue || dataValue;
  1133.  
  1134.     // Strips operations from responses
  1135.     if (response[fieldName] && response[fieldName].__op) {
  1136.       delete response[fieldName];
  1137.       if (clientSupportsDelete && dataValue.__op == 'Delete') {
  1138.         response[fieldName] = dataValue;
  1139.       }
  1140.     }
  1141.   });
  1142.   return response;
  1143. }
  1144.  
  1145. export default RestWrite;
  1146. module.exports = RestWrite;
  1147.  
downloadRestWrite.js Source code - Download parse-server Source code
Related Source Codes/Software:
react-boilerplate - 2017-06-07
webtorrent - Streaming torrent client for the web ... 2017-06-06
machine-learning-for-software-engineers - A complete daily plan for studying to become a mac... 2017-06-06
upterm - A terminal emulator for the 21st century. 2017-06-06
lottie-android - Render After Effects animations natively on Androi... 2017-06-07
AsyncDisplayKit - Smooth asynchronous user interfaces for iOS apps. ... 2017-06-07
ionicons - The premium icon font for Ionic ... 2017-06-07
storybook - 2017-06-07
prettier - Prettier is an opinionated JavaScript formatter. ... 2017-06-08
CRYENGINE - CRYENGINE is a powerful real-time game development... 2017-06-11
postal - 2017-06-11
reactide - Reactide is the first dedicated IDE for React web ... 2017-06-11
rkt - rkt is a pod-native container engine for Linux. It... 2017-06-11
uWebSockets - Tiny WebSockets https://for... 2017-06-11
realworld - TodoMVC for the RealWorld - Exemplary fullstack Me... 2017-06-11
goreplay - GoReplay is an open-source tool for capturing and ... 2017-06-10
pyenv - Simple Python version management 2017-06-10
redux-saga - An alternative side effect model for Redux apps ... 2017-06-10
angular-starter - 2017-06-10

 Back to top