diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index cafb309f0b..a74c467921 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -1702,4 +1702,36 @@ describe('Auth Adapter features', () => { expect(res.status).toBeLessThan(500); }); }); + + it('reads a user with legacy authData for a now-misconfigured provider (#9885)', async () => { + let optionsShouldThrow = false; + const legacyProvider = { + validateAppId: () => Promise.resolve(), + validateSetUp: () => Promise.resolve(), + validateUpdate: () => Promise.resolve(), + validateLogin: () => Promise.resolve(), + validateOptions: () => { + // Mirrors e.g. the Twitter adapter throwing when its keys are not (or no + // longer) configured on the server. + if (optionsShouldThrow) { + throw new Error('Consumer key and secret are required.'); + } + }, + afterFind: () => ({ id: 'user1' }), + }; + + await reconfigureServer({ auth: { legacyProvider } }); + + const user = new Parse.User(); + await user.save({ authData: { legacyProvider: { id: 'user1' } } }); + expect(user.id).toBeDefined(); + + // The provider becomes unconfigured/misconfigured after the user already + // carries its authData; reading that user must not fail. + optionsShouldThrow = true; + + const query = new Parse.Query(Parse.User); + const fetched = await query.get(user.id, { useMasterKey: true }); + expect(fetched.id).toEqual(user.id); + }); }); diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 51dd5342ef..f7f9e02b75 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -1,6 +1,7 @@ import loadAdapter from '../AdapterLoader'; import Parse from 'parse/node'; import AuthAdapter from './AuthAdapter'; +import logger from '../../logger'; const apple = require('./apple'); const digits = require('./twitter'); // digits tokens are validated by twitter @@ -228,7 +229,16 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { const adapters = Object.keys(authData); await Promise.all( adapters.map(async provider => { - const authAdapter = getValidatorForProvider(provider); + let authAdapter; + try { + authAdapter = getValidatorForProvider(provider); + } catch (e) { + // A provider that is no longer configured (or misconfigured) throws in + // validateOptions; that must not break reads of users still carrying its + // legacy authData (#9885). Skip it, as getProviders does for the same call. + logger.verbose(`Skipping afterFind for unconfigured auth provider "${provider}": ${e}`); + return; + } if (!authAdapter) { return; }