From fc9d1a0b55fce99c7a377c0e1978791fb1a27f06 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 7 Mar 2023 17:45:32 +1100 Subject: [PATCH 01/12] feat: initialize adapters at server start --- spec/AuthenticationAdapters.spec.js | 40 ++++++++------------ spec/AuthenticationAdaptersV2.spec.js | 4 +- spec/ParseUser.spec.js | 13 +++++++ src/Adapters/Auth/AuthAdapter.js | 20 +++++++--- src/Adapters/Auth/index.js | 53 ++++++++++++++++++--------- src/Auth.js | 12 +++--- src/Config.js | 5 +++ 7 files changed, 92 insertions(+), 55 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index bb89596cef..c37d9c3d7d 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -357,7 +357,7 @@ describe('AuthenticationProviders', function () { expect(typeof authAdapter.validateAppId).toBe('function'); } - it('properly loads custom adapter', done => { + it('properly loads custom adapter', async () => { const validAuthData = { id: 'hello', token: 'world', @@ -377,6 +377,9 @@ describe('AuthenticationProviders', function () { const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough(); const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough(); + authenticationLoader.initializeAuthAdapter('customAuthentication', { + customAuthentication: adapter, + }); const authenticationHandler = authenticationLoader({ customAuthentication: adapter, }); @@ -385,52 +388,38 @@ describe('AuthenticationProviders', function () { const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); validateValidator(validator); - validator(validAuthData, {}, {}).then( - () => { - expect(authDataSpy).toHaveBeenCalled(); - // AppIds are not provided in the adapter, should not be called - expect(appIdSpy).not.toHaveBeenCalled(); - done(); - }, - err => { - jfail(err); - done(); - } - ); + await validator(validAuthData, {}, {}); + expect(authDataSpy).toHaveBeenCalled(); + + expect(appIdSpy).not.toHaveBeenCalled(); }); - it('properly loads custom adapter module object', done => { - const authenticationHandler = authenticationLoader({ + it('properly loads custom adapter module object', async () => { + authenticationLoader.initializeAuthAdapter('customAuthentication', { customAuthentication: path.resolve('./spec/support/CustomAuth.js'), }); + const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); validateValidator(validator); - validator( + await validator( { token: 'my-token', }, {}, {} - ).then( - () => { - done(); - }, - err => { - jfail(err); - done(); - } ); }); it('properly loads custom adapter module object (again)', done => { - const authenticationHandler = authenticationLoader({ + authenticationLoader.initializeAuthAdapter('customAuthentication', { customAuthentication: { module: path.resolve('./spec/support/CustomAuthFunction.js'), options: { token: 'valid-token' }, }, }); + const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); @@ -552,6 +541,7 @@ describe('AuthenticationProviders', function () { id: 'test', access_token: 'test', }; + authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('facebook', options); await adapter.validateAuthData(authData, providerOptions); expect(httpsRequest.get.calls.first().args[0].includes('appsecret_proof')).toBe(true); diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index 9507691114..ac7a2c1607 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -349,6 +349,8 @@ describe('Auth Adapter features', () => { const spy = spyOn(modernAdapter3, 'validateOptions').and.callThrough(); const afterSpy = spyOn(modernAdapter3, 'afterFind').and.callThrough(); await reconfigureServer({ auth: { modernAdapter3 }, silent: false }); + expect(spy).toHaveBeenCalled(); + spy.calls.reset(); const user = new Parse.User(); await user.save({ authData: { modernAdapter3: { id: 'modernAdapter3Data' } } }); await user.fetch({ sessionToken: user.getSessionToken() }); @@ -366,7 +368,7 @@ describe('Auth Adapter features', () => { { id: 'modernAdapter3Data' }, undefined ); - expect(spy).toHaveBeenCalled(); + expect(spy).not.toHaveBeenCalled(); }); it('should throw if no triggers found', async () => { diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 4d3beaf349..80de81daf7 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -489,6 +489,19 @@ describe('Parse.User testing', () => { ); }); + it('cannot connect to unconfigured adapter', async () => { + await reconfigureServer({ + auth: {}, + }); + const provider = getMockFacebookProvider(); + Parse.User._registerAuthenticationProvider(provider); + const user = new Parse.User(); + user.set('foo', 'bar'); + await expectAsync(user._linkWith('facebook', {})).toBeRejectedWith( + new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.') + ); + }); + it('should not call beforeLogin with become', async done => { const provider = getMockFacebookProvider(); Parse.User._registerAuthenticationProvider(provider); diff --git a/src/Adapters/Auth/AuthAdapter.js b/src/Adapters/Auth/AuthAdapter.js index 5b18c75170..8d45ef380d 100644 --- a/src/Adapters/Auth/AuthAdapter.js +++ b/src/Adapters/Auth/AuthAdapter.js @@ -100,16 +100,26 @@ export class AuthAdapter { * @param {Object} options additional adapter options * @returns {Promise} Any overrides required to authData */ - afterFind(authData, options) { - return Promise.resolve({}); - } + afterFind(authData, options) {} /** * Triggered when the adapter is first attached to Parse Server * @param {Object} options Adapter Options */ - validateOptions(options) { - /* */ + validateOptions(options) {} + + _clearDefaultKeys(keys) { + const defaultAdapter = new AuthAdapter(); + keys.forEach(key => { + const existing = this[key]; + if ( + existing && + typeof existing === 'function' && + existing.toString() === defaultAdapter[key].toString() + ) { + this[key] = null; + } + }); } } diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 2defcb0dc0..059d7168f1 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -63,6 +63,8 @@ const providers = { ldap, }; +let authAdapters = {}; + // Indexed auth policies const authAdapterPolicies = { default: true, @@ -135,8 +137,7 @@ function authDataValidator(provider, adapter, appIds, options) { }; } -function loadAuthAdapter(provider, authOptions) { - // providers are auth providers implemented by default +function initializeAuthAdapter(provider, authOptions) { let defaultAdapter = providers[provider]; // authOptions can contain complete custom auth adapters or // a default auth adapter like Facebook @@ -154,8 +155,6 @@ function loadAuthAdapter(provider, authOptions) { return; } - const adapter = - defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter); const keys = [ 'validateAuthData', 'validateAppId', @@ -167,20 +166,13 @@ function loadAuthAdapter(provider, authOptions) { 'policy', 'afterFind', ]; - const defaultAuthAdapter = new AuthAdapter(); - keys.forEach(key => { - const existing = adapter?.[key]; - if ( - existing && - typeof existing === 'function' && - existing.toString() === defaultAuthAdapter[key].toString() - ) { - adapter[key] = null; - } - }); - const appIds = providerOptions ? providerOptions.appIds : undefined; - // Try the configuration methods + let adapter = Object.assign({}, defaultAdapter); + if (defaultAdapter instanceof AuthAdapter) { + adapter = new defaultAdapter.constructor(); + defaultAdapter._clearDefaultKeys(keys); + } + if (providerOptions) { const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); if (optionalAdapter) { @@ -191,13 +183,34 @@ function loadAuthAdapter(provider, authOptions) { }); } } + if (adapter.validateOptions) { + console.log({ provider }); adapter.validateOptions(providerOptions); } + if (providerOptions?.enabled !== false) { + authAdapters[provider] = adapter; + } +} + +function loadAuthAdapter(provider, authOptions) { + const adapter = authAdapters[provider]; + if (!adapter) { + return; + } + const providerOptions = authOptions[provider]; + const appIds = providerOptions ? providerOptions.appIds : undefined; return { adapter, appIds, providerOptions }; } +function validateAuthConfig(auth) { + authAdapters = {}; + // console.log({auth}); + // console.log(Object.keys(auth)); + Object.keys(auth).map(key => initializeAuthAdapter(key, auth, true)); +} + module.exports = function (authOptions = {}, enableAnonymousUsers = true) { let _enableAnonymousUsers = enableAnonymousUsers; const setEnableAnonymousUsers = function (enable) { @@ -209,7 +222,9 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { return { validator: undefined }; } const authAdapter = loadAuthAdapter(provider, authOptions); - if (!authAdapter) return; + if (!authAdapter) { + return { validator: undefined }; + } const { adapter, appIds, providerOptions } = authAdapter; return { validator: authDataValidator(provider, adapter, appIds, providerOptions), adapter }; }; @@ -251,4 +266,6 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { }); }; +module.exports.initializeAuthAdapter = initializeAuthAdapter; module.exports.loadAuthAdapter = loadAuthAdapter; +module.exports.validateAuthConfig = validateAuthConfig; diff --git a/src/Auth.js b/src/Auth.js index abd14391db..bd8668d4a2 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -443,18 +443,18 @@ const handleAuthDataValidation = async (authData, req, foundUser) => { } const { validator } = req.config.authDataManager.getValidatorForProvider(provider); const authProvider = (req.config.auth || {})[provider] || {}; - if (authProvider.enabled == null) { - Deprecator.logRuntimeDeprecation({ - usage: `Using the authentication adapter "${provider}" without explicitly enabling it`, - solution: `Enable the authentication adapter by setting the Parse Server option "auth.${provider}.enabled: true".`, - }); - } if (!validator || authProvider.enabled === false) { throw new Parse.Error( Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.' ); } + if (authProvider.enabled == null) { + Deprecator.logRuntimeDeprecation({ + usage: `Using the authentication adapter "${provider}" without explicitly enabling it`, + solution: `Enable the authentication adapter by setting the Parse Server option "auth.${provider}.enabled: true".`, + }); + } let validationResult = await validator(authData[provider], req, user, requestObject); method = validationResult && validationResult.method; requestObject.triggerName = method; diff --git a/src/Config.js b/src/Config.js index 2e7ef389c7..0311e568ed 100644 --- a/src/Config.js +++ b/src/Config.js @@ -7,6 +7,7 @@ import net from 'net'; import AppCache from './cache'; import DatabaseController from './Controllers/DatabaseController'; import { logLevels as validLogLevels } from './Controllers/LoggerController'; +import AuthAdapter from './Adapters/Auth'; import { AccountLockoutOptions, DatabaseOptions, @@ -86,6 +87,7 @@ export class Config { logLevels, rateLimit, databaseOptions, + auth, }) { if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); @@ -124,6 +126,9 @@ export class Config { this.validateRateLimit(rateLimit); this.validateLogLevels(logLevels); this.validateDatabaseOptions(databaseOptions); + if (auth) { + AuthAdapter.validateAuthConfig(auth); + } } static validateControllers({ From 043c40259d9dc1c89d09ca1acace79bd4cb37b82 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 7 Mar 2023 17:48:11 +1100 Subject: [PATCH 02/12] Update index.js --- src/Adapters/Auth/index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 059d7168f1..637231e187 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -184,12 +184,10 @@ function initializeAuthAdapter(provider, authOptions) { } } - if (adapter.validateOptions) { - console.log({ provider }); - adapter.validateOptions(providerOptions); - } - if (providerOptions?.enabled !== false) { + if (adapter.validateOptions) { + adapter.validateOptions(providerOptions); + } authAdapters[provider] = adapter; } } @@ -206,8 +204,6 @@ function loadAuthAdapter(provider, authOptions) { function validateAuthConfig(auth) { authAdapters = {}; - // console.log({auth}); - // console.log(Object.keys(auth)); Object.keys(auth).map(key => initializeAuthAdapter(key, auth, true)); } From 96ac3d8ac796f88fe7c4a2bb6c3d5b35ad4af4a9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 7 Mar 2023 18:31:26 +1100 Subject: [PATCH 03/12] wip --- spec/AuthenticationAdapters.spec.js | 17 +++++++++++++++++ spec/AuthenticationAdaptersV2.spec.js | 4 ++++ src/Adapters/Auth/index.js | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index c37d9c3d7d..d5d598662a 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -1024,6 +1024,7 @@ describe('oauth2 auth adapter', () => { oauth2: true, }, }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const loadedAuthAdapter = authenticationLoader.loadAuthAdapter('oauth2Authentication', options); expect(loadedAuthAdapter.adapter).toEqual(oauth2); }); @@ -1040,6 +1041,7 @@ describe('oauth2 auth adapter', () => { debug: true, }, }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const loadedAuthAdapter = authenticationLoader.loadAuthAdapter('oauth2Authentication', options); const appIds = loadedAuthAdapter.appIds; const providerOptions = loadedAuthAdapter.providerOptions; @@ -1063,6 +1065,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1087,6 +1090,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1111,6 +1115,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1137,6 +1142,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1163,6 +1169,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1190,6 +1197,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1219,6 +1227,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1250,6 +1259,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1281,6 +1291,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1310,6 +1321,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1338,6 +1350,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1379,6 +1392,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1422,6 +1436,7 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1781,6 +1796,7 @@ describe('Apple Game Center Auth adapter', () => { 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', }, }; + authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options @@ -1799,6 +1815,7 @@ describe('Apple Game Center Auth adapter', () => { 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', }, }; + authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index ac7a2c1607..e8ea0df724 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -1236,6 +1236,10 @@ describe('Auth Adapter features', () => { await user.save({ authData: { challengeAdapter: { id: 'challengeAdapter' } } }); spyOn(challengeAdapter, 'validateAuthData').and.rejectWith({}); + const authenticationLoader = require('../lib/Adapters/Auth'); + authenticationLoader.initializeAuthAdapter('challengeAdapter', { + challengeAdapter, + }); await expectAsync( requestWithExpectedError({ diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 637231e187..75add58d57 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 { isDeepStrictEqual } from 'util'; const apple = require('./apple'); const gcenter = require('./gcenter'); @@ -202,9 +203,17 @@ function loadAuthAdapter(provider, authOptions) { return { adapter, appIds, providerOptions }; } +let lastConfig = {}; function validateAuthConfig(auth) { + if (!auth.anonymous) { + auth.anonymous = { enabled: true }; + } + if (isDeepStrictEqual(lastConfig, auth)) { + return; + } authAdapters = {}; Object.keys(auth).map(key => initializeAuthAdapter(key, auth, true)); + lastConfig = auth; } module.exports = function (authOptions = {}, enableAnonymousUsers = true) { From 4bd02e5d8cd1e2916f5b8a2f9d9d4503fee61dbb Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 7 Mar 2023 18:46:06 +1100 Subject: [PATCH 04/12] tests --- spec/AuthenticationAdapters.spec.js | 11 +++++++++++ src/Routers/UsersRouter.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index d5d598662a..fc67e57f3f 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -449,6 +449,7 @@ describe('AuthenticationProviders', function () { appSecret: 'secret', }, }; + authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -495,6 +496,7 @@ describe('AuthenticationProviders', function () { const authData = { access_token: 'badtoken', }; + authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -824,6 +826,7 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -851,6 +854,7 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -878,6 +882,7 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -903,6 +908,7 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -936,6 +942,7 @@ describe('keycloak auth adapter', () => { roles: ['role1'], groups: ['group1'], }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -969,6 +976,7 @@ describe('keycloak auth adapter', () => { roles: ['role1'], groups: ['group1'], }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -1002,6 +1010,7 @@ describe('keycloak auth adapter', () => { roles: ['role1'], groups: ['group1'], }; + authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); await adapter.validateAuthData(authData, providerOptions); expect(httpsRequest.get).toHaveBeenCalledWith({ @@ -1842,6 +1851,7 @@ describe('Apple Game Center Auth adapter', () => { it('validateAuthData invalid signature id', async () => { gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert; gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-6.cer'] = testCert2; + authenticationLoader.initializeAuthAdapter('gcenter', {}); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', {} @@ -1989,6 +1999,7 @@ describe('phant auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; + authenticationLoader.initializeAuthAdapter('phantauth', {}); const { adapter } = authenticationLoader.loadAuthAdapter('phantauth', {}); spyOn(httpsRequest, 'get').and.callFake(() => Promise.resolve({ sub: 'invalidID' })); diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index feca46e802..4a8229da26 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -566,7 +566,7 @@ export class UsersRouter extends ClassesRouter { for (const provider of Object.keys(challengeData).sort()) { try { const authAdapter = req.config.authDataManager.getValidatorForProvider(provider); - if (!authAdapter) { + if (!authAdapter?.validator) { continue; } const { From 787902b4592e53fd1a4b8d04e6ee5458c5e0cded Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 8 Mar 2023 09:33:40 +1100 Subject: [PATCH 05/12] Update AuthenticationAdapters.spec.js --- spec/AuthenticationAdapters.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index fc67e57f3f..ca6005ead1 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -473,6 +473,7 @@ describe('AuthenticationProviders', function () { const authData = { access_token: 'badtoken', }; + authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -519,6 +520,7 @@ describe('AuthenticationProviders', function () { const authData = { access_token: 'badtoken', }; + authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -1946,6 +1948,7 @@ describe('Apple Game Center Auth adapter', () => { const options = { gcenter: {}, }; + authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options From 1b6ffd50a99253630c79be6da803913095900ac6 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 8 Mar 2023 09:58:16 +1100 Subject: [PATCH 06/12] Update AuthenticationAdapters.spec.js --- spec/AuthenticationAdapters.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index ca6005ead1..f53f65210b 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -1975,6 +1975,7 @@ describe('Apple Game Center Auth adapter', () => { rootCertificateUrl: 'https://example.com', }, }; + authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options From 12000c2a103d0d17807a6f6b959bcbead73fb012 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 14 Mar 2023 12:02:52 +1100 Subject: [PATCH 07/12] refactor --- spec/AuthenticationAdapters.spec.js | 45 ----------- spec/AuthenticationAdaptersV2.spec.js | 2 +- spec/helper.js | 2 + src/Adapters/Auth/index.js | 109 ++++++++++++-------------- 4 files changed, 53 insertions(+), 105 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index f53f65210b..83488bb4fa 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -377,9 +377,6 @@ describe('AuthenticationProviders', function () { const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough(); const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough(); - authenticationLoader.initializeAuthAdapter('customAuthentication', { - customAuthentication: adapter, - }); const authenticationHandler = authenticationLoader({ customAuthentication: adapter, }); @@ -395,9 +392,6 @@ describe('AuthenticationProviders', function () { }); it('properly loads custom adapter module object', async () => { - authenticationLoader.initializeAuthAdapter('customAuthentication', { - customAuthentication: path.resolve('./spec/support/CustomAuth.js'), - }); const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); @@ -413,12 +407,6 @@ describe('AuthenticationProviders', function () { }); it('properly loads custom adapter module object (again)', done => { - authenticationLoader.initializeAuthAdapter('customAuthentication', { - customAuthentication: { - module: path.resolve('./spec/support/CustomAuthFunction.js'), - options: { token: 'valid-token' }, - }, - }); const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); @@ -449,7 +437,6 @@ describe('AuthenticationProviders', function () { appSecret: 'secret', }, }; - authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -473,7 +460,6 @@ describe('AuthenticationProviders', function () { const authData = { access_token: 'badtoken', }; - authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -497,7 +483,6 @@ describe('AuthenticationProviders', function () { const authData = { access_token: 'badtoken', }; - authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -520,7 +505,6 @@ describe('AuthenticationProviders', function () { const authData = { access_token: 'badtoken', }; - authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'facebook', options @@ -545,7 +529,6 @@ describe('AuthenticationProviders', function () { id: 'test', access_token: 'test', }; - authenticationLoader.initializeAuthAdapter('facebook', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('facebook', options); await adapter.validateAuthData(authData, providerOptions); expect(httpsRequest.get.calls.first().args[0].includes('appsecret_proof')).toBe(true); @@ -828,7 +811,6 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -856,7 +838,6 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -884,7 +865,6 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -910,7 +890,6 @@ describe('keycloak auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -944,7 +923,6 @@ describe('keycloak auth adapter', () => { roles: ['role1'], groups: ['group1'], }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -978,7 +956,6 @@ describe('keycloak auth adapter', () => { roles: ['role1'], groups: ['group1'], }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); try { await adapter.validateAuthData(authData, providerOptions); @@ -1012,7 +989,6 @@ describe('keycloak auth adapter', () => { roles: ['role1'], groups: ['group1'], }; - authenticationLoader.initializeAuthAdapter('keycloak', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter('keycloak', options); await adapter.validateAuthData(authData, providerOptions); expect(httpsRequest.get).toHaveBeenCalledWith({ @@ -1035,7 +1011,6 @@ describe('oauth2 auth adapter', () => { oauth2: true, }, }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const loadedAuthAdapter = authenticationLoader.loadAuthAdapter('oauth2Authentication', options); expect(loadedAuthAdapter.adapter).toEqual(oauth2); }); @@ -1052,7 +1027,6 @@ describe('oauth2 auth adapter', () => { debug: true, }, }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const loadedAuthAdapter = authenticationLoader.loadAuthAdapter('oauth2Authentication', options); const appIds = loadedAuthAdapter.appIds; const providerOptions = loadedAuthAdapter.providerOptions; @@ -1076,7 +1050,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1101,7 +1074,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1126,7 +1098,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1153,7 +1124,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1180,7 +1150,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1208,7 +1177,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1238,7 +1206,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1270,7 +1237,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1302,7 +1268,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1332,7 +1297,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1361,7 +1325,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1403,7 +1366,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1447,7 +1409,6 @@ describe('oauth2 auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('oauth2Authentication', options); const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( 'oauth2Authentication', options @@ -1807,7 +1768,6 @@ describe('Apple Game Center Auth adapter', () => { 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', }, }; - authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options @@ -1826,7 +1786,6 @@ describe('Apple Game Center Auth adapter', () => { 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', }, }; - authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options @@ -1853,7 +1812,6 @@ describe('Apple Game Center Auth adapter', () => { it('validateAuthData invalid signature id', async () => { gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert; gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-6.cer'] = testCert2; - authenticationLoader.initializeAuthAdapter('gcenter', {}); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', {} @@ -1948,7 +1906,6 @@ describe('Apple Game Center Auth adapter', () => { const options = { gcenter: {}, }; - authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options @@ -1975,7 +1932,6 @@ describe('Apple Game Center Auth adapter', () => { rootCertificateUrl: 'https://example.com', }, }; - authenticationLoader.initializeAuthAdapter('gcenter', options); const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( 'gcenter', options @@ -2003,7 +1959,6 @@ describe('phant auth adapter', () => { id: 'fakeid', access_token: 'sometoken', }; - authenticationLoader.initializeAuthAdapter('phantauth', {}); const { adapter } = authenticationLoader.loadAuthAdapter('phantauth', {}); spyOn(httpsRequest, 'get').and.callFake(() => Promise.resolve({ sub: 'invalidID' })); diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index e8ea0df724..778aa7f019 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -1237,7 +1237,7 @@ describe('Auth Adapter features', () => { spyOn(challengeAdapter, 'validateAuthData').and.rejectWith({}); const authenticationLoader = require('../lib/Adapters/Auth'); - authenticationLoader.initializeAuthAdapter('challengeAdapter', { + authenticationLoader.loadAuthAdapter('challengeAdapter', { challengeAdapter, }); diff --git a/spec/helper.js b/spec/helper.js index 445de26509..c9b870090f 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -4,6 +4,7 @@ const semver = require('semver'); const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); const { SpecReporter } = require('jasmine-spec-reporter'); const SchemaCache = require('../lib/Adapters/Cache/SchemaCache').default; +const AuthAdapters = require('../lib/Adapters/Auth'); // Ensure localhost resolves to ipv4 address first on node v17+ if (dns.setDefaultResultOrder) { @@ -210,6 +211,7 @@ afterEach(function (done) { destroyAliveConnections(); await TestUtils.destroyAllDataPermanently(true); SchemaCache.clear(); + AuthAdapters.validateAuthConfig(defaultConfiguration.auth); if (didChangeConfiguration) { await reconfigureServer(); } else { diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 75add58d57..2b52720835 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -1,7 +1,6 @@ import loadAdapter from '../AdapterLoader'; import Parse from 'parse/node'; import AuthAdapter from './AuthAdapter'; -import { isDeepStrictEqual } from 'util'; const apple = require('./apple'); const gcenter = require('./gcenter'); @@ -64,7 +63,7 @@ const providers = { ldap, }; -let authAdapters = {}; +const authAdapters = {}; // Indexed auth policies const authAdapterPolicies = { @@ -138,62 +137,61 @@ function authDataValidator(provider, adapter, appIds, options) { }; } -function initializeAuthAdapter(provider, authOptions) { - let defaultAdapter = providers[provider]; - // authOptions can contain complete custom auth adapters or - // a default auth adapter like Facebook - const providerOptions = authOptions[provider]; - if ( - providerOptions && - Object.prototype.hasOwnProperty.call(providerOptions, 'oauth2') && - providerOptions['oauth2'] === true - ) { - defaultAdapter = oauth2; - } +function loadAuthAdapter(provider, authOptions, cached) { + if (!cached) { + let defaultAdapter = providers[provider]; + // authOptions can contain complete custom auth adapters or + // a default auth adapter like Facebook + const providerOptions = authOptions[provider]; + if ( + providerOptions && + Object.prototype.hasOwnProperty.call(providerOptions, 'oauth2') && + providerOptions['oauth2'] === true + ) { + defaultAdapter = oauth2; + } - // Default provider not found and a custom auth provider was not provided - if (!defaultAdapter && !providerOptions) { - return; - } + // Default provider not found and a custom auth provider was not provided + if (!defaultAdapter && !providerOptions) { + return; + } - const keys = [ - 'validateAuthData', - 'validateAppId', - 'validateSetUp', - 'validateLogin', - 'validateUpdate', - 'challenge', - 'validateOptions', - 'policy', - 'afterFind', - ]; + const keys = [ + 'validateAuthData', + 'validateAppId', + 'validateSetUp', + 'validateLogin', + 'validateUpdate', + 'challenge', + 'validateOptions', + 'policy', + 'afterFind', + ]; - let adapter = Object.assign({}, defaultAdapter); - if (defaultAdapter instanceof AuthAdapter) { - adapter = new defaultAdapter.constructor(); - defaultAdapter._clearDefaultKeys(keys); - } + let adapter = Object.assign({}, defaultAdapter); + if (defaultAdapter instanceof AuthAdapter) { + adapter = new defaultAdapter.constructor(); + defaultAdapter._clearDefaultKeys(keys); + } - if (providerOptions) { - const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); - if (optionalAdapter) { - keys.forEach(key => { - if (optionalAdapter[key]) { - adapter[key] = optionalAdapter[key]; - } - }); + if (providerOptions) { + const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); + if (optionalAdapter) { + keys.forEach(key => { + if (optionalAdapter[key]) { + adapter[key] = optionalAdapter[key]; + } + }); + } } - } - if (providerOptions?.enabled !== false) { - if (adapter.validateOptions) { - adapter.validateOptions(providerOptions); + if (providerOptions?.enabled !== false) { + if (adapter.validateOptions) { + adapter.validateOptions(providerOptions); + } + authAdapters[provider] = adapter; } - authAdapters[provider] = adapter; } -} - -function loadAuthAdapter(provider, authOptions) { const adapter = authAdapters[provider]; if (!adapter) { return; @@ -203,17 +201,11 @@ function loadAuthAdapter(provider, authOptions) { return { adapter, appIds, providerOptions }; } -let lastConfig = {}; function validateAuthConfig(auth) { if (!auth.anonymous) { auth.anonymous = { enabled: true }; } - if (isDeepStrictEqual(lastConfig, auth)) { - return; - } - authAdapters = {}; - Object.keys(auth).map(key => initializeAuthAdapter(key, auth, true)); - lastConfig = auth; + Object.keys(auth).forEach(key => loadAuthAdapter(key, auth)); } module.exports = function (authOptions = {}, enableAnonymousUsers = true) { @@ -226,7 +218,7 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { if (provider === 'anonymous' && !_enableAnonymousUsers) { return { validator: undefined }; } - const authAdapter = loadAuthAdapter(provider, authOptions); + const authAdapter = loadAuthAdapter(provider, authOptions, true); if (!authAdapter) { return { validator: undefined }; } @@ -271,6 +263,5 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { }); }; -module.exports.initializeAuthAdapter = initializeAuthAdapter; module.exports.loadAuthAdapter = loadAuthAdapter; module.exports.validateAuthConfig = validateAuthConfig; From a1792537e810e3a77756a7276fac6603e3259095 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 14 Mar 2023 12:57:16 +1100 Subject: [PATCH 08/12] wip --- spec/AuthenticationAdapters.spec.js | 30 +++++++++++++++++++---------- src/Adapters/Auth/index.js | 3 ++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 83488bb4fa..13eec932f7 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -377,6 +377,8 @@ describe('AuthenticationProviders', function () { const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough(); const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough(); + await reconfigureServer({ auth: { customAuthentication: adapter } }); + const authenticationHandler = authenticationLoader({ customAuthentication: adapter, }); @@ -392,6 +394,14 @@ describe('AuthenticationProviders', function () { }); it('properly loads custom adapter module object', async () => { + await reconfigureServer({ + auth: { + customAuthentication: { + validateAppId() {}, + validateAuthData() {}, + }, + }, + }); const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); @@ -406,27 +416,27 @@ describe('AuthenticationProviders', function () { ); }); - it('properly loads custom adapter module object (again)', done => { + it('properly loads custom adapter module object (again)', async () => { + await reconfigureServer({ + auth: { + customAuthentication: { + validateAppId() {}, + validateAuthData() {}, + }, + }, + }); const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); validateValidator(validator); - validator( + await validator( { token: 'valid-token', }, {}, {} - ).then( - () => { - done(); - }, - err => { - jfail(err); - done(); - } ); }); diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 2b52720835..05683a5937 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -63,7 +63,7 @@ const providers = { ldap, }; -const authAdapters = {}; +let authAdapters = {}; // Indexed auth policies const authAdapterPolicies = { @@ -202,6 +202,7 @@ function loadAuthAdapter(provider, authOptions, cached) { } function validateAuthConfig(auth) { + authAdapters = {}; if (!auth.anonymous) { auth.anonymous = { enabled: true }; } From 230efddc81daf134f868972ee7fed3e4e2ec7fad Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 8 Jun 2023 12:54:06 +1000 Subject: [PATCH 09/12] wip --- package-lock.json | 2 +- spec/AuthenticationAdapters.spec.js | 2 +- spec/helper.js | 2 +- src/Adapters/Auth/index.js | 20 +++++++++++++------- src/Config.js | 7 +++---- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index deed9e6054..a71afdc1d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "all-node-versions": "11.3.0", "apollo-upload-client": "17.0.0", "bcrypt-nodejs": "0.0.3", - "clean-jsdoc-theme": "^4.2.7", + "clean-jsdoc-theme": "4.2.7", "cross-env": "7.0.2", "deep-diff": "1.0.2", "eslint": "8.26.0", diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 13eec932f7..e3683ab056 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -18,7 +18,7 @@ const responses = { microsoft: { id: 'userId', mail: 'userMail' }, }; -describe('AuthenticationProviders', function () { +fdescribe('AuthenticationProviders', function () { [ 'apple', 'gcenter', diff --git a/spec/helper.js b/spec/helper.js index c9b870090f..94468c2fde 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -83,7 +83,7 @@ on_db( ); let logLevel; -let silent = true; +let silent = false; if (process.env.VERBOSE) { silent = false; logLevel = 'verbose'; diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 05683a5937..3c42e89d4c 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -1,7 +1,7 @@ import loadAdapter from '../AdapterLoader'; import Parse from 'parse/node'; import AuthAdapter from './AuthAdapter'; - +import Config from '../../Config'; const apple = require('./apple'); const gcenter = require('./gcenter'); const gpgames = require('./gpgames'); @@ -63,8 +63,6 @@ const providers = { ldap, }; -let authAdapters = {}; - // Indexed auth policies const authAdapterPolicies = { default: true, @@ -138,6 +136,7 @@ function authDataValidator(provider, adapter, appIds, options) { } function loadAuthAdapter(provider, authOptions, cached) { + const config = Config.get(Parse.applicationId); if (!cached) { let defaultAdapter = providers[provider]; // authOptions can contain complete custom auth adapters or @@ -189,10 +188,13 @@ function loadAuthAdapter(provider, authOptions, cached) { if (adapter.validateOptions) { adapter.validateOptions(providerOptions); } - authAdapters[provider] = adapter; + if (!config.authCache) { + config.authCache = new Map(); + } + config.authCache.set(provider, adapter); } } - const adapter = authAdapters[provider]; + const adapter = config.authCache?.get(provider); if (!adapter) { return; } @@ -202,11 +204,15 @@ function loadAuthAdapter(provider, authOptions, cached) { } function validateAuthConfig(auth) { - authAdapters = {}; + const authCache = new Map(); if (!auth.anonymous) { auth.anonymous = { enabled: true }; } - Object.keys(auth).forEach(key => loadAuthAdapter(key, auth)); + Object.keys(auth).forEach(key => { + const adapter = loadAuthAdapter(key, auth); + authCache.set(key, adapter); + }); + return authCache; } module.exports = function (authOptions = {}, enableAnonymousUsers = true) { diff --git a/src/Config.js b/src/Config.js index cac3e4a1ed..cd0675ff56 100644 --- a/src/Config.js +++ b/src/Config.js @@ -58,6 +58,9 @@ export class Config { Config.validateControllers(serverConfiguration); AppCache.put(serverConfiguration.appId, serverConfiguration); Config.setupPasswordValidator(serverConfiguration.passwordPolicy); + if (serverConfiguration.auth) { + serverConfiguration.authStore = AuthAdapter.validateAuthConfig(serverConfiguration.auth); + } return serverConfiguration; } @@ -87,7 +90,6 @@ export class Config { logLevels, rateLimit, databaseOptions, - auth, extendSessionOnUse, }) { if (masterKey === readOnlyMasterKey) { @@ -131,9 +133,6 @@ export class Config { this.validateRateLimit(rateLimit); this.validateLogLevels(logLevels); this.validateDatabaseOptions(databaseOptions); - if (auth) { - AuthAdapter.validateAuthConfig(auth); - } } static validateControllers({ From f56ed3ea82f01674bcfb30a5794b209a8ad0ecf5 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 8 Jun 2023 14:32:12 +1000 Subject: [PATCH 10/12] refactor --- package-lock.json | 7 ++- spec/AuthenticationAdapters.spec.js | 92 +++++++++++++++++++++-------- spec/helper.js | 2 - src/Adapters/Auth/index.js | 22 +++---- src/Config.js | 4 -- 5 files changed, 80 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1333a85173..cb900c5590 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "pluralize": "8.0.0", "rate-limit-redis": "3.0.2", "redis": "4.6.6", - "semver": "^7.5.1", + "semver": "7.5.1", "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.0", @@ -15213,6 +15213,11 @@ "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, "engines": { "node": ">=0.10.0" } diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index e3683ab056..b8addbf456 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -18,7 +18,7 @@ const responses = { microsoft: { id: 'userId', mail: 'userMail' }, }; -fdescribe('AuthenticationProviders', function () { +describe('AuthenticationProviders', function () { [ 'apple', 'gcenter', @@ -357,7 +357,7 @@ fdescribe('AuthenticationProviders', function () { expect(typeof authAdapter.validateAppId).toBe('function'); } - it('properly loads custom adapter', async () => { + it('properly loads custom adapter', done => { const validAuthData = { id: 'hello', token: 'world', @@ -377,8 +377,6 @@ fdescribe('AuthenticationProviders', function () { const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough(); const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough(); - await reconfigureServer({ auth: { customAuthentication: adapter } }); - const authenticationHandler = authenticationLoader({ customAuthentication: adapter, }); @@ -387,56 +385,98 @@ fdescribe('AuthenticationProviders', function () { const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); validateValidator(validator); - await validator(validAuthData, {}, {}); - expect(authDataSpy).toHaveBeenCalled(); - - expect(appIdSpy).not.toHaveBeenCalled(); + validator(validAuthData, {}, {}).then( + () => { + expect(authDataSpy).toHaveBeenCalled(); + // AppIds are not provided in the adapter, should not be called + expect(appIdSpy).not.toHaveBeenCalled(); + done(); + }, + err => { + jfail(err); + done(); + } + ); }); - it('properly loads custom adapter module object', async () => { - await reconfigureServer({ - auth: { - customAuthentication: { - validateAppId() {}, - validateAuthData() {}, - }, + it('should cache adapter', async () => { + const adapter = { + validateAppId() { + return Promise.resolve(); + }, + validateAuthData() { + return Promise.resolve(); }, + validateOptions() {}, + }; + + const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough(); + const optionsSpy = spyOn(adapter, 'validateOptions').and.callThrough(); + + await reconfigureServer({ auth: { customAuthentication: adapter } }); + + expect(optionsSpy).toHaveBeenCalled(); + await Parse.User.logInWith('customAuthentication', { + authData: { id: 'user1', token: 'fakeToken1' }, + }); + await Parse.User.logInWith('customAuthentication', { + authData: { id: 'user2', token: 'fakeToken2' }, + }); + expect(authDataSpy).toHaveBeenCalled(); + expect(optionsSpy).toHaveBeenCalledTimes(1); + }); + + it('properly loads custom adapter module object', done => { + const authenticationHandler = authenticationLoader({ + customAuthentication: path.resolve('./spec/support/CustomAuth.js'), }); - const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); validateValidator(validator); - await validator( + validator( { token: 'my-token', }, {}, {} + ).then( + () => { + done(); + }, + err => { + jfail(err); + done(); + } ); }); - it('properly loads custom adapter module object (again)', async () => { - await reconfigureServer({ - auth: { - customAuthentication: { - validateAppId() {}, - validateAuthData() {}, - }, + it('properly loads custom adapter module object (again)', done => { + const authenticationHandler = authenticationLoader({ + customAuthentication: { + module: path.resolve('./spec/support/CustomAuthFunction.js'), + options: { token: 'valid-token' }, }, }); - const authenticationHandler = authenticationLoader(); validateAuthenticationHandler(authenticationHandler); const { validator } = authenticationHandler.getValidatorForProvider('customAuthentication'); validateValidator(validator); - await validator( + validator( { token: 'valid-token', }, {}, {} + ).then( + () => { + done(); + }, + err => { + jfail(err); + done(); + } ); }); diff --git a/spec/helper.js b/spec/helper.js index 94468c2fde..3c8aa785e6 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -4,7 +4,6 @@ const semver = require('semver'); const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); const { SpecReporter } = require('jasmine-spec-reporter'); const SchemaCache = require('../lib/Adapters/Cache/SchemaCache').default; -const AuthAdapters = require('../lib/Adapters/Auth'); // Ensure localhost resolves to ipv4 address first on node v17+ if (dns.setDefaultResultOrder) { @@ -211,7 +210,6 @@ afterEach(function (done) { destroyAliveConnections(); await TestUtils.destroyAllDataPermanently(true); SchemaCache.clear(); - AuthAdapters.validateAuthConfig(defaultConfiguration.auth); if (didChangeConfiguration) { await reconfigureServer(); } else { diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 3c42e89d4c..dd5851b8a5 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -1,7 +1,6 @@ import loadAdapter from '../AdapterLoader'; import Parse from 'parse/node'; import AuthAdapter from './AuthAdapter'; -import Config from '../../Config'; const apple = require('./apple'); const gcenter = require('./gcenter'); const gpgames = require('./gpgames'); @@ -135,9 +134,8 @@ function authDataValidator(provider, adapter, appIds, options) { }; } -function loadAuthAdapter(provider, authOptions, cached) { - const config = Config.get(Parse.applicationId); - if (!cached) { +function loadAuthAdapter(provider, authOptions, adapter) { + if (!adapter) { let defaultAdapter = providers[provider]; // authOptions can contain complete custom auth adapters or // a default auth adapter like Facebook @@ -167,7 +165,7 @@ function loadAuthAdapter(provider, authOptions, cached) { 'afterFind', ]; - let adapter = Object.assign({}, defaultAdapter); + adapter = Object.assign({}, defaultAdapter); if (defaultAdapter instanceof AuthAdapter) { adapter = new defaultAdapter.constructor(); defaultAdapter._clearDefaultKeys(keys); @@ -188,13 +186,8 @@ function loadAuthAdapter(provider, authOptions, cached) { if (adapter.validateOptions) { adapter.validateOptions(providerOptions); } - if (!config.authCache) { - config.authCache = new Map(); - } - config.authCache.set(provider, adapter); } } - const adapter = config.authCache?.get(provider); if (!adapter) { return; } @@ -209,8 +202,8 @@ function validateAuthConfig(auth) { auth.anonymous = { enabled: true }; } Object.keys(auth).forEach(key => { - const adapter = loadAuthAdapter(key, auth); - authCache.set(key, adapter); + const authObject = loadAuthAdapter(key, auth); + authCache.set(key, authObject.adapter); }); return authCache; } @@ -220,12 +213,13 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { const setEnableAnonymousUsers = function (enable) { _enableAnonymousUsers = enable; }; + const authCache = validateAuthConfig(authOptions); // To handle the test cases on configuration const getValidatorForProvider = function (provider) { if (provider === 'anonymous' && !_enableAnonymousUsers) { return { validator: undefined }; } - const authAdapter = loadAuthAdapter(provider, authOptions, true); + const authAdapter = loadAuthAdapter(provider, authOptions, authCache.get(provider)); if (!authAdapter) { return { validator: undefined }; } @@ -267,8 +261,8 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { getValidatorForProvider, setEnableAnonymousUsers, runAfterFind, + authCache, }); }; module.exports.loadAuthAdapter = loadAuthAdapter; -module.exports.validateAuthConfig = validateAuthConfig; diff --git a/src/Config.js b/src/Config.js index 31a38059d9..f63b5d47da 100644 --- a/src/Config.js +++ b/src/Config.js @@ -7,7 +7,6 @@ import net from 'net'; import AppCache from './cache'; import DatabaseController from './Controllers/DatabaseController'; import { logLevels as validLogLevels } from './Controllers/LoggerController'; -import AuthAdapter from './Adapters/Auth'; import { AccountLockoutOptions, DatabaseOptions, @@ -58,9 +57,6 @@ export class Config { Config.validateControllers(serverConfiguration); AppCache.put(serverConfiguration.appId, serverConfiguration); Config.setupPasswordValidator(serverConfiguration.passwordPolicy); - if (serverConfiguration.auth) { - serverConfiguration.authStore = AuthAdapter.validateAuthConfig(serverConfiguration.auth); - } return serverConfiguration; } From 631835c93838e1a5294d95308076ba7189bdc5d5 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 8 Jun 2023 15:27:28 +1000 Subject: [PATCH 11/12] tests --- spec/AuthenticationAdaptersV2.spec.js | 6 +- src/Adapters/Auth/index.js | 99 +++++++++++++-------------- 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index 778aa7f019..ceecb9b308 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -1236,9 +1236,9 @@ describe('Auth Adapter features', () => { await user.save({ authData: { challengeAdapter: { id: 'challengeAdapter' } } }); spyOn(challengeAdapter, 'validateAuthData').and.rejectWith({}); - const authenticationLoader = require('../lib/Adapters/Auth'); - authenticationLoader.loadAuthAdapter('challengeAdapter', { - challengeAdapter, + + await reconfigureServer({ + auth: { challengeAdapter, soloAdapter }, }); await expectAsync( diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index dd5851b8a5..6691a9da6a 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -134,64 +134,59 @@ function authDataValidator(provider, adapter, appIds, options) { }; } -function loadAuthAdapter(provider, authOptions, adapter) { - if (!adapter) { - let defaultAdapter = providers[provider]; - // authOptions can contain complete custom auth adapters or - // a default auth adapter like Facebook - const providerOptions = authOptions[provider]; - if ( - providerOptions && - Object.prototype.hasOwnProperty.call(providerOptions, 'oauth2') && - providerOptions['oauth2'] === true - ) { - defaultAdapter = oauth2; - } +function loadAuthAdapter(provider, authOptions) { + let defaultAdapter = providers[provider]; + // authOptions can contain complete custom auth adapters or + // a default auth adapter like Facebook + const providerOptions = authOptions[provider]; + if ( + providerOptions && + Object.prototype.hasOwnProperty.call(providerOptions, 'oauth2') && + providerOptions['oauth2'] === true + ) { + defaultAdapter = oauth2; + } - // Default provider not found and a custom auth provider was not provided - if (!defaultAdapter && !providerOptions) { - return; - } + // Default provider not found and a custom auth provider was not provided + if (!defaultAdapter && !providerOptions) { + return; + } - const keys = [ - 'validateAuthData', - 'validateAppId', - 'validateSetUp', - 'validateLogin', - 'validateUpdate', - 'challenge', - 'validateOptions', - 'policy', - 'afterFind', - ]; + const keys = [ + 'validateAuthData', + 'validateAppId', + 'validateSetUp', + 'validateLogin', + 'validateUpdate', + 'challenge', + 'validateOptions', + 'policy', + 'afterFind', + ]; - adapter = Object.assign({}, defaultAdapter); - if (defaultAdapter instanceof AuthAdapter) { - adapter = new defaultAdapter.constructor(); - defaultAdapter._clearDefaultKeys(keys); - } + let adapter = Object.assign({}, defaultAdapter); + if (defaultAdapter instanceof AuthAdapter) { + adapter = new defaultAdapter.constructor(); + defaultAdapter._clearDefaultKeys(keys); + } - if (providerOptions) { - const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); - if (optionalAdapter) { - keys.forEach(key => { - if (optionalAdapter[key]) { - adapter[key] = optionalAdapter[key]; - } - }); - } + if (providerOptions) { + const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); + if (optionalAdapter) { + keys.forEach(key => { + if (optionalAdapter[key]) { + adapter[key] = optionalAdapter[key]; + } + }); } + } - if (providerOptions?.enabled !== false) { - if (adapter.validateOptions) { - adapter.validateOptions(providerOptions); - } + if (providerOptions?.enabled !== false) { + if (adapter.validateOptions) { + adapter.validateOptions(providerOptions); } } - if (!adapter) { - return; - } - const providerOptions = authOptions[provider]; + const appIds = providerOptions ? providerOptions.appIds : undefined; return { adapter, appIds, providerOptions }; } @@ -203,7 +198,7 @@ function validateAuthConfig(auth) { } Object.keys(auth).forEach(key => { const authObject = loadAuthAdapter(key, auth); - authCache.set(key, authObject.adapter); + authCache.set(key, authObject); }); return authCache; } @@ -219,7 +214,7 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { if (provider === 'anonymous' && !_enableAnonymousUsers) { return { validator: undefined }; } - const authAdapter = loadAuthAdapter(provider, authOptions, authCache.get(provider)); + const authAdapter = authCache.get(provider); if (!authAdapter) { return { validator: undefined }; } From 9419c53009c1f848ed00dc0ce84ebdb973925a58 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 8 Jun 2023 15:45:30 +1000 Subject: [PATCH 12/12] Update helper.js --- spec/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helper.js b/spec/helper.js index 3c8aa785e6..445de26509 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -82,7 +82,7 @@ on_db( ); let logLevel; -let silent = false; +let silent = true; if (process.env.VERBOSE) { silent = false; logLevel = 'verbose';