passport-xing
Version:
Xing authentication strategy for Passport.
196 lines (172 loc) • 6.42 kB
JavaScript
/**
* Module dependencies.
*/
var util = require('util')
, OAuthStrategy = require('passport-oauth').OAuthStrategy
, InternalOAuthError = require('passport-oauth').InternalOAuthError;
/**
* `Strategy` constructor.
*
* The Xing authentication strategy authenticates requests by delegating to
* Xing using the OAuth protocol.
*
* Applications must supply a `verify` callback which accepts a `token`,
* `tokenSecret` and service-specific `profile`, and then calls the `done`
* 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:
* - `consumerKey` identifies client to Xing
* - `consumerSecret` secret used to establish ownership of the consumer key
* - `callbackURL` URL to which Xing will redirect the user after obtaining authorization
*
* Examples:
*
* passport.use(new XingStrategy({
* consumerKey: '123-456-789',
* consumerSecret: 'shhh-its-a-secret'
* callbackURL: 'https://www.example.net/auth/xing/callback'
* },
* function(token, tokenSecret, profile, done) {
* User.findOrCreate(..., function (err, user) {
* done(err, user);
* });
* }
* ));
*
* @param {Object} options
* @param {Function} verify
* @api public
*/
function Strategy(options, verify) {
options = options || {};
options.requestTokenURL = options.requestTokenURL || 'https://api.xing.com/v1/request_token';
options.accessTokenURL = options.accessTokenURL || 'https://api.xing.com/v1/access_token';
options.userAuthorizationURL = options.userAuthorizationURL || 'https://api.xing.com/v1/authorize';
options.sessionKey = options.sessionKey || 'oauth:xing';
OAuthStrategy.call(this, options, verify);
this.name = 'xing';
this._profileFields = options.profileFields || null;
}
/**
* Inherit from `OAuthStrategy`.
*/
util.inherits(Strategy, OAuthStrategy);
/**
* Authenticate request by delegating to Xing using OAuth.
*
* @param {Object} req
* @api protected
*/
Strategy.prototype.authenticate = function(req) {
// When a user denies authorization on Xing, they are presented with a
// link to return to the application in the following format:
//
// http://www.example.com/auth/xing/callback?oauth_problem=user_refused
//
// Following the link back to the application is interpreted as an
// authentication failure.
if (req.query && req.query.oauth_problem) {
return this.fail();
}
// Call the base class for standard OAuth authentication.
OAuthStrategy.prototype.authenticate.call(this, req);
}
/**
* Retrieve user profile from Xing.
*
* This function constructs a normalized profile, with the following properties:
*
* - `id`
* - `displayName`
* - `name.familyName`
* - `name.givenName`
*
* @param {String} token
* @param {String} tokenSecret
* @param {Object} params
* @param {Function} done
* @api protected
*/
Strategy.prototype.userProfile = function(token, tokenSecret, params, done) {
var url = 'https://api.xing.com/v1/users/me.json?fields=id,first_name,last_name,display_name,active_email,page_name,permalink,gender,photo_urls,birth_date';
if (this._profileFields) {
var fields = this._convertProfileFields(this._profileFields);
url = 'https://api.xing.com/v1/users/me.json?fields=' + fields;
}
this._oauth.get(url, token, tokenSecret, function (err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var json = JSON.parse(body);
var profile = { provider: 'xing' };
// Xing returns an array of user, currently only the first is used
var user = json.users[0];
profile.id = user.id;
profile.displayName = user.display_name;
profile.name = { familyName: user.last_name,
givenName: user.first_name };
// The following fields are optional and only populated if they are provided by Xing
if (user.page_name) { profile.username = user.page_name };
if (user.permalink) { profile.profileUrl = user.permalink };
if (user.gender) { profile.gender = user.gender }
if (user.birth_date && user.birth_date.year && user.birth_date.month && user.birth_date.day) {
profile.birthday = new Date(user.birth_date.year,
user.birth_date.month,
user.birth_date.day);
}
profile.emails = [];
if (user.active_email) { profile.emails.push({type: "home", value: user.active_email, primary: true}); };
if (user.private_address) { profile.emails.push({type: "home", value: user.private_address.email}); };
if (user.business_address) { profile.emails.push({type: "work", value: user.business_address.email}); };
if (profile.emails.length === 0) { delete profile.emails };
if (user.photo_urls) {
profile.photos = [];
Object.keys(user.photo_urls).forEach(function(key) {
profile.photos.push({value: user.photo_urls[key],
type: key});
});
}
profile._raw = body;
profile._json = user;
done(null, profile);
} catch(e) {
done(e);
}
});
}
Strategy.prototype._convertProfileFields = function(profileFields) {
var map = {
'id': 'id',
'displayName': 'display_name',
'name': ['first_name', 'last_name'],
'emails': 'active_email',
'photos': 'photo_urls',
'profileUrl': 'permalink',
'gender': 'gender',
'username': 'page_name',
'birthday': 'birth_date'
};
var fields = ['id', 'first_name', 'last_name', 'display_name'];
profileFields.forEach(function(f) {
// return raw Xing 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]);
}
});
// Make sure the fields are unique
var unique = [];
for (var i = 0; i < fields.length; i++) {
if (unique.indexOf(fields[i]) == -1) {
unique.push(fields[i]);
}
}
return unique.join(',');
}
/**
* Expose `Strategy`.
*/
module.exports = Strategy;