Skip to content

Commit 2979264

Browse files
committed
Fix authData handling to exclude unlinked providers and add tests for edge cases
1 parent 7db1f8e commit 2979264

File tree

2 files changed

+165
-1
lines changed

2 files changed

+165
-1
lines changed

spec/Users.authdata.spec.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
describe('RestWrite.handleAuthData', () => {
2+
const MOCK_USER_ID = 'mockUserId';
3+
const MOCK_ACCESS_TOKEN = 'mockAccessToken123';
4+
5+
const createMockUser = () => ({
6+
id: MOCK_USER_ID,
7+
code: 'C1',
8+
});
9+
10+
const mockGooglePlayGamesAPI = () => {
11+
mockFetch([
12+
{
13+
url: 'https://oauth2.googleapis.com/token',
14+
method: 'POST',
15+
response: {
16+
ok: true,
17+
json: () => Promise.resolve({ access_token: MOCK_ACCESS_TOKEN }),
18+
},
19+
},
20+
{
21+
url: `https://www.googleapis.com/games/v1/players/${MOCK_USER_ID}`,
22+
method: 'GET',
23+
response: {
24+
ok: true,
25+
json: () => Promise.resolve({ playerId: MOCK_USER_ID }),
26+
},
27+
},
28+
]);
29+
};
30+
31+
const setupAuthConfig = (additionalProviders = {}) => {
32+
return reconfigureServer({
33+
auth: {
34+
gpgames: {
35+
clientId: 'validClientId',
36+
clientSecret: 'validClientSecret',
37+
},
38+
someAdapter1: {
39+
validateAuthData: () => Promise.resolve(),
40+
validateAppId: () => Promise.resolve(),
41+
validateOptions: () => {},
42+
},
43+
someAdapter2: {
44+
validateAuthData: () => Promise.resolve(),
45+
validateAppId: () => Promise.resolve(),
46+
validateOptions: () => {},
47+
},
48+
...additionalProviders,
49+
},
50+
});
51+
};
52+
53+
beforeEach(async () => {
54+
await setupAuthConfig();
55+
});
56+
57+
it('should handle multiple providers correctly', async () => {
58+
mockGooglePlayGamesAPI();
59+
60+
const authData = {
61+
gpgames: { id: MOCK_USER_ID, code: 'C4' },
62+
someAdapter2: { id: 'F1', access_token: 'fb_token' },
63+
};
64+
65+
const user = new Parse.User();
66+
user.set('authData', authData);
67+
await user.save();
68+
69+
const sessionToken = user.getSessionToken();
70+
71+
await user.fetch({ sessionToken });
72+
const currentAuthData = user.get('authData') || {};
73+
74+
user.set('authData', {
75+
someAdapter2: currentAuthData.someAdapter2,
76+
someAdapter1: { id: 'T2', access_token: 'tw_token' },
77+
gpgames: null, // Unlink Google Play Games
78+
});
79+
await user.save(null, { sessionToken });
80+
81+
const updatedUser = await new Parse.Query(Parse.User).get(user.id, { useMasterKey: true });
82+
const finalAuthData = updatedUser.get('authData') || {};
83+
84+
expect(finalAuthData.gpgames).toBeUndefined();
85+
expect(finalAuthData.someAdapter2?.id).toBe('F1');
86+
expect(finalAuthData.someAdapter1?.id).toBe('T2');
87+
});
88+
89+
it('should unlink provider via null', async () => {
90+
mockGooglePlayGamesAPI();
91+
92+
const authData = createMockUser();
93+
const user = await Parse.User.logInWith('gpgames', { authData });
94+
const sessionToken = user.getSessionToken();
95+
96+
await user.fetch({ sessionToken });
97+
const currentAuthData = user.get('authData') || {};
98+
99+
user.set('authData', {
100+
...currentAuthData,
101+
gpgames: null,
102+
});
103+
await user.save(null, { sessionToken });
104+
105+
const updatedUser = await new Parse.Query(Parse.User).get(user.id, { useMasterKey: true });
106+
const finalAuthData = updatedUser.get('authData') || {};
107+
108+
expect(finalAuthData.gpgames).toBeUndefined();
109+
});
110+
111+
it('should handle empty authData gracefully', async () => {
112+
mockGooglePlayGamesAPI();
113+
114+
const user = await Parse.User.signUp('test', 'password123');
115+
116+
const sessionToken = user.getSessionToken();
117+
await user.fetch({ sessionToken });
118+
119+
user.set('authData', {
120+
someAdapter1: { id: 'T3', access_token: 'token456' },
121+
});
122+
await user.save(null, { sessionToken });
123+
124+
const updatedUser = await new Parse.Query(Parse.User).get(user.id, { useMasterKey: true });
125+
const finalAuthData = updatedUser.get('authData');
126+
127+
expect(finalAuthData).toBeDefined();
128+
expect(finalAuthData.someAdapter1?.id).toBe('T3');
129+
});
130+
131+
it('should handle partial provider data updates correctly', async () => {
132+
mockGooglePlayGamesAPI();
133+
134+
const authData = createMockUser();
135+
const user = await Parse.User.logInWith('gpgames', { authData });
136+
137+
const sessionToken = user.getSessionToken();
138+
139+
await user.fetch({ sessionToken });
140+
141+
const currentAuthData = user.get('authData') || {};
142+
user.set('authData', {
143+
...currentAuthData,
144+
gpgames: {
145+
...currentAuthData.gpgames,
146+
code: 'new',
147+
},
148+
});
149+
await user.save(null, { sessionToken });
150+
151+
const updatedUser = await new Parse.Query(Parse.User).get(user.id, { useMasterKey: true });
152+
const finalAuthData = updatedUser.get('authData');
153+
154+
expect(finalAuthData.gpgames.id).toBe(MOCK_USER_ID);
155+
});
156+
});

src/RestWrite.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,15 @@ RestWrite.prototype.ensureUniqueAuthDataId = async function () {
538538
};
539539

540540
RestWrite.prototype.handleAuthData = async function (authData) {
541-
const r = await Auth.findUsersWithAuthData(this.config, authData, true);
541+
const withoutUnlinked = {};
542+
for (const provider of Object.keys(authData)) {
543+
if (authData[provider] === null || authData[provider] === undefined) {
544+
continue;
545+
}
546+
withoutUnlinked[provider] = authData[provider];
547+
}
548+
549+
const r = await Auth.findUsersWithAuthData(this.config, withoutUnlinked, true);
542550
const results = this.filteredObjectsByACL(r);
543551

544552
const userId = this.getUserId();

0 commit comments

Comments
 (0)