Twitter Auth + Fix Previous Errors

This commit is contained in:
Omar 2018-12-30 16:07:26 -08:00
parent 506166b146
commit 4248e2e870
11 changed files with 205 additions and 14 deletions

29
.eslintrc Normal file
View File

@ -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"
]
}
}

11
package-lock.json generated
View File

@ -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",

View File

@ -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"
},

View File

@ -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": {

View File

@ -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',

View File

@ -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);

View File

@ -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 {

View File

@ -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
};

View File

@ -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) => `
<!DOCTYPE HTML>
<html>
<body>
<script>
function waitForBridge() {
if(window.postMessage.length !== 1){
setTimeout(waitForBridge, 200);
}
else {
window.postMessage('${result}');
}
}
window.onload = waitForBridge;
</script>
</body>
</html>
`;
module.exports = {
getOAuthToken,
getUsername
};

View File

@ -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};
});
}

View File

@ -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
};