diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 9a6e2507..ab95768e 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -3453,4 +3453,34 @@ describe('Parse.User testing', () => { done(); }); }); + + it('does not duplicate session when logging in multiple times #3451', (done) => { + const user = new Parse.User(); + user.signUp({ + username: 'yolo', + password: 'yolo', + email: 'yo@lo.com' + }).then(() => { + const promises = []; + while(promises.length != 5) { + Parse.User.logIn('yolo', 'yolo') + promises.push(Parse.User.logIn('yolo', 'yolo').then((res) => { + // ensure a new session token is generated at each login + expect(res.getSessionToken()).not.toBe(user.getSessionToken()); + })); + } + return Promise.all(promises); + }).then(() => { + // wait because session destruction is not synchronous + return new Promise((resolve) => { + setTimeout(resolve, 100); + }); + }).then(() => { + const query = new Parse.Query('_Session'); + return query.find({ useMasterKey: true }); + }).then((results) => { + // only one session in the end + expect(results.length).toBe(1); + }).then(done, done.fail); + }); }); diff --git a/src/RestWrite.js b/src/RestWrite.js index 61b8eea4..424284d5 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -81,6 +81,8 @@ RestWrite.prototype.execute = function() { return this.transformUser(); }).then(() => { return this.expandFilesForExistingObjects(); + }).then(() => { + return this.destroyDuplicatedSessions(); }).then(() => { return this.runDatabaseOperation(); }).then(() => { @@ -588,19 +590,33 @@ RestWrite.prototype.createSessionToken = function() { this.response.response.sessionToken = token; } - // Destroy the sessions in 'Background' - this.config.database.destroy('_Session', { - user: { - __type: 'Pointer', - className: '_User', - objectId: this.objectId() - }, - installationId: this.auth.installationId, - sessionToken: { '$ne': token }, - }); return new RestWrite(this.config, Auth.master(this.config), '_Session', null, sessionData).execute(); } +RestWrite.prototype.destroyDuplicatedSessions = function() { + // Only for _Session, and at creation time + if (this.className != '_Session' || this.query) { + return; + } + // Destroy the sessions in 'Background' + const { + user, + installationId, + sessionToken, + } = this.data; + if (!user || !installationId) { + return; + } + if (!user.objectId) { + return; + } + this.config.database.destroy('_Session', { + user, + installationId, + sessionToken: { '$ne': sessionToken }, + }); +} + // Handles any followup logic RestWrite.prototype.handleFollowup = function() { if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) {