From 4248e2e870c6642ea37c310b9932f433beb5db67 Mon Sep 17 00:00:00 2001 From: Omar Date: Sun, 30 Dec 2018 16:07:26 -0800 Subject: [PATCH] Twitter Auth + Fix Previous Errors --- .eslintrc | 29 +++++++++++++ package-lock.json | 11 +++-- package.json | 2 + src/config/app.json | 20 +++++++++ src/config/errors.js | 1 + src/config/router.js | 6 ++- src/ups/auth/dao.js | 4 +- src/ups/auth/handlers.js | 34 +++++++++++++-- src/ups/socials/twitter.js | 84 ++++++++++++++++++++++++++++++++++++++ src/ups/users/dao.js | 2 +- src/ups/utils/utils.js | 26 ++++++++++-- 11 files changed, 205 insertions(+), 14 deletions(-) create mode 100644 .eslintrc create mode 100644 src/ups/socials/twitter.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..a9ef1a6 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,29 @@ + { + "env": { + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ] + } + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ac22f97..264caf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3305,6 +3305,11 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "oauth-1.0a": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.5.tgz", + "integrity": "sha512-7Y4Gs+2dPOjrlJDzEPdpuH46gzgYeVF7CKOOQNpWKz1nlbWn2rqKSt3oF0By6KxudbOQATEUY74SphuTN0grPg==" + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -3820,9 +3825,9 @@ "dev": true }, "sshpk": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", - "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", + "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", "requires": { "asn1": "0.2.4", "assert-plus": "1.0.0", diff --git a/package.json b/package.json index 92b0731..826258f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "firebase-admin": "^6.4.0", "lodash": "^4.17.11", "lokijs": "^1.5.5", + "oauth-1.0a": "^2.2.5", "phone": "^2.3.0", + "request": "^2.88.0", "twilio": "^3.25.0", "uuid": "^3.3.2" }, diff --git a/src/config/app.json b/src/config/app.json index 7dbe1cd..ba3e76a 100644 --- a/src/config/app.json +++ b/src/config/app.json @@ -28,6 +28,26 @@ "accountSid": "ACf62469621684ed007c01dfc9aa0923fa", "authToken": "62bbc8dc0bd42503dcc46eb17fb26895", "phoneNumber": "+17027108370 " + }, + "twitter": { + "consumer": { + "key": "u7pN7MZzYwXwnlofOdKyYJNfN", + "secret": "c4WvRNMYYNj6KZKWaqKDcvnM2Pz3tLbr9Za1EkKOmV948VEIYE" + }, + "token": { + "key": "58031606-MFz9HErfQBZPcFHIQBjCcJUUtQfYS4Gf2LfZjjtyr", + "secret": "mJVbfIADRHVjmS91BSSgweh1HcK3sk0FLutqFM6mWMWd3" + }, + "endpoints": { + "request_token": { + "url": "https://api.twitter.com/oauth/request_token", + "method": "POST" + }, + "access_token": { + "url": "https://api.twitter.com/oauth/access_token", + "method": "POST" + } + } } }, "dbPath": { diff --git a/src/config/errors.js b/src/config/errors.js index 33ae981..f55d172 100644 --- a/src/config/errors.js +++ b/src/config/errors.js @@ -12,6 +12,7 @@ module.exports = { AUTH_INVALID_SESSION_ID: 'auth/invalid-session-id', AUTH_UNAUTHORIZED_ACCESS: 'auth/unauthorized-access-into-api', AUTH_UNABLE_TO_EXTRACT_SESSION_ID_FROM_AUTH_HEADER: 'auth/unable-to-extract-session-id-from-auth-header', + AUTH_TWITTER_REQUEST_TOKEN_FAILURE: 'auth/twitter-request-token-failure', //App Errors APP_NETWORK_ERROR: 'app/network-error', APP_NETWORK_TIMEOUT: 'app/network-timeout', diff --git a/src/config/router.js b/src/config/router.js index 92e4f6b..6f53907 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -8,16 +8,18 @@ const ProfileHandlers = require('../ups/profiles/handlers'); */ function router(app) { // Auth Endpoints + app.get('/auth/twitter/token', AuthHandlers.getTwitterToken ); + app.get('/auth/twitter/username', AuthHandlers.getTwitterUsername ); app.get('/auth/email/:email/exists', AuthHandlers.doesAttributeExist ); app.get('/auth/tagferId/:tagferId/exists', AuthHandlers.doesAttributeExist ); app.post('/auth/phone/code', AuthHandlers.sendPhoneCode); app.post('/auth/phone/verify', AuthHandlers.verifyPhoneCode); app.post('/auth/signin', AuthHandlers.signin); app.put('/auth/signup', AuthHandlers.signup); - app.post('/auth/passwordReset', AuthHandlers.sendPasswordResetEmail) + app.post('/auth/passwordReset', AuthHandlers.sendPasswordResetEmail); // Users Endpoints - app.get('/users/by/phone', UserHandlers.findNetworkByPhone); + app.post('/users/by/phone', UserHandlers.findNetworkByPhone); // Profile Endpoints app.post('/profiles/:profileNumber', ProfileHandlers.updateUserProfile); diff --git a/src/ups/auth/dao.js b/src/ups/auth/dao.js index ec3f3bb..ce86a29 100644 --- a/src/ups/auth/dao.js +++ b/src/ups/auth/dao.js @@ -113,8 +113,8 @@ function createNewSessionId(tagferId) { * @throws AUTH_INVALID_SESSION_ID */ function getSession(id) { - const sessions = loki.getCollection("sessions"); - var session = sessions.by('id', id) + const sessions = loki.getCollection('sessions'); + var session = sessions.by('id', id); if (session) { return session; } else { diff --git a/src/ups/auth/handlers.js b/src/ups/auth/handlers.js index d02d3e6..1de5593 100644 --- a/src/ups/auth/handlers.js +++ b/src/ups/auth/handlers.js @@ -4,6 +4,7 @@ const utils = require('../utils/utils'); const errors = require('../../config/errors'); const appConfig = require('../../config/app.json'); const http = require('../../config/http'); +const twitter = require('../socials/twitter'); // Handlers /** @@ -105,7 +106,7 @@ async function signup(req, res) { } profile.name = appConfig.defaultProfileName; - profile.email = user.email + profile.email = user.email; try { await authDao.createNewUser(user); @@ -130,7 +131,32 @@ function sendPasswordResetEmail(req, res) { return; } - dao.resetPassword(email).then(() => res.json({}) ).catch( error => res.json(error)) + authDao.resetPassword(email).then(() => res.json({}) ).catch( error => res.json(error)); +} + +/** + * Endpoint: auth/twitter/token + * Calls the twitter endpoint using OAuth, to get the `oauth_token` + * @param {Object} req {} + * @param {Object} res { token: String } | { error: String } + */ +function getTwitterToken(req, res) { + if (!utils.isAppSecretValid(req,res)) { + return; + } + + twitter.getOAuthToken((result) => res.json(result)); +} + +/** + * Endpoint: auth/twitter/username + * Calls the twitter endpoint to get the `screen_name` + * @param {Object} req {} + * @param {Object} res HTML @see socials/twitter#passingHTML + */ +function getTwitterUsername(req, res) { + res.set('Content-Type', 'text/html'); + twitter.getUsername(req.query, (result) => res.send(result)); } module.exports = { @@ -139,5 +165,7 @@ module.exports = { verifyPhoneCode, signin, signup, - sendPasswordResetEmail + sendPasswordResetEmail, + getTwitterToken, + getTwitterUsername }; \ No newline at end of file diff --git a/src/ups/socials/twitter.js b/src/ups/socials/twitter.js new file mode 100644 index 0000000..f8e22e8 --- /dev/null +++ b/src/ups/socials/twitter.js @@ -0,0 +1,84 @@ +const request = require('request'); +const querystring = require('querystring'); + +const appConfig = require('../../config/app.json'); +const utils = require('../utils/utils'); +const errors = require('../../config/errors'); +const http = require('../../config/http'); + +/** + * Calls api.twiter.com/oauth/request_token + * + * @param {Function} Takes an object that either contains {`token`} or {`error`} + */ +function getOAuthToken(callback) { + const twitter = appConfig.keys.twitter; + const twitterRequest = twitter.endpoints.request_token; + twitterRequest.headers = utils.createOAuthHeader(twitterRequest, twitter); + + request(twitterRequest, (twitterError, twitterResponse, twitterBody) => { + if (!twitterError && twitterResponse.statusCode === http.OK) { + const tokens = querystring.parse(twitterBody); + callback({ token: tokens.oauth_token }); + } else { + callback({ error: errors.AUTH_TWITTER_REQUEST_TOKEN_FAILURE }); + } + }); +} + +/** + * Callback excecuted by the Twitter API. + * @param {Object} query { oauth_token: String, oauth_verifier: String } | { denied: String } + * @param {Function} callback non-void callback that accepts a parameter of the HTML string. + */ +function getUsername(query, callback) { + const twitter = appConfig.keys.twitter; + const { oauth_token, oauth_verifier, denied } = query; + + if (denied) { + callback(passingHTML(JSON.stringify({}))); + return; + } + + request({ + url: `${twitter.endpoints.access_token.url}?oauth_token=${oauth_token}&oauth_verifier=${oauth_verifier}`, + method: twitter.endpoints.access_token.method + }, (twitterError, twitterResponse, twitterBody) => { + let result = {}; + + if(!twitterError && twitterResponse.statusCode === http.OK ){ + const { screen_name } = querystring.parse(twitterBody); + result = { username: screen_name }; + } + + callback(passingHTML(JSON.stringify(result))); + }); +} + +/** + * Raw HTML passes the result using Window.postMessage from the WebView(DOM) back to ReactNative + */ +const passingHTML = (result) => ` + + + + + + +`; + +module.exports = { + getOAuthToken, + getUsername +}; \ No newline at end of file diff --git a/src/ups/users/dao.js b/src/ups/users/dao.js index f7270c6..8246d5d 100644 --- a/src/ups/users/dao.js +++ b/src/ups/users/dao.js @@ -38,7 +38,7 @@ function _getContactStatus(phoneNumber) { return admin.auth().getUserByPhoneNumber(phoneNumber).then( user => { return {inNetwork: true, tagferId: user.uid}; }).catch( error => { - return error === errors.AUTH_USER_NOT_FOUND ? { outNetwork: true, phoneNumber } : { error: error.code, phoneNumber}; + return error.code === errors.AUTH_USER_NOT_FOUND ? { outNetwork: true, phoneNumber } : { error: error.code, phoneNumber}; }); } diff --git a/src/ups/utils/utils.js b/src/ups/utils/utils.js index 1e80f4b..9c2a3f5 100644 --- a/src/ups/utils/utils.js +++ b/src/ups/utils/utils.js @@ -1,7 +1,9 @@ +const OAuth = require('oauth-1.0a'); +const crypto = require('crypto'); + const appConfig = require('../../config/app.json'); const http = require('../../config/http'); const errors = require('../../config/errors'); -const auth = require('../auth/dao'); /** * Verifies if the request is valid by checking if the request has the right app secret. @@ -21,7 +23,7 @@ function isAppSecretValid(req, res) { return true; } -function getSessionIdFromAuthHeader(req, res) { +function getSessionIdFromAuthHeader(req) { const usrToken = req.headers.authorization; if (usrToken) { @@ -52,9 +54,27 @@ function isProfileNumberValid(profileNumber, response) { } } +/** + * TODO: move this so other files in the socials dir can use it + * Creates the OAuth header to be passed into a request. + * @param {Object} request { method: String, url: String, data: Object } + * @param {Object} app { consumer: Object, token: Object }, each object is of the following { key: String, secret: String } + * @returns { Authorization: String } + */ +function createOAuthHeader(request, app) { + const oauth = OAuth({ + consumer: app.consumer, + signature_method: 'HMAC-SHA1', + hash_function: (base_string, key) => (crypto.createHmac('sha1', key).update(base_string).digest('base64')) + }); + + return oauth.toHeader(oauth.authorize(request, app.token)); +} + module.exports = { isAppSecretValid, isBodyValid, getSessionIdFromAuthHeader, - isProfileNumberValid + isProfileNumberValid, + createOAuthHeader }; \ No newline at end of file