From 9f6aa61b9c2c5fbf4bc5d0884e24f2955b6cad12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Abra=CC=83o?= Date: Mon, 24 Oct 2016 16:35:40 -0200 Subject: [PATCH 1/3] - Method added to prototype that handle requested params that does not exist on FB profile response; - Object "processed" added to profile returned object which include all requested params with a null value when FB does not provide it. - Test cases - IIFE pattern and use strict added to strategy.js --- lib/strategy.js | 490 ++++++++++++++++++---------------- test/strategy.profile.test.js | 40 ++- 2 files changed, 299 insertions(+), 231 deletions(-) diff --git a/lib/strategy.js b/lib/strategy.js index f7881b1..9282b88 100644 --- a/lib/strategy.js +++ b/lib/strategy.js @@ -1,240 +1,284 @@ // Load modules. -var OAuth2Strategy = require('passport-oauth2') - , util = require('util') - , uri = require('url') - , crypto = require('crypto') - , Profile = require('./profile') - , InternalOAuthError = require('passport-oauth2').InternalOAuthError - , FacebookAuthorizationError = require('./errors/facebookauthorizationerror') - , FacebookTokenError = require('./errors/facebooktokenerror') - , FacebookGraphAPIError = require('./errors/facebookgraphapierror'); - - -/** - * `Strategy` constructor. - * - * The Facebook authentication strategy authenticates requests by delegating to - * Facebook using the OAuth 2.0 protocol. - * - * Applications must supply a `verify` callback which accepts an `accessToken`, - * `refreshToken` and service-specific `profile`, and then calls the `cb` - * callback supplying a `user`, which should be set to `false` if the - * credentials are not valid. If an exception occured, `err` should be set. - * - * Options: - * - `clientID` your Facebook application's App ID - * - `clientSecret` your Facebook application's App Secret - * - `callbackURL` URL to which Facebook will redirect the user after granting authorization - * - * Examples: - * - * passport.use(new FacebookStrategy({ +(function () { + "use strict"; + + var OAuth2Strategy = require('passport-oauth2') + , util = require('util') + , uri = require('url') + , crypto = require('crypto') + , Profile = require('./profile') + , InternalOAuthError = require('passport-oauth2').InternalOAuthError + , FacebookAuthorizationError = require('./errors/facebookauthorizationerror') + , FacebookTokenError = require('./errors/facebooktokenerror') + , FacebookGraphAPIError = require('./errors/facebookgraphapierror'); + + /** + * `Strategy` constructor. + * + * The Facebook authentication strategy authenticates requests by delegating to + * Facebook using the OAuth 2.0 protocol. + * + * Applications must supply a `verify` callback which accepts an `accessToken`, + * `refreshToken` and service-specific `profile`, and then calls the `cb` + * callback supplying a `user`, which should be set to `false` if the + * credentials are not valid. If an exception occured, `err` should be set. + * + * Options: + * - `clientID` your Facebook application's App ID + * - `clientSecret` your Facebook application's App Secret + * - `callbackURL` URL to which Facebook will redirect the user after granting authorization + * + * Examples: + * + * passport.use(new FacebookStrategy({ * clientID: '123-456-789', * clientSecret: 'shhh-its-a-secret' * callbackURL: 'https://www.example.net/auth/facebook/callback' * }, - * function(accessToken, refreshToken, profile, cb) { + * function(accessToken, refreshToken, profile, cb) { * User.findOrCreate(..., function (err, user) { * cb(err, user); * }); * } - * )); - * - * @constructor - * @param {object} options - * @param {function} verify - * @access public - */ -function Strategy(options, verify) { - options = options || {}; - options.authorizationURL = options.authorizationURL || 'https://www.facebook.com/dialog/oauth'; - options.tokenURL = options.tokenURL || 'https://graph.facebook.com/oauth/access_token'; - options.scopeSeparator = options.scopeSeparator || ','; - - OAuth2Strategy.call(this, options, verify); - this.name = 'facebook'; - this._profileURL = options.profileURL || 'https://graph.facebook.com/v2.5/me'; - this._profileFields = options.profileFields || null; - this._enableProof = options.enableProof; - this._clientSecret = options.clientSecret; -} + * )); + * + * @constructor + * @param {object} options + * @param {function} verify + * @access public + */ + function Strategy(options, verify) { + options = options || {}; + options.authorizationURL = options.authorizationURL || 'https://www.facebook.com/dialog/oauth'; + options.tokenURL = options.tokenURL || 'https://graph.facebook.com/oauth/access_token'; + options.scopeSeparator = options.scopeSeparator || ','; + + OAuth2Strategy.call(this, options, verify); + this.name = 'facebook'; + this._profileURL = options.profileURL || 'https://graph.facebook.com/v2.8/me'; + this._profileFields = options.profileFields || null; + this._enableProof = options.enableProof; + this._clientSecret = options.clientSecret; + } // Inherit from `OAuth2Strategy`. -util.inherits(Strategy, OAuth2Strategy); - - -/** - * Authenticate request by delegating to Facebook using OAuth 2.0. - * - * @param {http.IncomingMessage} req - * @param {object} options - * @access protected - */ -Strategy.prototype.authenticate = function(req, options) { - // Facebook doesn't conform to the OAuth 2.0 specification, with respect to - // redirecting with error codes. - // - // FIX: https://github.com/jaredhanson/passport-oauth/issues/16 - if (req.query && req.query.error_code && !req.query.error) { - return this.error(new FacebookAuthorizationError(req.query.error_message, parseInt(req.query.error_code, 10))); - } - - OAuth2Strategy.prototype.authenticate.call(this, req, options); -}; - -/** - * Return extra Facebook-specific parameters to be included in the authorization - * request. - * - * Options: - * - `display` Display mode to render dialog, { `page`, `popup`, `touch` }. - * - * @param {object} options - * @return {object} - * @access protected - */ -Strategy.prototype.authorizationParams = function (options) { - var params = {}; - - // https://developers.facebook.com/docs/reference/dialogs/oauth/ - if (options.display) { - params.display = options.display; - } - - // https://developers.facebook.com/docs/facebook-login/reauthentication/ - if (options.authType) { - params.auth_type = options.authType; - } - if (options.authNonce) { - params.auth_nonce = options.authNonce; - } - - return params; -}; - -/** - * Retrieve user profile from Facebook. - * - * This function constructs a normalized profile, with the following properties: - * - * - `provider` always set to `facebook` - * - `id` the user's Facebook ID - * - `username` the user's Facebook username - * - `displayName` the user's full name - * - `name.familyName` the user's last name - * - `name.givenName` the user's first name - * - `name.middleName` the user's middle name - * - `gender` the user's gender: `male` or `female` - * - `profileUrl` the URL of the profile for the user on Facebook - * - `emails` the proxied or contact email address granted by the user - * - * @param {string} accessToken - * @param {function} done - * @access protected - */ -Strategy.prototype.userProfile = function(accessToken, done) { - var url = uri.parse(this._profileURL); - if (this._enableProof) { - // Secure API call by adding proof of the app secret. This is required when - // the "Require AppSecret Proof for Server API calls" setting has been - // enabled. The proof is a SHA256 hash of the access token, using the app - // secret as the key. - // - // For further details, refer to: - // https://developers.facebook.com/docs/reference/api/securing-graph-api/ - var proof = crypto.createHmac('sha256', this._clientSecret).update(accessToken).digest('hex'); - url.search = (url.search ? url.search + '&' : '') + 'appsecret_proof=' + proof; - } - if (this._profileFields) { - var fields = this._convertProfileFields(this._profileFields); - if (fields !== '') { url.search = (url.search ? url.search + '&' : '') + 'fields=' + fields; } - } - url = uri.format(url); - - this._oauth2.get(url, accessToken, function (err, body, res) { - var json; - - if (err) { - if (err.data) { + util.inherits(Strategy, OAuth2Strategy); + + + /** + * Authenticate request by delegating to Facebook using OAuth 2.0. + * + * @param {http.IncomingMessage} req + * @param {object} options + * @access protected + */ + Strategy.prototype.authenticate = function(req, options) { + // Facebook doesn't conform to the OAuth 2.0 specification, with respect to + // redirecting with error codes. + // + // FIX: https://github.com/jaredhanson/passport-oauth/issues/16 + if (req.query && req.query.error_code && !req.query.error) { + return this.error(new FacebookAuthorizationError(req.query.error_message, parseInt(req.query.error_code, 10))); + } + + OAuth2Strategy.prototype.authenticate.call(this, req, options); + }; + + /** + * Return extra Facebook-specific parameters to be included in the authorization + * request. + * + * Options: + * - `display` Display mode to render dialog, { `page`, `popup`, `touch` }. + * + * @param {object} options + * @return {object} + * @access protected + */ + Strategy.prototype.authorizationParams = function (options) { + var params = {}; + + // https://developers.facebook.com/docs/reference/dialogs/oauth/ + if (options.display) { + params.display = options.display; + } + + // https://developers.facebook.com/docs/facebook-login/reauthentication/ + if (options.authType) { + params.auth_type = options.authType; + } + if (options.authNonce) { + params.auth_nonce = options.authNonce; + } + + return params; + }; + + /** + * Proccess the incoming object and requested fields to null any parameter that is not returned from Graph API. + * + * @param {comma separated string} incomingList + * @param {object} _json param from Graph API response + * @return a new object to be appended on profile holder object + */ + Strategy.prototype.nullableFields = function (incomingList, responseList) { try { - json = JSON.parse(err.data); - } catch (_) {} - } - - if (json && json.error && typeof json.error == 'object') { - return done(new FacebookGraphAPIError(json.error.message, json.error.type, json.error.code, json.error.error_subcode, json.error.fbtrace_id)); - } - return done(new InternalOAuthError('Failed to fetch user profile', err)); - } - - try { - json = JSON.parse(body); - } catch (ex) { - return done(new Error('Failed to parse user profile')); - } + var incomingListArr = incomingList.trim().split(","), + supportArr = responseList, + isListedOnResponse; - var profile = Profile.parse(json); - profile.provider = 'facebook'; - profile._raw = body; - profile._json = json; - - done(null, profile); - }); -}; - -/** - * Parse error response from Facebook OAuth 2.0 token endpoint. - * - * @param {string} body - * @param {number} status - * @return {Error} - * @access protected - */ -Strategy.prototype.parseErrorResponse = function(body, status) { - var json = JSON.parse(body); - if (json.error && typeof json.error == 'object') { - return new FacebookTokenError(json.error.message, json.error.type, json.error.code, json.error.error_subcode, json.error.fbtrace_id); - } - return OAuth2Strategy.prototype.parseErrorResponse.call(this, body, status); -}; - -/** - * Convert Facebook profile to a normalized profile. - * - * @param {object} profileFields - * @return {string} - * @access protected - */ -Strategy.prototype._convertProfileFields = function(profileFields) { - var map = { - 'id': 'id', - 'username': 'username', - 'displayName': 'name', - 'name': ['last_name', 'first_name', 'middle_name'], - 'gender': 'gender', - 'birthday': 'birthday', - 'profileUrl': 'link', - 'emails': 'email', - 'photos': 'picture' - }; - - var fields = []; - - profileFields.forEach(function(f) { - // return raw Facebook profile field to support the many fields that don't - // map cleanly to Portable Contacts - if (typeof map[f] === 'undefined') { return fields.push(f); }; - - if (Array.isArray(map[f])) { - Array.prototype.push.apply(fields, map[f]); - } else { - fields.push(map[f]); - } - }); + } catch (e) { + return responseList; + } + + incomingListArr.forEach(function (property) { + isListedOnResponse = false; + for (var element in responseList) { + if (responseList.hasOwnProperty(element)) { + if (property === element) { + isListedOnResponse = true; + break; + } + } + } + + if (!isListedOnResponse) { + if (property) { + supportArr[property] = null; + } + + } + }); + return supportArr; + }; + + /** + * Retrieve user profile from Facebook. + * + * This function constructs a normalized profile, with the following properties: + * + * - `provider` always set to `facebook` + * - `id` the user's Facebook ID + * - `username` the user's Facebook username + * - `displayName` the user's full name + * - `name.familyName` the user's last name + * - `name.givenName` the user's first name + * - `name.middleName` the user's middle name + * - `gender` the user's gender: `male` or `female` + * - `profileUrl` the URL of the profile for the user on Facebook + * - `emails` the proxied or contact email address granted by the user + * + * @param {string} accessToken + * @param {function} done + * @access protected + */ + Strategy.prototype.userProfile = function(accessToken, done) { + var url = uri.parse(this._profileURL); + if (this._enableProof) { + // Secure API call by adding proof of the app secret. This is required when + // the "Require AppSecret Proof for Server API calls" setting has been + // enabled. The proof is a SHA256 hash of the access token, using the app + // secret as the key. + // + // For further details, refer to: + // https://developers.facebook.com/docs/reference/api/securing-graph-api/ + var proof = crypto.createHmac('sha256', this._clientSecret).update(accessToken).digest('hex'); + url.search = (url.search ? url.search + '&' : '') + 'appsecret_proof=' + proof; + } + if (this._profileFields) { + var fields = this._convertProfileFields(this._profileFields); + if (fields !== '') { url.search = (url.search ? url.search + '&' : '') + 'fields=' + fields; } + } + url = uri.format(url); + + this._oauth2.get(url, accessToken, function (err, body, res) { + var json; + + if (err) { + if (err.data) { + try { + json = JSON.parse(err.data); + } catch (_) {} + } + + if (json && json.error && typeof json.error == 'object') { + return done(new FacebookGraphAPIError(json.error.message, json.error.type, json.error.code, json.error.error_subcode, json.error.fbtrace_id)); + } + return done(new InternalOAuthError('Failed to fetch user profile', err)); + } + + try { + json = JSON.parse(body); + } catch (ex) { + return done(new Error('Failed to parse user profile')); + } + + var profile = Profile.parse(json); + profile.provider = 'facebook'; + profile._raw = body; + profile._json = json; + profile._proccessed = Strategy.prototype.nullableFields(fields, profile._json); + + done(null, profile); + }); + }; + + + + /** + * Parse error response from Facebook OAuth 2.0 token endpoint. + * + * @param {string} body + * @param {number} status + * @return {Error} + * @access protected + */ + Strategy.prototype.parseErrorResponse = function(body, status) { + var json = JSON.parse(body); + if (json.error && typeof json.error == 'object') { + return new FacebookTokenError(json.error.message, json.error.type, json.error.code, json.error.error_subcode, json.error.fbtrace_id); + } + return OAuth2Strategy.prototype.parseErrorResponse.call(this, body, status); + }; + + /** + * Convert Facebook profile to a normalized profile. + * + * @param {object} profileFields + * @return {string} + * @access protected + */ + Strategy.prototype._convertProfileFields = function(profileFields) { + var map = { + 'id': 'id', + 'username': 'username', + 'displayName': 'name', + 'name': ['last_name', 'first_name', 'middle_name'], + 'gender': 'gender', + 'birthday': 'birthday', + 'profileUrl': 'link', + 'emails': 'email', + 'photos': 'picture' + }; + + var fields = []; + + profileFields.forEach(function(f) { + // return raw Facebook profile field to support the many fields that don't + // map cleanly to Portable Contacts + if (typeof map[f] === 'undefined') { return fields.push(f); }; + + if (Array.isArray(map[f])) { + Array.prototype.push.apply(fields, map[f]); + } else { + fields.push(map[f]); + } + }); - return fields.join(','); -}; + return fields.join(','); + }; // Expose constructor. -module.exports = Strategy; + module.exports = Strategy; +}()); \ No newline at end of file diff --git a/test/strategy.profile.test.js b/test/strategy.profile.test.js index e1f2c53..6a2dfa6 100644 --- a/test/strategy.profile.test.js +++ b/test/strategy.profile.test.js @@ -4,6 +4,31 @@ var FacebookStrategy = require('../lib/strategy'); +describe('Strategy#nullableFields', function () { + + describe("Check main object prototype", function () { + var strategy = new FacebookStrategy({ + clientID: 'ABC123', + clientSecret: 'secret' + }, function() {}); + it("should contain a nullableFields function", function () { + expect(strategy.nullableFields).to.be.an('function'); + }); + + it("should return an original response profile object if an error occurs", function () { + expect(strategy.nullableFields(null, {"prop": "test"})).to.haveOwnProperty("prop"); + }); + + it("should return a merged object containing the returned profile and the requested fields with null value if not present in response", function () { + expect(strategy.nullableFields("id,name,favorite_athletes", {"id": "123", "name": "daniel"})).to.deep.equal({"id": "123", "name": "daniel", "favorite_athletes": null}); + }); + + it("should exclude empty incoming parameters", function () { + expect(strategy.nullableFields("id,name,favorite_athletes, ", {"id": "123", "name": "daniel"})).to.deep.equal({"id": "123", "name": "daniel", "favorite_athletes": null}); + }); + }) +}); + describe('Strategy#userProfile', function() { describe('fetched from default endpoint', function() { @@ -13,7 +38,7 @@ describe('Strategy#userProfile', function() { }, function() {}); strategy._oauth2.get = function(url, accessToken, callback) { - if (url != 'https://graph.facebook.com/v2.5/me') { return callback(new Error('incorrect url argument')); } + if (url != 'https://graph.facebook.com/v2.8/me') { return callback(new Error('incorrect url argument')); } if (accessToken != 'token') { return callback(new Error('incorrect token argument')); } var body = '{"id":"500308595","name":"Jared Hanson","first_name":"Jared","last_name":"Hanson","link":"http:\\/\\/www.facebook.com\\/jaredhanson","username":"jaredhanson","gender":"male","email":"jaredhanson\\u0040example.com"}'; @@ -63,7 +88,7 @@ describe('Strategy#userProfile', function() { }, function() {}); strategy._oauth2.get = function(url, accessToken, callback) { - if (url != 'https://graph.facebook.com/v2.5/me?appsecret_proof=e941110e3d2bfe82621f0e3e1434730d7305d106c5f68c87165d0b27a4611a4a') { return callback(new Error('incorrect url argument')); } + if (url != 'https://graph.facebook.com/v2.8/me?appsecret_proof=e941110e3d2bfe82621f0e3e1434730d7305d106c5f68c87165d0b27a4611a4a') { return callback(new Error('incorrect url argument')); } if (accessToken != 'token') { return callback(new Error('incorrect token argument')); } var body = '{"id":"500308595","name":"Jared Hanson","first_name":"Jared","last_name":"Hanson","link":"http:\\/\\/www.facebook.com\\/jaredhanson","username":"jaredhanson","gender":"male","email":"jaredhanson\\u0040example.com"}'; @@ -96,7 +121,7 @@ describe('Strategy#userProfile', function() { }, function() {}); strategy._oauth2.get = function(url, accessToken, callback) { - if (url != 'https://graph.facebook.com/v2.5/me?fields=id,username,name,last_name,first_name,middle_name,gender,link,email,picture') { return callback(new Error('incorrect url argument')); } + if (url != 'https://graph.facebook.com/v2.8/me?fields=id,username,name,last_name,first_name,middle_name,gender,link,email,picture') { return callback(new Error('incorrect url argument')); } if (accessToken != 'token') { return callback(new Error('incorrect token argument')); } var body = '{"id":"500308595","name":"Jared Hanson","first_name":"Jared","last_name":"Hanson","link":"http:\\/\\/www.facebook.com\\/jaredhanson","username":"jaredhanson","gender":"male","email":"jaredhanson\\u0040example.com"}'; @@ -130,7 +155,7 @@ describe('Strategy#userProfile', function() { }, function() {}); strategy._oauth2.get = function(url, accessToken, callback) { - if (url != 'https://graph.facebook.com/v2.5/me?appsecret_proof=e941110e3d2bfe82621f0e3e1434730d7305d106c5f68c87165d0b27a4611a4a&fields=id,username,name,last_name,first_name,middle_name,gender,link,email,picture') { return callback(new Error('incorrect url argument')); } + if (url != 'https://graph.facebook.com/v2.8/me?appsecret_proof=e941110e3d2bfe82621f0e3e1434730d7305d106c5f68c87165d0b27a4611a4a&fields=id,username,name,last_name,first_name,middle_name,gender,link,email,picture') { return callback(new Error('incorrect url argument')); } if (accessToken != 'token') { return callback(new Error('incorrect token argument')); } var body = '{"id":"500308595","name":"Jared Hanson","first_name":"Jared","last_name":"Hanson","link":"http:\\/\\/www.facebook.com\\/jaredhanson","username":"jaredhanson","gender":"male","email":"jaredhanson\\u0040example.com"}'; @@ -163,13 +188,12 @@ describe('Strategy#userProfile', function() { }, function() {}); strategy._oauth2.get = function(url, accessToken, callback) { - if (url != 'https://graph.facebook.com/v2.5/me?fields=id,username,name,last_name,first_name,middle_name,gender,link,email,picture,public_key,updated_time') { return callback(new Error('incorrect url argument')); } + if (url != 'https://graph.facebook.com/v2.8/me?fields=id,username,name,last_name,first_name,middle_name,gender,link,email,picture,public_key,updated_time') { return callback(new Error('incorrect url argument')); } if (accessToken != 'token') { return callback(new Error('incorrect token argument')); } var body = '{"id":"500308595","name":"Jared Hanson","first_name":"Jared","last_name":"Hanson","link":"http:\\/\\/www.facebook.com\\/jaredhanson","username":"jaredhanson","gender":"male","email":"jaredhanson\\u0040example.com", "updated_time": "2013-11-02T18:33:09+0000"}'; callback(null, body, undefined); - } - + }; var profile; @@ -200,7 +224,7 @@ describe('Strategy#userProfile', function() { }, function() {}); strategy._oauth2.get = function(url, accessToken, callback) { - if (url != 'https://graph.facebook.com/v2.5/me') { return callback(new Error('incorrect url argument')); } + if (url != 'https://graph.facebook.com/v2.8/me') { return callback(new Error('incorrect url argument')); } if (accessToken != 'token') { return callback(new Error('incorrect token argument')); } var body = '{"id":"500308595","name":"Jared Hanson","first_name":"Jared","last_name":"Hanson","link":"http:\\/\\/www.facebook.com\\/jaredhanson","username":"jaredhanson","gender":"male","email":"jaredhanson\\u0040example.com"}'; From 1f9b720ec0887d03de9fb40d333bdb2c141e11ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Abra=CC=83o?= Date: Wed, 16 Nov 2016 23:42:58 -0200 Subject: [PATCH 2/3] - typo proccessed fixed to processed - added property nullEmptyResponse to config object to control to null empty response or not --- lib/strategy.js | 5 ++++- test/strategy.test.js | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/strategy.js b/lib/strategy.js index 9282b88..9c15095 100644 --- a/lib/strategy.js +++ b/lib/strategy.js @@ -59,6 +59,7 @@ this._profileFields = options.profileFields || null; this._enableProof = options.enableProof; this._clientSecret = options.clientSecret; + this._nullEmptyResponse = options.nullEmptyResponse; } // Inherit from `OAuth2Strategy`. @@ -217,7 +218,9 @@ profile.provider = 'facebook'; profile._raw = body; profile._json = json; - profile._proccessed = Strategy.prototype.nullableFields(fields, profile._json); + if (this._nullEmptyResponse) { + profile._processed = Strategy.prototype.nullableFields(fields, profile._json); + } done(null, profile); }); diff --git a/test/strategy.test.js b/test/strategy.test.js index 59ee9bf..7eafeef 100644 --- a/test/strategy.test.js +++ b/test/strategy.test.js @@ -17,7 +17,7 @@ describe('Strategy', function() { it('should be named facebook', function() { expect(strategy.name).to.equal('facebook'); }); - }) + }); describe('constructed with undefined options', function() { it('should throw', function() { @@ -25,7 +25,7 @@ describe('Strategy', function() { var strategy = new FacebookStrategy(undefined, function(){}); }).to.throw(Error); }); - }) + }); describe('authorization request with display parameter', function() { var strategy = new FacebookStrategy({ From ca85ba778d6afd566ccf48d3a9e29e8d3baa1393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Abra=CC=83o?= Date: Wed, 16 Nov 2016 23:46:03 -0200 Subject: [PATCH 3/3] =?UTF-8?q?-=20fixed=20=E2=80=98this=E2=80=99=20refere?= =?UTF-8?q?nce=20on=20closure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/strategy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/strategy.js b/lib/strategy.js index 9c15095..faadff2 100644 --- a/lib/strategy.js +++ b/lib/strategy.js @@ -175,6 +175,7 @@ */ Strategy.prototype.userProfile = function(accessToken, done) { var url = uri.parse(this._profileURL); + var self = this; if (this._enableProof) { // Secure API call by adding proof of the app secret. This is required when // the "Require AppSecret Proof for Server API calls" setting has been @@ -218,7 +219,7 @@ profile.provider = 'facebook'; profile._raw = body; profile._json = json; - if (this._nullEmptyResponse) { + if (self._nullEmptyResponse) { profile._processed = Strategy.prototype.nullableFields(fields, profile._json); }