Skip to content

Commit 4d27893

Browse files
committed
fix: Prevent unindexed database query for unconfigured auth providers
1 parent 48cc262 commit 4d27893

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

spec/vulnerabilities.spec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4141,4 +4141,68 @@ describe('(GHSA-6qh5-m6g3-xhq6) LiveQuery query depth DoS via deeply nested subs
41414141
const subscription = await query.subscribe();
41424142
expect(subscription).toBeDefined();
41434143
});
4144+
4145+
describe('(GHSA-g4cf-xj29-wqqr) DoS via unindexed database query for unconfigured auth providers', () => {
4146+
it('should not query database for unconfigured auth provider on signup', async () => {
4147+
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
4148+
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
4149+
await expectAsync(
4150+
new Parse.User().save({ authData: { nonExistentProvider: { id: 'test123' } } })
4151+
).toBeRejectedWith(
4152+
new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.')
4153+
);
4154+
const authDataQueries = spy.calls.all().filter(call => {
4155+
const query = call.args[2];
4156+
return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
4157+
});
4158+
expect(authDataQueries.length).toBe(0);
4159+
});
4160+
4161+
it('should not query database for unconfigured auth provider on challenge', async () => {
4162+
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
4163+
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
4164+
await expectAsync(
4165+
request({
4166+
method: 'POST',
4167+
url: Parse.serverURL + '/challenge',
4168+
headers: {
4169+
'X-Parse-Application-Id': Parse.applicationId,
4170+
'X-Parse-REST-API-Key': 'rest',
4171+
'Content-Type': 'application/json',
4172+
},
4173+
body: JSON.stringify({
4174+
authData: { nonExistentProvider: { id: 'test123' } },
4175+
challengeData: { nonExistentProvider: { token: 'abc' } },
4176+
}),
4177+
})
4178+
).toBeRejected();
4179+
const authDataQueries = spy.calls.all().filter(call => {
4180+
const query = call.args[2];
4181+
return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
4182+
});
4183+
expect(authDataQueries.length).toBe(0);
4184+
});
4185+
4186+
it('should still query database for configured auth provider', async () => {
4187+
await reconfigureServer({
4188+
auth: {
4189+
myConfiguredProvider: {
4190+
module: {
4191+
validateAppId: () => Promise.resolve(),
4192+
validateAuthData: () => Promise.resolve(),
4193+
},
4194+
},
4195+
},
4196+
});
4197+
const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
4198+
const spy = spyOn(databaseAdapter, 'find').and.callThrough();
4199+
const user = new Parse.User();
4200+
await user.save({ authData: { myConfiguredProvider: { id: 'validId', token: 'validToken' } } });
4201+
const authDataQueries = spy.calls.all().filter(call => {
4202+
const query = call.args[2];
4203+
return query?.$or?.some(q => q['authData.myConfiguredProvider.id']);
4204+
});
4205+
expect(authDataQueries.length).toBeGreaterThan(0);
4206+
});
4207+
});
41444208
});

src/Auth.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,13 @@ const findUsersWithAuthData = async (config, authData, beforeFind, currentUserAu
448448
const isUnchanged = storedProviderData && incomingKeys.length > 0 &&
449449
!incomingKeys.some(key => !isDeepStrictEqual(providerAuthData[key], storedProviderData[key]));
450450

451-
const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
451+
const validatorConfig = config.authDataManager.getValidatorForProvider(provider);
452+
// Skip database query for unconfigured providers to avoid unindexed collection scans;
453+
// the provider will be rejected later in handleAuthDataValidation with UNSUPPORTED_SERVICE
454+
if (!validatorConfig?.validator) {
455+
return null;
456+
}
457+
const adapter = validatorConfig.adapter;
452458
if (beforeFind && typeof adapter?.beforeFind === 'function' && !isUnchanged) {
453459
await adapter.beforeFind(providerAuthData);
454460
}

0 commit comments

Comments
 (0)