UNPKG

@dessly/passport-vkontakte

Version:
226 lines (205 loc) 6.57 kB
/** * 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;