diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 90e45b9f55..afad5d3223 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -416,6 +416,7 @@ describe('AuthenticationProviders', function() { const options = { facebook: { appIds: ['a', 'b'], + appSecret: 'secret', }, }; const { @@ -428,6 +429,56 @@ describe('AuthenticationProviders', function() { expect(providerOptions).toEqual(options.facebook); }); + it('should handle Facebook appSecret for validating appIds', async () => { + const httpsRequest = require('../lib/Adapters/Auth/httpsRequest'); + spyOn(httpsRequest, 'get').and.callFake(() => { + return Promise.resolve({ id: 'a' }); + }); + const options = { + facebook: { + appIds: ['a', 'b'], + appSecret: 'secret_sauce', + }, + }; + const authData = { + access_token: 'badtoken', + }; + const { + adapter, + appIds, + providerOptions, + } = authenticationLoader.loadAuthAdapter('facebook', options); + await adapter.validateAppId(appIds, authData, providerOptions); + expect( + httpsRequest.get.calls.first().args[0].includes('appsecret_proof') + ).toBe(true); + }); + + it('should handle Facebook appSecret for validating auth data', async () => { + const httpsRequest = require('../lib/Adapters/Auth/httpsRequest'); + spyOn(httpsRequest, 'get').and.callFake(() => { + return Promise.resolve(); + }); + const options = { + facebook: { + appIds: ['a', 'b'], + appSecret: 'secret_sauce', + }, + }; + const authData = { + id: 'test', + access_token: 'test', + }; + const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( + 'facebook', + options + ); + await adapter.validateAuthData(authData, providerOptions); + expect( + httpsRequest.get.calls.first().args[0].includes('appsecret_proof') + ).toBe(true); + }); + it('properly loads a custom adapter with options', () => { const options = { custom: { diff --git a/src/Adapters/Auth/facebook.js b/src/Adapters/Auth/facebook.js index c6946dc00e..1ee0147aa2 100644 --- a/src/Adapters/Auth/facebook.js +++ b/src/Adapters/Auth/facebook.js @@ -1,11 +1,27 @@ // Helper functions for accessing the Facebook Graph API. const httpsRequest = require('./httpsRequest'); var Parse = require('parse/node').Parse; +const crypto = require('crypto'); + +function getAppSecretPath(authData, options = {}) { + const appSecret = options.appSecret; + if (!appSecret) { + return ''; + } + const appsecret_proof = crypto + .createHmac('sha256', appSecret) + .update(authData.access_token) + .digest('hex'); + + return `&appsecret_proof=${appsecret_proof}`; +} // Returns a promise that fulfills iff this user id is valid. -function validateAuthData(authData) { +function validateAuthData(authData, options) { return graphRequest( - 'me?fields=id&access_token=' + authData.access_token + 'me?fields=id&access_token=' + + authData.access_token + + getAppSecretPath(authData, options) ).then(data => { if ( (data && data.id == authData.id) || @@ -21,7 +37,7 @@ function validateAuthData(authData) { } // Returns a promise that fulfills iff this app id is valid. -function validateAppId(appIds, authData) { +function validateAppId(appIds, authData, options) { var access_token = authData.access_token; if (process.env.TESTING && access_token === 'test') { return Promise.resolve(); @@ -32,7 +48,9 @@ function validateAppId(appIds, authData) { 'Facebook auth is not configured.' ); } - return graphRequest('app?access_token=' + access_token).then(data => { + return graphRequest( + 'app?access_token=' + access_token + getAppSecretPath(authData, options) + ).then(data => { if (data && appIds.indexOf(data.id) != -1) { return; }