diff --git a/lib/errors/facebookauthorizationerror.js b/lib/errors/facebookauthorizationerror.js index 1e5aa46..126aad2 100644 --- a/lib/errors/facebookauthorizationerror.js +++ b/lib/errors/facebookauthorizationerror.js @@ -10,9 +10,7 @@ * * @constructor * @param {String} [message] - * @param {String} [code] - * @param {String} [uri] - * @param {Number} [status] + * @param {Number} [code] * @api public */ function FacebookAuthorizationError(message, code) { diff --git a/lib/errors/facebooktokenerror.js b/lib/errors/facebooktokenerror.js new file mode 100644 index 0000000..829aff4 --- /dev/null +++ b/lib/errors/facebooktokenerror.js @@ -0,0 +1,38 @@ +/** + * `FacebookTokenError` error. + * + * FacebookTokenError represents an error received from a Facebook's token + * endpoint. Note that these responses don't conform to the OAuth 2.0 + * specification. + * + * References: + * - https://developers.facebook.com/docs/reference/api/errors/ + * + * @constructor + * @param {String} [message] + * @param {String} [type] + * @param {Number} [code] + * @param {Number} [subcode] + * @api public + */ +function FacebookTokenError(message, type, code, subcode) { + Error.call(this); + Error.captureStackTrace(this, arguments.callee); + this.name = 'FacebookTokenError'; + this.message = message; + this.type = type; + this.code = code; + this.subcode = subcode; + this.status = 500; +} + +/** + * Inherit from `Error`. + */ +FacebookTokenError.prototype.__proto__ = Error.prototype; + + +/** + * Expose `FacebookTokenError`. + */ +module.exports = FacebookTokenError; diff --git a/lib/strategy.js b/lib/strategy.js index ce56ea5..3388dc7 100644 --- a/lib/strategy.js +++ b/lib/strategy.js @@ -5,7 +5,8 @@ var parse = require('./profile').parse , util = require('util') , OAuth2Strategy = require('passport-oauth2') , InternalOAuthError = require('passport-oauth2').InternalOAuthError - , FacebookAuthorizationError = require('./errors/facebookauthorizationerror'); + , FacebookAuthorizationError = require('./errors/facebookauthorizationerror') + , FacebookTokenError = require('./errors/facebooktokenerror'); /** @@ -153,6 +154,22 @@ Strategy.prototype.userProfile = function(accessToken, done) { }); }; +/** + * Parse error response from Facebook OAuth 2.0 token endpoint. + * + * @param {String} body + * @param {Number} status + * @return {Error} + * @api 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); + } + return OAuth2Strategy.prototype.parseErrorResponse.call(this, body, status); +}; + Strategy.prototype._convertProfileFields = function(profileFields) { var map = { 'id': 'id', diff --git a/test/strategy.token.error.test.js b/test/strategy.token.error.test.js new file mode 100644 index 0000000..eb6dbdd --- /dev/null +++ b/test/strategy.token.error.test.js @@ -0,0 +1,80 @@ +var chai = require('chai') + , FacebookStrategy = require('../lib/strategy'); + + +describe('Strategy', function() { + + describe('using token endpoint that responds with non-standard error', function() { + var strategy = new FacebookStrategy({ + clientID: 'ABC123', + clientSecret: 'secret' + }, + function() {}); + + // inject a "mock" oauth2 instance + strategy._oauth2.getOAuthAccessToken = function(code, options, callback) { + return callback({ statusCode: 400, data: '{"error":{"message":"Invalid verification code format.","type":"OAuthException","code":100}}' }); + } + + describe('handling response', function() { + var err; + + before(function(done) { + chai.passport(strategy) + .error(function(e) { + err = e; + done(); + }) + .req(function(req) { + req.query = {}; + req.query.code = 'SplxlOBeZQQYbYS6WxSbIA+ALT1'; + }) + .authenticate(); + }); + + it('should error', function() { + expect(err.constructor.name).to.equal('FacebookTokenError'); + expect(err.message).to.equal('Invalid verification code format.'); + expect(err.type).to.equal('OAuthException'); + expect(err.code).to.equal(100); + }); + }); + }); + + describe('using token endpoint that responds with standard error', function() { + var strategy = new FacebookStrategy({ + clientID: 'ABC123', + clientSecret: 'secret' + }, + function() {}); + + // inject a "mock" oauth2 instance + strategy._oauth2.getOAuthAccessToken = function(code, options, callback) { + return callback({ statusCode: 400, data: '{"error":"invalid_grant","error_description":"The provided value for the input parameter \'code\' is not valid."} '}); + } + + describe('handling response', function() { + var err; + + before(function(done) { + chai.passport(strategy) + .error(function(e) { + err = e; + done(); + }) + .req(function(req) { + req.query = {}; + req.query.code = 'SplxlOBeZQQYbYS6WxSbIA+ALT1'; + }) + .authenticate(); + }); + + it('should error', function() { + expect(err.constructor.name).to.equal('TokenError'); + expect(err.message).to.equal('The provided value for the input parameter \'code\' is not valid.'); + expect(err.code).to.equal('invalid_grant'); + }); + }); + }); + +});