From 6905b94fd04c47805e96390e6ce661d53367c360 Mon Sep 17 00:00:00 2001 From: G-T-P <45485090+G-T-P@users.noreply.github.com> Date: Tue, 18 Dec 2018 14:43:48 +0100 Subject: [PATCH 1/3] Update to the new API Here is the link to the new documentation: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context The api request now has to be: `GET https://api.linkedin.com/v2/me` It does not accept parameters any longer and the only scope that are valid are nor r_liteprofile and r_emailaddress which also requires its own api call --- lib/oauth2.js | 99 ++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/lib/oauth2.js b/lib/oauth2.js index 82773c49..dd0ce3c5 100644 --- a/lib/oauth2.js +++ b/lib/oauth2.js @@ -15,7 +15,7 @@ function Strategy(options, verify) { OAuth2Strategy.call(this, options, verify); this.name = 'linkedin'; - this.profileUrl = 'https://api.linkedin.com/v1/people/~:(' + this._convertScopeToUserProfileFields(options.scope, options.profileFields) + ')'; + this.profileUrl = 'https://api.linkedin.com/v2/me?projection=(' + this._convertScopeToUserProfileFields(options.scope, options.profileFields) + ')'; } util.inherits(Strategy, OAuth2Strategy); @@ -24,10 +24,31 @@ Strategy.prototype.userProfile = function(accessToken, done) { //LinkedIn uses a custom name for the access_token parameter this._oauth2.setAccessTokenName("oauth2_access_token"); - - this._oauth2.get(this.profileUrl, accessToken, function (err, body, res) { + + if (this.emailUrl) { + var self = this + this._oauth2.get(this.emailUrl, accessToken, function (err, body, res) { + if (err) { return done(new InternalOAuthError('failed to fetch user email', err)); } + + try { + var json = JSON.parse(body); + var email = json.emailAddress; + + self._fetchAsyncProfile(accessToken, done, email); + + } catch(e) { + done(e); + } + } + } else { + this._fetchAsyncProfile(accessToken, done); + } +}; + +Strategy.prototype._fetchAsyncProfile = function(accessToken, done, email) { + this._oauth2.get(this.profileUrl, accessToken, function (err, body, res) { if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); } - + try { var json = JSON.parse(body); @@ -39,7 +60,7 @@ Strategy.prototype.userProfile = function(accessToken, done) { familyName: json.lastName, givenName: json.firstName }; - profile.emails = [{ value: json.emailAddress }]; + profile.emails = [{ value: email || json.emailAddress }]; profile.photos = []; if (json.pictureUrl) { profile.photos.push({ value: json.pictureUrl }); @@ -52,76 +73,32 @@ Strategy.prototype.userProfile = function(accessToken, done) { done(e); } }); -} - - +}; Strategy.prototype._convertScopeToUserProfileFields = function(scope, profileFields) { var self = this; var map = { - 'r_basicprofile': [ + 'r_liteprofile': [ 'id', - 'first-name', - 'last-name', - 'picture-url', - 'picture-urls::(original)', - 'formatted-name', - 'maiden-name', - 'phonetic-first-name', - 'phonetic-last-name', - 'formatted-phonetic-name', - 'headline', - 'location:(name,country:(code))', - 'industry', - 'distance', - 'relation-to-viewer:(distance,connections)', - 'current-share', - 'num-connections', - 'num-connections-capped', - 'summary', - 'specialties', - 'positions', - 'site-standard-profile-request', - 'api-standard-profile-request:(headers,url)', - 'public-profile-url' + 'firstName', + 'lastName', + 'profilePicture(displayImage~:playableStreams)' ], - 'r_emailaddress': ['email-address'], - 'r_fullprofile': [ - 'last-modified-timestamp', - 'proposal-comments', - 'associations', - 'interests', - 'publications', - 'patents', - 'languages', - 'skills', - 'certifications', - 'educations', - 'courses', - 'volunteer', - 'three-current-positions', - 'three-past-positions', - 'num-recommenders', - 'recommendations-received', - 'mfeed-rss-url', - 'following', - 'job-bookmarks', - 'suggestions', - 'date-of-birth', - 'member-url-resources:(name,url)', - 'related-profile-views', - 'honors-awards' - ] }; + var r_emailaddress = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))'; var fields = []; // To obtain pre-defined field mappings if(Array.isArray(scope) && profileFields === null) { - if(scope.indexOf('r_basicprofile') === -1){ - scope.unshift('r_basicprofile'); + if(scope.indexOf('r_liteprofile') === -1){ + scope.unshift('r_liteprofile'); + } + + if(scope.indexOf('r_emailaddress') !== -1){ + self.emailUrl = r_emailaddress } scope.forEach(function(f) { From 1355d05726665db18fefcdb97b5a9978b98cf0b4 Mon Sep 17 00:00:00 2001 From: G-T-P <45485090+G-T-P@users.noreply.github.com> Date: Tue, 18 Dec 2018 15:01:04 +0100 Subject: [PATCH 2/3] Missed some brackets --- lib/oauth2.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/oauth2.js b/lib/oauth2.js index dd0ce3c5..bc3e3e70 100644 --- a/lib/oauth2.js +++ b/lib/oauth2.js @@ -7,7 +7,7 @@ function Strategy(options, verify) { options = options || {}; options.authorizationURL = options.authorizationURL || 'https://www.linkedin.com/oauth/v2/authorization'; options.tokenURL = options.tokenURL || 'https://www.linkedin.com/oauth/v2/accessToken'; - options.scope = options.scope || ['r_basicprofile']; + options.scope = options.scope || ['r_liteprofile']; options.profileFields = options.profileFields || null; //By default we want data in JSON @@ -39,7 +39,7 @@ Strategy.prototype.userProfile = function(accessToken, done) { } catch(e) { done(e); } - } + }); } else { this._fetchAsyncProfile(accessToken, done); } From 6c2da37779c2a78195f64931f3fba3359472ad4e Mon Sep 17 00:00:00 2001 From: G-T-P <45485090+G-T-P@users.noreply.github.com> Date: Tue, 18 Dec 2018 16:05:09 +0100 Subject: [PATCH 3/3] Went a bit fast in the first commit Tested it and it works. The details of waht is returned (which could be greatly improved upon) is documented here: https://developer.linkedin.com/docs/ref/v2/media-migration --- lib/oauth2.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/oauth2.js b/lib/oauth2.js index bc3e3e70..0f3d2632 100644 --- a/lib/oauth2.js +++ b/lib/oauth2.js @@ -24,31 +24,29 @@ Strategy.prototype.userProfile = function(accessToken, done) { //LinkedIn uses a custom name for the access_token parameter this._oauth2.setAccessTokenName("oauth2_access_token"); - - if (this.emailUrl) { - var self = this - this._oauth2.get(this.emailUrl, accessToken, function (err, body, res) { - if (err) { return done(new InternalOAuthError('failed to fetch user email', err)); } - - try { - var json = JSON.parse(body); - var email = json.emailAddress; - - self._fetchAsyncProfile(accessToken, done, email); - - } catch(e) { - done(e); - } - }); + if (!!this.emailUrl) { + var self = this + this._oauth2.get(this.emailUrl, accessToken, function (err, body, res) { + if (err) { return done(new InternalOAuthError('failed to fetch user email', err)); } + + try { + var json = JSON.parse(body); + var emails = json.elements; + self._fetchAsyncProfile(accessToken, done, emails); + + } catch(e) { + done(e); + } + }); } else { - this._fetchAsyncProfile(accessToken, done); + this._fetchAsyncProfile(accessToken, done); } }; -Strategy.prototype._fetchAsyncProfile = function(accessToken, done, email) { +Strategy.prototype._fetchAsyncProfile = function(accessToken, done, emails) { this._oauth2.get(this.profileUrl, accessToken, function (err, body, res) { if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); } - + try { var json = JSON.parse(body); @@ -60,7 +58,7 @@ Strategy.prototype._fetchAsyncProfile = function(accessToken, done, email) { familyName: json.lastName, givenName: json.firstName }; - profile.emails = [{ value: email || json.emailAddress }]; + profile.emails = emails || [{ value: json.emailAddress }]; profile.photos = []; if (json.pictureUrl) { profile.photos.push({ value: json.pictureUrl }); @@ -96,7 +94,7 @@ Strategy.prototype._convertScopeToUserProfileFields = function(scope, profileFie if(scope.indexOf('r_liteprofile') === -1){ scope.unshift('r_liteprofile'); } - + if(scope.indexOf('r_emailaddress') !== -1){ self.emailUrl = r_emailaddress }