diff --git a/package.json b/package.json index 826258f..ea84881 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "phone": "^2.3.0", "request": "^2.88.0", "twilio": "^3.25.0", - "uuid": "^3.3.2" + "uuid": "^3.3.2", + "oauth-1.0a": "^2.2.5" }, "devDependencies": { "eslint": "^5.9.0", diff --git a/src/config/app.json b/src/config/app.json index 15341e0..b4079b5 100644 --- a/src/config/app.json +++ b/src/config/app.json @@ -48,6 +48,31 @@ "method": "POST" } } + }, + "linkedIn": { + "consumer": { + "key": "77fpx2v0o6zqus", + "secret": "7RfmnUs8amWvAoV8" + }, + "token": { + "key":"77fpx2v0o6zqus", + "secret": "7RfmnUs8amWvAoV8" + }, + "endpoints": { + "request_token": { + "url": "https://www.linkedin.com/oauth/v2/authorization", + "method": "GET" + }, + "access_token": { + "url": "https://www.linkedin.com/oauth/v2/accessToken", + "method": "POST" + }, + "get_profile": { + "url": "https://api.linkedin.com/v2/me", + "method": "GET" + } + }, + "redirect_uri": "http://192.168.1.201:3000/auth/linkedin/token" } }, "dbPath": { diff --git a/src/config/errors.js b/src/config/errors.js index 94bc884..55378b9 100644 --- a/src/config/errors.js +++ b/src/config/errors.js @@ -19,6 +19,10 @@ module.exports = { APP_NETWORK_TIMEOUT: 'app/network-timeout', APP_UNABLE_TO_PARSE_RESPONSE: 'app/unable-to-parse-response', APP_FIREBASE_DATABASE_ERROR: 'app/firebase-database-error', + AUTH_LINKEDIN_REQUEST_HTML_FAILURE: 'auth/linkedin-request-html-failure', + AUTH_LINKEDIN_REQUEST_TOKEN_FAILURE: 'auth/linkedin-request-token-failure', + AUTH_LINKEDIN_ACCESS_TOKEN_FAILURE: 'auth/linkedin-access-token-failure', + AUTH_LINKEDIN_PROFILE_REQUEST_FAILURE: 'auth/linkedin-profile-request-failure', //Request Errors MISSING_BODY_ATTRIBUTES: 'request/missing-body-attributes', //Profile Errors diff --git a/src/config/router.js b/src/config/router.js index fee4e3a..09321b1 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -10,6 +10,9 @@ function router(app) { // Auth Endpoints app.get('/auth/twitter/token', AuthHandlers.getTwitterToken ); app.get('/auth/twitter/username', AuthHandlers.getTwitterUsername ); + app.get('/auth/linkedin/login', AuthHandlers.getLinkedInLoginURL); + app.get('/auth/linkedin/token', AuthHandlers.getLinkedInAccessToken); + app.get('/auth/linkedin/get/profile', AuthHandlers.getLinkedInProfile); app.get('/auth/email/:email/exists', AuthHandlers.doesAttributeExist ); app.get('/auth/tagferId/:tagferId/exists', AuthHandlers.doesAttributeExist ); app.post('/auth/phone/code', AuthHandlers.sendPhoneCode); diff --git a/src/ups/auth/handlers.js b/src/ups/auth/handlers.js index 1de5593..3256fc5 100644 --- a/src/ups/auth/handlers.js +++ b/src/ups/auth/handlers.js @@ -5,6 +5,7 @@ const errors = require('../../config/errors'); const appConfig = require('../../config/app.json'); const http = require('../../config/http'); const twitter = require('../socials/twitter'); +const linkedin = require('../socials/linkedIn'); // Handlers /** @@ -159,6 +160,48 @@ function getTwitterUsername(req, res) { twitter.getUsername(req.query, (result) => res.send(result)); } +/** + * Endpoint: auth/linkedin/html + * Calls the linkedin endpoint to get the oauth url with clientId appended + * @param {Object} req {} + * @param {Object} res { result:{ uri: String} } | { error: String } + */ +function getLinkedInLoginURL(req, res) { + if (!utils.isAppSecretValid(req,res)) { + return; + } + linkedin.getOAuthLoginURL((result) => res.json(result)); +} + +/** + * Endpoint: auth/linkedin/token + * Calls the linkedin endpoint from ui view in tagfer app, to get the `oauth access token` + * @param {Object} req {} + * @param {Object} res { String } | { error: String } + */ +function getLinkedInAccessToken(req, res) { + const token = req.query.code; + if (token) { + linkedin.getOAuthAccessToken(token, (result) => res.send(result)); + } else { + res.status(http.UNAUTHORIZED).json({ error: errors.AUTH_LINKEDIN_REQUEST_TOKEN_FAILURE }); + } +} + +/** + * Endpoint: auth/linkedin/get/profile + * Calls the linkedin endpoint from + * @param {Object} req {} + * @param {Object} res { String } | { error: String } + */ +function getLinkedInProfile(req, res) { + if (req.query.access_token && req.query.expires_in) { + linkedin.getUserProfile(req.query.access_token, (result) => res.json(result)); + } else { + res.status(http.UNAUTHORIZED).json({ error: errors.AUTH_LINKEDIN_PROFILE_REQUEST_FAILURE }); + } +} + module.exports = { doesAttributeExist, sendPhoneCode, @@ -167,5 +210,7 @@ module.exports = { signup, sendPasswordResetEmail, getTwitterToken, - getTwitterUsername + getLinkedInLoginURL, + getLinkedInAccessToken, + getLinkedInProfile }; \ No newline at end of file diff --git a/src/ups/socials/linkedIn.js b/src/ups/socials/linkedIn.js new file mode 100644 index 0000000..b54194a --- /dev/null +++ b/src/ups/socials/linkedIn.js @@ -0,0 +1,68 @@ +const request = require('request'); + +const appConfig = require('../../config/app.json'); +const errors = require('../../config/errors'); +const utils = require('../utils/utils'); +const _ = require('lodash'); + +/** + * Calls linkedin.com/oauth/v2/authorization + * + * @param {Function} Takes an object that either contains {`token`} or {`error`} + */ +function getOAuthLoginURL(callback) { + let linkedIn = appConfig.keys.linkedIn; + let linkedInRequest = _.cloneDeep(linkedIn.endpoints.request_token); + linkedInRequest.url = `${linkedInRequest.url}?client_id=${linkedIn.consumer.key}&scope=r_fullprofile%20r_basicprofile%20r_emailaddress&redirect_uri=${encodeURIComponent(linkedIn.redirect_uri)}&response_type=code`; + callback({ uri: linkedInRequest.url}) +} + +function getOAuthAccessToken(token, callback) { + let linkedIn = appConfig.keys.linkedIn; + let linkedInRequest = _.cloneDeep(linkedIn.endpoints.access_token); + let url = `${linkedInRequest.url}?code=${token}&client_id=${linkedIn.consumer.key}&client_secret=${linkedIn.consumer.secret}&grant_type=authorization_code&redirect_uri=${encodeURIComponent(linkedIn.redirect_uri)}` + request({ + url, + headers: { + 'Content-Type':'application/x-www-form-urlencoded' + } + }, (error,result) => { + if (!error) { + callback(utils.passingHTML(result.body)); + } else { + callback({ error: errors.AUTH_LINKEDIN_ACCESS_TOKEN_FAILURE }); + } + }) +} + +function getUserProfile(token, callback) { + let linkedIn = appConfig.keys.linkedIn; + let linkedInRequest = linkedIn.endpoints.get_profile; + let url = linkedInRequest.url; + + request({ + url, + headers: { + 'Authorization': `Bearer ${token}`, + 'X-RestLi-Protocol-Version': '2.0.0' + } + }, (error, result) => { + if (!error) { + var body = JSON.parse(result.body); + if(body.serviceErrorCode) { + console.log(body.serviceErrorCode) + callback({ error: errors.AUTH_LINKEDIN_PROFILE_REQUEST_FAILURE }); + } else { + callback(body); + } + } else { + callback({ error: errors.AUTH_LINKEDIN_PROFILE_REQUEST_FAILURE }) + } + }) +} + +module.exports = { + getOAuthLoginURL, + getOAuthAccessToken, + getUserProfile +}; \ No newline at end of file diff --git a/src/ups/utils/utils.js b/src/ups/utils/utils.js index 9c2a3f5..7b37f0e 100644 --- a/src/ups/utils/utils.js +++ b/src/ups/utils/utils.js @@ -77,4 +77,5 @@ module.exports = { getSessionIdFromAuthHeader, isProfileNumberValid, createOAuthHeader -}; \ No newline at end of file +}; +