@dessly/passport-vkontakte
Version:
VK.com authentication strategy for Passport.
226 lines (205 loc) • 6.57 kB
JavaScript
/**
* Module dependencies.
*/
var parse = require("./profile").parse,
util = require("util"),
url = require("url"),
OAuth2Strategy = require("passport-oauth2"),
InternalOAuthError = require("passport-oauth2").InternalOAuthError,
VkontakteAuthorizationError = require("./errors/vkontakteauthorizationerror"),
VkontakteTokenError = require("./errors/vkontaktetokenerror"),
VkontakteAPIError = require("./errors/vkontakteapierror");
/**
* `Strategy` constructor.
*
* The VK.com authentication strategy authenticates requests by delegating to
* VK.com 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 `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:
* - `clientID` your VK.com application's App ID
* - `clientSecret` your VK.com application's App Secret
* - `callbackURL` URL to which VK.com will redirect the user after granting authorization
* - `profileFields` array of fields to retrieve from VK.com
* - `apiVersion` version of VK API to use
*
* Examples:
*
* passport.use(new VKontakteStrategy({
* clientID: '123-456-789',
* clientSecret: 'shhh-its-a-secret'
* callbackURL: 'https://www.example.net/auth/facebook/callback'
* },
* function(accessToken, refreshToken, 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.authorizationURL =
options.authorizationURL || "https://oauth.vk.com/authorize";
options.tokenURL = options.tokenURL || "https://oauth.vk.com/access_token";
options.scopeSeparator = options.scopeSeparator || ",";
options.passReqToCallback = true;
this.lang = options.lang || "en";
this.photoSize = options.photoSize || 200;
// since options.lang have nothing to do with OAuth2Strategy
delete options.lang;
delete options.photoSize;
OAuth2Strategy.call(this, options, verifyWrapper(options, verify));
this.name = "vkontakte";
this._profileURL =
options.profileURL || "https://api.vk.com/method/users.get";
this._profileFields = options.profileFields || [];
this._apiVersion = options.apiVersion || "5.131"; // Рекомендуемая версия API
}
/**
* VK doesn't allow getting user's email using its API method.
* But, if the app requests `email` scope, it can be requested during
* token exchange. See https://new.vk.com/dev/auth_sites
*
* We wrap the `verify` function supplied by the user of this library
* in order to transparently merge the result of calling `users.get` API
* method and this email we got.
*/
function verifyWrapper(options, verify) {
return function passportVerify(
req,
accessToken,
refreshToken,
params,
profile,
verified
) {
if (params && params.email) {
profile.emails = [{ value: params.email }];
}
var arity = verify.length;
if (arity == 6) {
verify(req, accessToken, refreshToken, params, profile, verified);
} else if (arity == 5) {
verify(accessToken, refreshToken, params, profile, verified);
} else if (arity == 4) {
verify(accessToken, refreshToken, profile, verified);
} else {
this.error(
new Error(
"VKontakteStrategy: verify callback must take 4 or 5 parameters"
)
);
}
};
}
/**
* Inherit from `OAuth2Strategy`.
*/
util.inherits(Strategy, OAuth2Strategy);
/**
* Return extra parameters to be included in the authorization request.
*
* Options:
* - `display` Display mode to render dialog, { `page`, `popup`, `mobile` }.
*
* @param {Object} options
* @return {Object}
* @api protected
*/
Strategy.prototype.authorizationParams = function (options) {
var params = {};
// http://vk.com/dev/auth_mobile
if (options.display) {
params.display = options.display;
}
return params;
};
/**
* Retrieve user profile from Vkontakte.
*
* This function constructs a normalized profile, with the following properties:
*
* - `provider` always set to `vkontakte`
* - `id` the user's VK.com ID
* - `displayName` the user's full name
* - `name.familyName` the user's last name
* - `name.givenName` the user's first name
* - `gender` the user's gender: `male` or `female`
* - `photos` array of `{ value: 'url' }`
* - `city` the user's city (if requested by specifying it in profileFields setting)
*
* @param {String} accessToken
* @param {Function} done
* @api protected
*/
Strategy.prototype.userProfile = function (accessToken, done) {
var url = this._profileURL;
var fields = [
"uid",
"first_name",
"last_name",
"screen_name",
"sex",
"photo_" + this.photoSize,
];
this._profileFields.forEach(function (f) {
if (fields.indexOf(f) < 0) fields.push(f);
});
url += "?fields=" + fields.join(",") + "&v=" + this._apiVersion + "&https=1";
if (this.lang) url += "&lang=" + this.lang;
this._oauth2.getProtectedResource(
url,
accessToken,
function (err, body, res) {
if (err) {
return done(
new InternalOAuthError("failed to fetch user profile", err)
);
}
try {
var json = JSON.parse(body);
if (json.error)
throw new VkontakteAPIError(
json.error.error_msg,
json.error.error_code
);
json = json.response[0];
var profile = parse(json);
profile.provider = "vkontakte";
profile._raw = body;
profile._json = json;
done(null, profile);
} catch (e) {
done(e);
}
}
);
};
/**
* Parse error response from Vkontakte 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 VkontakteTokenError(json.error.error_msg, json.error.error_code);
}
return OAuth2Strategy.prototype.parseErrorResponse.call(this, body, status);
};
/**
* Expose `Strategy`.
*/
module.exports = Strategy;