diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 7e144bc5aa..35c6413c74 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -1730,6 +1730,57 @@ describe('Parse.User testing', () => { }); }); + it('should allow PUT request with stale auth Data', (done) => { + const provider = { + authData: { + id: '12345', + access_token: 'token' + }, + restoreAuthentication: function() { + return true; + }, + deauthenticate: function() { + provider.authData = {}; + }, + authenticate: function(options) { + options.success(this, provider.authData); + }, + getAuthType: function() { + return "shortLivedAuth"; + } + } + defaultConfiguration.auth.shortLivedAuth.setValidAccessToken('token'); + Parse.User._registerAuthenticationProvider(provider); + Parse.User._logInWith("shortLivedAuth", {}).then(() => { + // Simulate a remotely expired token (like a short lived one) + // In this case, we want success as it was valid once. + // If the client needs an updated one, do lock the user out + defaultConfiguration.auth.shortLivedAuth.setValidAccessToken('otherToken'); + return rp.put({ + url: Parse.serverURL + '/users/' + Parse.User.current().id, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey, + 'X-Parse-Session-Token': Parse.User.current().getSessionToken(), + 'Content-Type': 'application/json' + }, + json: { + key: 'value', // update a key + authData: { // pass the original auth data + shortLivedAuth: { + id: '12345', + access_token: 'token' + } + } + } + }) + }).then(() => { + done(); + }, (err) => { + done.fail(err); + }); + }); + it('should properly error when password is missing', (done) => { var provider = getMockFacebookProvider(); Parse.User._registerAuthenticationProvider(provider); diff --git a/src/RestWrite.js b/src/RestWrite.js index 6e045b98f9..3f6e542700 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -288,30 +288,32 @@ RestWrite.prototype.handleAuthData = function(authData) { this.storage['authProvider'] = Object.keys(authData).join(','); if (results.length > 0) { + const userResult = results[0]; + const mutatedAuthData = {}; + Object.keys(authData).forEach((provider) => { + const providerData = authData[provider]; + const userAuthData = userResult.authData[provider]; + if (!_.isEqual(providerData, userAuthData)) { + mutatedAuthData[provider] = providerData; + } + }); + const hasMutatedAuthData = Object.keys(mutatedAuthData).length !== 0; if (!this.query) { // Login with auth data delete results[0].password; - const userResult = results[0]; // need to set the objectId first otherwise location has trailing undefined this.data.objectId = userResult.objectId; // Determine if authData was updated - const mutatedAuthData = {}; - Object.keys(authData).forEach((provider) => { - const providerData = authData[provider]; - const userAuthData = userResult.authData[provider]; - if (!_.isEqual(providerData, userAuthData)) { - mutatedAuthData[provider] = providerData; - } - }); + this.response = { response: userResult, location: this.location() }; // If we didn't change the auth data, just keep going - if (Object.keys(mutatedAuthData).length === 0) { + if (!hasMutatedAuthData) { return; } // We have authData that is updated on login @@ -330,10 +332,14 @@ RestWrite.prototype.handleAuthData = function(authData) { } else if (this.query && this.query.objectId) { // Trying to update auth data but users // are different - if (results[0].objectId !== this.query.objectId) { + if (userResult.objectId !== this.query.objectId) { throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); } + // No auth data was mutated, just keep going + if (!hasMutatedAuthData) { + return; + } } } return this.handleAuthDataValidation(authData);