Skip to content

fix: ParseUser.linkWith doesn't remove anonymous auth data #2007

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 2 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration/test/ParseUserTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
30 changes: 23 additions & 7 deletions src/ParseUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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").
*
Expand All @@ -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);
}

Expand Down
137 changes: 137 additions & 0 deletions src/__tests__/ParseUser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {},
#() {},
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();
Expand Down