From 5a8bfdff2c8122561a64e55c8106e00433f17e29 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 29 Aug 2023 15:42:06 -0500 Subject: [PATCH] fix: `User.linkWith` doesn't remove anonymous auth --- integration/test/ParseUserTest.js | 4 +- src/ParseUser.js | 30 +++++-- src/__tests__/ParseUser-test.js | 137 ++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 9 deletions(-) diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js index eef11b73c..d67baae91 100644 --- a/integration/test/ParseUserTest.js +++ b/integration/test/ParseUserTest.js @@ -979,11 +979,11 @@ describe('Parse User', () => { await Parse.FacebookUtils.link(user); expect(Parse.FacebookUtils.isLinked(user)).toBe(true); - expect(Parse.AnonymousUtils.isLinked(user)).toBe(true); + expect(Parse.AnonymousUtils.isLinked(user)).toBe(false); await Parse.FacebookUtils.unlink(user); expect(Parse.FacebookUtils.isLinked(user)).toBe(false); - expect(Parse.AnonymousUtils.isLinked(user)).toBe(true); + expect(Parse.AnonymousUtils.isLinked(user)).toBe(false); }); it('can link with twitter', async () => { diff --git a/src/ParseUser.js b/src/ParseUser.js index 67c26a854..146221614 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -110,9 +110,15 @@ class ParseUser extends ParseObject { throw new Error('Invalid type: authData field should be an object'); } authData[authType] = options.authData; + const oldAnonymousData = authData.anonymous; + this.stripAnonymity(); const controller = CoreManager.getUserController(); - return controller.linkWith(this, authData, saveOpts); + return controller.linkWith(this, authData, saveOpts).catch((e) => { + delete authData[authType]; + this.restoreAnonimity(oldAnonymousData); + throw e; + }); } else { return new Promise((resolve, reject) => { provider.authenticate({ @@ -310,6 +316,21 @@ class ParseUser extends ParseObject { return !!current && current.id === this.id; } + stripAnonymity() { + const authData = this.get('authData'); + if (authData && typeof authData === 'object' && authData.hasOwnProperty('anonymous')) { + // We need to set anonymous to null instead of deleting it in order to remove it from Parse. + authData.anonymous = null; + } + } + + restoreAnonimity(anonymousData: any) { + if (anonymousData) { + const authData = this.get('authData'); + authData.anonymous = anonymousData; + } + } + /** * Returns get("username"). * @@ -329,12 +350,7 @@ class ParseUser extends ParseObject { * @param {string} username */ setUsername(username: string) { - // Strip anonymity - const authData = this.get('authData'); - if (authData && typeof authData === 'object' && authData.hasOwnProperty('anonymous')) { - // We need to set anonymous to null instead of deleting it in order to remove it from Parse. - authData.anonymous = null; - } + this.stripAnonymity(); this.set('username', username); } diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index 19fe726a5..889418ec0 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -1187,6 +1187,143 @@ describe('ParseUser', () => { spy.mockRestore(); }); + it('can strip anonymous user on linkWith', async () => { + ParseUser.enableUnsafeCurrentUser(); + ParseUser._clearCache(); + CoreManager.setRESTController({ + request() { + return Promise.resolve( + { + objectId: 'uidstrip', + sessionToken: 'r:123abc', + authData: { + anonymous: { + id: 'anonymousId', + }, + }, + }, + 200 + ); + }, + ajax() {}, + }); + const user = await AnonymousUtils.logIn(); + + expect(user.get('authData').anonymous).toBeDefined(); + + ParseUser._setCurrentUserCache(user); + + CoreManager.setRESTController({ + request() { + return Promise.resolve( + { + objectId: 'uidstrip', + sessionToken: 'r:123abc', + authData: { + test: { + id: 'id', + access_token: 'access_token', + }, + }, + }, + 200 + ); + }, + ajax() {}, + }); + const provider = { + authenticate(options) { + if (options.success) { + options.success(this, { + id: 'id', + access_token: 'access_token', + }); + } + }, + restoreAuthentication() {}, + getAuthType() { + return 'test'; + }, + deauthenticate() {}, + }; + + await user.linkWith(provider, null, { useMasterKey: true }); + + expect(user.get('authData')).toEqual({ + test: { id: 'id', access_token: 'access_token' }, + }); + }); + + it('can restore anonymous user on linkWith failure', async () => { + ParseUser.enableUnsafeCurrentUser(); + ParseUser._clearCache(); + CoreManager.setRESTController({ + request() { + return Promise.resolve( + { + objectId: 'uidrestore', + sessionToken: 'r:123abc', + authData: { + anonymous: { + id: 'anonymousId', + }, + }, + }, + 200 + ); + }, + ajax() {}, + }); + const user = await AnonymousUtils.logIn(); + expect(user.get('authData').anonymous).toBeDefined(); + + ParseUser._setCurrentUserCache(user); + + const provider = { + authenticate(options) { + if (options.success) { + options.success(this, { + id: 'id', + access_token: 'access_token', + }); + } + }, + restoreAuthentication() {}, + getAuthType() { + return 'test'; + }, + deauthenticate() {}, + }; + + const UserController = CoreManager.getUserController(); + CoreManager.setUserController({ + linkWith(user) { + expect(user.get('authData').anonymous).toEqual(null); + return Promise.reject('authentication error'); + }, + currentUserAsync() {}, + setCurrentUser() {}, + currentUser() {}, + signUp() {}, + logIn() {}, + become() {}, + logOut() {}, + me() {}, + requestPasswordReset() {}, + upgradeToRevocableSession() {}, + requestEmailVerification() {}, + verifyPassword() {}, + }); + try { + await user.linkWith(provider, null, { useMasterKey: true }); + expect(true).toBe(false); + } catch (e) { + expect(e).toBe('authentication error'); + } + expect(user.get('authData')).toEqual({ anonymous: { id: 'anonymousId' } }); + CoreManager.setUserController(UserController); + }); + it('can logout anonymous user', async () => { ParseUser.enableUnsafeCurrentUser(); ParseUser._clearCache();