From 3b581965ac8fbe25614ebb86d91dfbd3e4b29c40 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 12 Jul 2016 21:51:08 -0400 Subject: [PATCH 1/2] Creates a new sessionToken when updating password --- spec/ParseUser.spec.js | 42 ++++++++++++++++++++++++++++++++++++++---- src/RestWrite.js | 15 +++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index fdd90b5d25..626f20d698 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -2262,7 +2262,9 @@ describe('Parse.User testing', () => { req.object.set('foo', 'bar'); res.success(); }); - + + let originalSessionToken; + let originalUserId; // Simulate anonymous user save new Promise((resolve, reject) => { request.post({ @@ -2280,6 +2282,8 @@ describe('Parse.User testing', () => { } }); }).then((user) => { + originalSessionToken = user.sessionToken; + originalUserId = user.objectId; // Simulate registration return new Promise((resolve, reject) => { request.put({ @@ -2291,7 +2295,7 @@ describe('Parse.User testing', () => { }, json: { authData: {anonymous: null}, - user: 'user', + username: 'user', password: 'password', } }, (err, res, body) => { @@ -2305,7 +2309,30 @@ describe('Parse.User testing', () => { }).then((user) => { expect(typeof user).toEqual('object'); expect(user.authData).toBeUndefined(); - done(); + expect(user.sessionToken).not.toBeUndefined(); + // Session token should have changed + expect(user.sessionToken).not.toEqual(originalSessionToken); + // test that the sessionToken is valid + return new Promise((resolve, reject) => { + request.get({ + url: 'http://localhost:8378/1/users/me', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Session-Token': user.sessionToken, + 'X-Parse-REST-API-Key': 'rest', + }, + json: true + }, (err, res, body) => { + expect(body.username).toEqual(user.username); + expect(body.objectId).toEqual(originalUserId); + if (err) { + reject(err); + } else { + resolve(body); + } + done(); + }); + }); }).catch((err) => { fail('no request should fail: ' + JSON.stringify(err)); done(); @@ -2471,9 +2498,16 @@ describe('Parse.User testing', () => { user.set('password', 'password'); return user.save() }) + .then(() => { + // Session token should have been recycled + expect(body.sessionToken).not.toEqual(user.getSessionToken()); + }) .then(() => obj.fetch()) + .then((res) => { + done(); + }) .catch(error => { - expect(error.code).toEqual(Parse.Error.INVALID_SESSION_TOKEN); + fail('should not fail') done(); }); }) diff --git a/src/RestWrite.js b/src/RestWrite.js index cb438545f8..210d3f4f0f 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -367,6 +367,7 @@ RestWrite.prototype.transformUser = function() { } if (this.query && !this.auth.isMaster ) { this.storage['clearSessions'] = true; + this.storage['generateNewSession'] = true; } return passwordCrypto.hash(this.data.password).then((hashedPassword) => { this.data._hashed_password = hashedPassword; @@ -428,6 +429,10 @@ RestWrite.prototype.createSessionTokenIfNeeded = function() { if (this.query) { return; } + return this.createSessionToken(); +} + +RestWrite.prototype.createSessionToken = function() { var token = 'r:' + cryptoUtils.newToken(); var expiresAt = this.config.generateSessionExpiresAt(); @@ -464,7 +469,13 @@ RestWrite.prototype.handleFollowup = function() { } }; delete this.storage['clearSessions']; - this.config.database.destroy('_Session', sessionQuery) + return this.config.database.destroy('_Session', sessionQuery) + .then(this.handleFollowup.bind(this)); + } + + if (this.storage && this.storage['generateNewSession']) { + delete this.storage['generateNewSession']; + return this.createSessionToken() .then(this.handleFollowup.bind(this)); } @@ -472,7 +483,7 @@ RestWrite.prototype.handleFollowup = function() { delete this.storage['sendVerificationEmail']; // Fire and forget! this.config.userController.sendVerificationEmail(this.data); - this.handleFollowup.bind(this); + return this.handleFollowup.bind(this); } }; From 41178ac259b2d229c165c4747cc75c059e8da7f9 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 13 Jul 2016 00:25:20 -0400 Subject: [PATCH 2/2] Adds test ensuring email is properly sent when upgrading from anon --- spec/ParseUser.spec.js | 55 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 626f20d698..d2daa46e1a 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -2257,7 +2257,7 @@ describe('Parse.User testing', () => { }) }); - it('should cleanup null authData keys ParseUser update (regression test for #1198)', (done) => { + it_exclude_dbs(['postgres'])('should cleanup null authData keys ParseUser update (regression test for #1198, #2252)', (done) => { Parse.Cloud.beforeSave('_User', (req, res) => { req.object.set('foo', 'bar'); res.success(); @@ -2339,6 +2339,59 @@ describe('Parse.User testing', () => { }); }); + it_exclude_dbs(['postgres'])('should send email when upgrading from anon', (done) => { + + let emailCalled = false; + let emailOptions; + var emailAdapter = { + sendVerificationEmail: (options) => { + emailOptions = options; + emailCalled = true; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve() + } + reconfigureServer({ + appName: 'unused', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" + }) + // Simulate anonymous user save + return rp.post({ + url: 'http://localhost:8378/1/classes/_User', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + }, + json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}} + }).then((user) => { + return rp.put({ + url: 'http://localhost:8378/1/classes/_User/' + user.objectId, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Session-Token': user.sessionToken, + 'X-Parse-REST-API-Key': 'rest', + }, + json: { + authData: {anonymous: null}, + username: 'user', + email: 'user@email.com', + password: 'password', + } + }); + }).then(() => { + expect(emailCalled).toBe(true); + expect(emailOptions).not.toBeUndefined(); + expect(emailOptions.user.get('email')).toEqual('user@email.com'); + done(); + }).catch((err) => { + console.error(err); + fail('no request should fail: ' + JSON.stringify(err)); + done(); + }); + }); + it('should aftersave with full object', (done) => { var hit = 0;