From 61aa5a8d6249423ca68d8e18618c118988872410 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Sat, 9 Jul 2016 00:49:46 -0400 Subject: [PATCH] Let auth data be updated on login (#2219) * Let user update authData token upon login * Adds tests that ensures linked authData isnt overriden * fixes focused testing problem --- spec/ParseUser.spec.js | 89 +++++++++++++++++++++++++++++++++++++++--- spec/helper.js | 11 ++++-- src/RestWrite.js | 33 ++++++++++++++-- 3 files changed, 122 insertions(+), 11 deletions(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index e7c4ce00..fdd90b5d 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -907,13 +907,11 @@ describe('Parse.User testing', () => { })); }); - // Note that this mocks out client-side Facebook action rather than - // server-side. - var getMockFacebookProvider = function() { + var getMockFacebookProviderWithIdToken = function(id, token) { return { authData: { - id: "8675309", - access_token: "jenny", + id: id, + access_token: token, expiration_date: new Date().toJSON(), }, shouldError: false, @@ -951,6 +949,12 @@ describe('Parse.User testing', () => { this.restoreAuthentication(null); } }; + } + + // Note that this mocks out client-side Facebook action rather than + // server-side. + var getMockFacebookProvider = function() { + return getMockFacebookProviderWithIdToken('8675309', 'jenny'); }; var getMockMyOauthProvider = function() { @@ -1025,6 +1029,40 @@ describe('Parse.User testing', () => { }); }); + it_exclude_dbs(['postgres'])("log in with provider and update token", (done) => { + var provider = getMockFacebookProvider(); + var secondProvider = getMockFacebookProviderWithIdToken('8675309', 'jenny_valid_token'); + var errorHandler = function(err) { + fail('should not fail'); + done(); + } + Parse.User._registerAuthenticationProvider(provider); + Parse.User._logInWith("facebook", { + success: (model) => { + Parse.User._registerAuthenticationProvider(secondProvider); + return Parse.User.logOut().then(() => { + Parse.User._logInWith("facebook", { + success: (model) => { + expect(secondProvider.synchronizedAuthToken).toEqual('jenny_valid_token'); + // Make sure we can login with the new token again + Parse.User.logOut().then(() => { + Parse.User._logInWith("facebook", { + success: done, + error: errorHandler + }); + }); + }, + error: errorHandler + }); + }) + }, + error: errorHandler + }).catch((err) => { + errorHandler(err); + done(); + }); + }); + it_exclude_dbs(['postgres'])('returns authData when authed and logged in with provider (regression test for #1498)', done => { Parse.Object.enableSingleInstance(); let provider = getMockFacebookProvider(); @@ -1428,6 +1466,47 @@ describe('Parse.User testing', () => { }); }); + it_exclude_dbs(['postgres'])("link multiple providers and updates token", (done) => { + var provider = getMockFacebookProvider(); + var secondProvider = getMockFacebookProviderWithIdToken('8675309', 'jenny_valid_token'); + + var errorHandler = function(model, error) { + console.error(error); + fail('Should not fail'); + done(); + } + var mockProvider = getMockMyOauthProvider(); + Parse.User._registerAuthenticationProvider(provider); + Parse.User._logInWith("facebook", { + success: function(model) { + Parse.User._registerAuthenticationProvider(mockProvider); + let objectId = model.id; + model._linkWith("myoauth", { + success: function(model) { + Parse.User._registerAuthenticationProvider(secondProvider); + Parse.User.logOut().then(() => { + return Parse.User._logInWith("facebook", { + success: () => { + Parse.User.logOut().then(() => { + return Parse.User._logInWith("myoauth", { + success: (user) => { + expect(user.id).toBe(objectId); + done(); + } + }) + }) + }, + error: errorHandler + }); + }) + }, + error: errorHandler + }) + }, + error: errorHandler + }); + }); + it_exclude_dbs(['postgres'])("link multiple providers and update token", (done) => { var provider = getMockFacebookProvider(); var mockProvider = getMockMyOauthProvider(); diff --git a/spec/helper.js b/spec/helper.js index 3e22f59b..8a1d0c5f 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -281,17 +281,17 @@ function range(n) { return answer; } -function mockFacebook() { +function mockFacebookAuthenticator(id, token) { var facebook = {}; facebook.validateAuthData = function(authData) { - if (authData.id === '8675309' && authData.access_token === 'jenny') { + if (authData.id === id && authData.access_token.startsWith(token)) { return Promise.resolve(); } else { throw undefined; } }; facebook.validateAppId = function(appId, authData) { - if (authData.access_token === 'jenny') { + if (authData.access_token.startsWith(token)) { return Promise.resolve(); } else { throw undefined; @@ -300,6 +300,10 @@ function mockFacebook() { return facebook; } +function mockFacebook() { + return mockFacebookAuthenticator('8675309', 'jenny'); +} + // This is polluting, but, it makes it way easier to directly port old tests. @@ -320,6 +324,7 @@ global.jequal = jequal; global.range = range; global.reconfigureServer = reconfigureServer; global.defaultConfiguration = defaultConfiguration; +global.mockFacebookAuthenticator = mockFacebookAuthenticator; global.it_exclude_dbs = excluded => { if (excluded.includes(process.env.PARSE_SERVER_TEST_DB)) { diff --git a/src/RestWrite.js b/src/RestWrite.js index 695e5a1b..bc1ad2c0 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -290,14 +290,41 @@ RestWrite.prototype.handleAuthData = function(authData) { if (results.length > 0) { if (!this.query) { // Login with auth data - // Short circuit delete results[0].password; + let userResult = results[0]; + // need to set the objectId first otherwise location has trailing undefined - this.data.objectId = results[0].objectId; + this.data.objectId = userResult.objectId; + + // Determine if authData was updated + let mutatedAuthData = {}; + Object.keys(authData).forEach((provider) => { + let providerData = authData[provider]; + let userAuthData = userResult.authData[provider]; + if (!_.isEqual(providerData, userAuthData)) { + mutatedAuthData[provider] = providerData; + } + }); + this.response = { - response: results[0], + response: userResult, location: this.location() }; + + // We have authData that is updated on login + // that can happen when token are refreshed, + // We should update the token and let the user in + if (Object.keys(mutatedAuthData).length > 0) { + // Assign the new authData in the response + Object.keys(mutatedAuthData).forEach((provider) => { + this.response.response.authData[provider] = mutatedAuthData[provider]; + }); + // Run the DB update directly, as 'master' + // Just update the authData part + return this.config.database.update(this.className, {objectId: this.data.objectId}, {authData: mutatedAuthData}, {}); + } + return; + } else if (this.query && this.query.objectId) { // Trying to update auth data but users // are different