mirror of
https://bitbucket.org/tagfer_team/tagfer-server.git
synced 2025-12-25 03:37:38 +00:00
Signup + Signin + Phone Verify + Sessions + Import Contacts
This commit is contained in:
parent
6d494482db
commit
35f15fd215
4293
package-lock.json
generated
Normal file
4293
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "tagfer-server",
|
||||
"version": "0.0.0",
|
||||
"description": "Express server hosts the business logic behind tagfer.inc",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/mihilmy/tagfer-server.git"
|
||||
},
|
||||
"keywords": [
|
||||
"api"
|
||||
],
|
||||
"author": "@mihilmy, @oonyeje",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mihilmy/tagfer-server/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mihilmy/tagfer-server#readme",
|
||||
"dependencies": {
|
||||
"bcrypt": "^3.0.2",
|
||||
"express": "^4.16.4",
|
||||
"firebase": "^5.7.0",
|
||||
"firebase-admin": "^6.4.0",
|
||||
"lodash": "^4.17.11",
|
||||
"lokijs": "^1.5.5",
|
||||
"phone": "^2.3.0",
|
||||
"twilio": "^3.25.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^5.9.0",
|
||||
"faker": "^4.1.0"
|
||||
}
|
||||
}
|
||||
14
server.js
Normal file
14
server.js
Normal file
@ -0,0 +1,14 @@
|
||||
require('./src/config/firebase');
|
||||
|
||||
const express = require('express');
|
||||
const appConfig = require('./src/config/app.json');
|
||||
const router = require('./src/config/router');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
router(app);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
app.listen(appConfig.server.port, () => console.log('Listening on port 3000!'));
|
||||
41
src/config/app.json
Normal file
41
src/config/app.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"server": {
|
||||
"port": 3000
|
||||
},
|
||||
"keys": {
|
||||
"appSecret": "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJujjNO60eZu3GUU+1Tx9ruhVzlbjlpfZlvROONBqwj6jOgd86dTPtbfvBZO7dKlwiIikLJNP6tpFeNohxO6EcsCAwEAAQ",
|
||||
"firebaseAdmin": {
|
||||
"type": "service_account",
|
||||
"project_id": "tagfer-inc",
|
||||
"private_key_id": "bd5a13081cb521ea1b43f374d3bdc1f09b13ef99",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDLbi762wbF2uO9\ngPalp+FT3HxPmt4PSstdCwUofjFVrbfsS0hupcu+y2p81moyfi60upGPE63oQOdi\natmkvtJ1L2jiFwqB9NXuEWNJr/hY804sVzwka0NnOc2DYWA5B5Jh0PFyw0jqt2+y\nNG02/8viL0Qtw8ghnnkRpA5j5SByACkmdpYi9OpDB9GBp0II3R9l/CNHJyyG/v8Q\n1OLweURYE+53fMnqUjhmQazq6EMQwjrxyrTojUUyYZUq+n3D20bJteDl8Hn1vUyh\nHz3zwvUD+2CjUXwxrg026ArIsBy4Hq9mjQjjjzIEcGpzA3SxRSkr2FweJ8ZZgsbU\n5lEZpv6tAgMBAAECggEACeH+np7mYNVQoWOzAnrklk43VydcaLIcWBb8If/4xe33\nFJ/W/dJzJ//k2qS7VbGjSOdpa+HWXdqirbl9fPns6oF/nxWPI/TGMdcevjUyI6F0\n/PAjUqzyrNX2NyQ+EvcxtCvSQbOIBtMbi4pC0LGzm3eVYIMgOrXisX80AxleA/u0\nwC3wuEMRX5DMwkSqYFdD/06G6Z1FM+07vueeixEwE1aj4yXoCAbxdxrHYIeygpgz\nIb68giI78j9tw7bKYAwrdwsDqKk10T0CFp+YUKolPY5aEF3lAHuG0bUvdUI/OyPp\nNgSxSXrwa9KlZovZbrjxUWvjM0RoX+7bEZcpZqcdFwKBgQD59w/lpjzuImL83kKt\n9YyDVilhOnx4JQRl0s7pbk/BJjp+9uXZEC/3z1xESy5duDgV8noIzLWg2pXUBG+L\n+DSAQnxG1BcaVyD/85oBgnLdcnG+AlsMqWct+UPzdsm9o26vvBpUipKfvJnGg1qR\nJJvQv55hDnQg4CCpBHzUXTg/awKBgQDQV4IrbOi9iLBiDc2eFePOY+mu1Z0ymsqA\nd09qoABHh/y+FvBkR9LFeGOfAd6O0ZZ0jOGA9FfpBlPhmKzJ0eEb9MRJTQ1RUyZi\nY+utttBVjxPsoAck2EdbZVx9Svtbtnrt3IyEYoKMq9u92gmDFUFBnJnh1AE9iP2H\nNEV+JQo4RwKBgQC/I98yGnZJGl5bQpH2d+eknoQx5wk6zgOY4SR7d3DhH5xnbeDA\npRIpCpVhW6Pu4mlwzuPmSrMwdzVO1L1/aKKs2Soy9wdbivie/+Xp9ZhkIZk8VIzP\nF9LgYtVFHLaTnp+LHel8cCJCp3NnSxY8GqRTcdNoICdI5FnVJKtXsJjMVQKBgQCM\neTRPS1Nx1+P1eREWcfPziPJa67TeFfhLviZR4ifOEyaalKTpOHQoqQ+ieoQxD6e+\nVe8GH7nWaGnORj7apSR+0P433jgIiWPsGyshKY424g2xEgU/FoSmXyWJZTEtmVAx\naO9lo3YamxXCYGzhcUdakdg/p85eSyuGKfxhHWBSqQKBgQC9eBtfcoK9TeK8M6SD\nLOI3Unl36CEAdLG9KVZnbrw8cFjXRoSdeHGiTQGwnjKGFl85P0N118ds/thngA/i\nuc2DbkLczXSFaPNFLlEijwAeVROzl19KxuP9PwhOdJjdKQOazc7BiEyaMlPp6lfE\neFGb6qu0ZoT7vaEt4rSA2iLpPw==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-tcgaf@tagfer-inc.iam.gserviceaccount.com",
|
||||
"client_id": "112076836493715153722",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-tcgaf%40tagfer-inc.iam.gserviceaccount.com"
|
||||
},
|
||||
"firebase": {
|
||||
"apiKey": "AIzaSyCtyieRAu2Vl_0i9KvnD8aYfAqFGUnWDkY",
|
||||
"authDomain": "tagfer-inc.firebaseapp.com",
|
||||
"databaseURL": "https://tagfer-inc.firebaseio.com",
|
||||
"projectId": "tagfer-inc",
|
||||
"storageBucket": "tagfer-inc.appspot.com",
|
||||
"messagingSenderId": "497562483601"
|
||||
},
|
||||
"twilio": {
|
||||
"accountSid": "ACf62469621684ed007c01dfc9aa0923fa",
|
||||
"authToken": "62bbc8dc0bd42503dcc46eb17fb26895",
|
||||
"phoneNumber": "+17027108370 "
|
||||
}
|
||||
},
|
||||
"dbPath": {
|
||||
"users": "users"
|
||||
},
|
||||
"loki" : {
|
||||
"verificationTTL": 300000,
|
||||
"verificationTTLClear": 1800000
|
||||
}
|
||||
|
||||
}
|
||||
15
src/config/errors.js
Normal file
15
src/config/errors.js
Normal file
@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
AUTH_INVALID_EMAIL: 'auth/invalid-email',
|
||||
AUTH_INVALID_PHONE_NUMBER: 'auth/invalid-phone-number',
|
||||
AUTH_USER_NOT_FOUND: 'auth/user-not-found',
|
||||
AUTH_WRONG_PASSWORD: 'auth/wrong-password',
|
||||
AUTH_UID_ALREADY_EXISTS: 'auth/uid-already-exists',
|
||||
AUTH_EMAIL_ALREADY_EXISTS: 'auth/email-already-exists',
|
||||
AUTH_PHONE_ALREADY_EXISTS: 'auth/phone-number-already-exists',
|
||||
AUTH_PHONE_NOT_CACHED: 'auth/phone-number-not-cached',
|
||||
AUTH_VERIFICATION_CODE_MISMATCH: 'auth/phone-verification-code-mismatch',
|
||||
APP_NETWORK_ERROR: 'app/network-error',
|
||||
APP_NETWORK_TIMEOUT: 'app/network-timeout',
|
||||
APP_UNABLE_TO_PARSE_RESPONSE: 'app/unable-to-parse-response',
|
||||
MISSING_BODY_ATTRIBUTES: 'request/missing-body-attributes'
|
||||
};
|
||||
11
src/config/firebase.js
Normal file
11
src/config/firebase.js
Normal file
@ -0,0 +1,11 @@
|
||||
const admin = require('firebase-admin');
|
||||
const firebase = require('firebase');
|
||||
|
||||
const appConfig = require('./app.json');
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(appConfig.keys.firebaseAdmin),
|
||||
databaseURL: appConfig.keys.firebase.databaseURL
|
||||
});
|
||||
|
||||
firebase.initializeApp(appConfig.keys.firebase);
|
||||
8
src/config/http.js
Normal file
8
src/config/http.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
INTERNAL_SERVER_ERROR: 500
|
||||
};
|
||||
27
src/config/loki.js
Normal file
27
src/config/loki.js
Normal file
@ -0,0 +1,27 @@
|
||||
const Loki = require('lokijs');
|
||||
|
||||
const appConfig = require('./app.json');
|
||||
|
||||
const loki = new Loki('store.json', { autosave: true, autoload: true, autoloadCallback: lokiInit });
|
||||
|
||||
/**
|
||||
* Initializes the collections in our local loki
|
||||
*/
|
||||
function lokiInit() {
|
||||
let sessions = loki.getCollection('sessions');
|
||||
let verifications = loki.getCollection('verifications');
|
||||
|
||||
if (sessions == null) {
|
||||
sessions = loki.addCollection('sessions', { unique: ['id'] });
|
||||
}
|
||||
|
||||
if(verifications == null) {
|
||||
verifications = loki.addCollection('verifications', {
|
||||
unique: ['phoneNumber'],
|
||||
ttl: appConfig.loki.verificationTTL,
|
||||
ttlInterval: appConfig.loki.verificationTTLClear
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = loki;
|
||||
21
src/config/router.js
Normal file
21
src/config/router.js
Normal file
@ -0,0 +1,21 @@
|
||||
const AuthHandlers = require('../ups/auth/handlers');
|
||||
const UserHandlers = require('../ups/users/handlers');
|
||||
|
||||
/**
|
||||
* Main router for our application, include all routes here. No logic should be added in this file.
|
||||
* @param {Express} app
|
||||
*/
|
||||
function router(app) {
|
||||
// Auth Endpoints
|
||||
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);
|
||||
|
||||
// Users Endpoints
|
||||
app.get('/users/by/phone', UserHandlers.findNetworkByPhone);
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
30
src/config/twilio.js
Normal file
30
src/config/twilio.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Twilio = require('twilio');
|
||||
|
||||
const appConfig = require('./app.json');
|
||||
|
||||
const authToken = appConfig.keys.twilio.authToken;
|
||||
const accountSid = appConfig.keys.twilio.accountSid;
|
||||
const tagferPhone = appConfig.keys.twilio.phoneNumber;
|
||||
const client = new Twilio(accountSid, authToken);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function sendSMSTo(clientPhone, message, callback) {
|
||||
const sms = {
|
||||
from: tagferPhone,
|
||||
body: message,
|
||||
to: clientPhone
|
||||
};
|
||||
|
||||
client.messages.create(sms).then(() => {
|
||||
callback({result: true});
|
||||
}).catch(error => {
|
||||
//TODO: add twilio error handler
|
||||
callback({error: error.message});
|
||||
}).done();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendSMSTo
|
||||
};
|
||||
173
src/ups/auth/dao.js
Normal file
173
src/ups/auth/dao.js
Normal file
@ -0,0 +1,173 @@
|
||||
const firebase = require('firebase');
|
||||
const admin = require('firebase-admin');
|
||||
const uuid = require('uuid/v4');
|
||||
const _ = require('lodash');
|
||||
|
||||
const errors = require('../../config/errors');
|
||||
const loki = require('../../config/loki');
|
||||
const twilio = require('../../config/twilio');
|
||||
|
||||
/**
|
||||
* Checks if the email exists in our auth system
|
||||
*/
|
||||
function doesEmailExist(email) {
|
||||
return _doesKeyValueExist({email});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tagferId exists in our auth system
|
||||
*/
|
||||
function doesTagferIdExist(tagferId) {
|
||||
return _doesKeyValueExist({tagferId});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the phone number exists in our auth system
|
||||
*/
|
||||
function doesPhoneExist(phoneNumber) {
|
||||
return _doesKeyValueExist({phoneNumber});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in user using firebase client api
|
||||
* @param {String} email
|
||||
* @param {String} password
|
||||
*/
|
||||
function signinWithEmail(email, password) {
|
||||
return firebase.auth().signInWithEmailAndPassword(email, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in with tagferId using the admin sdk to get email and then the firebase client to sign in
|
||||
* @param {String} tagferId
|
||||
* @param {String} password
|
||||
*/
|
||||
function signinWithTagferId(tagferId, password) {
|
||||
return admin.auth().getUser(tagferId).then(user => signinWithEmail(user.email, password) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user in firebase auth
|
||||
* @param {Object} user
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function createNewUser(user) {
|
||||
return admin.auth().createUser({
|
||||
uid: user.tagferId.toLowerCase(),
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
phoneNumber: user.phoneNumber,
|
||||
displayName: _.startCase(user.fullName)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} phone
|
||||
* @param {number} usrCode
|
||||
*/
|
||||
function isVerificationCodeCorrect(phoneNumber, usrCode) {
|
||||
try {
|
||||
const sysCode = getVerificationCode(phoneNumber);
|
||||
return {result: usrCode == sysCode};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the verification code by calling twilio and saving the code to our local storgae
|
||||
* @param {string} phoneNumber
|
||||
* @param {function} callback
|
||||
*/
|
||||
function sendVerificationCode(phoneNumber, callback) {
|
||||
const code = createNewVerificationCode(phoneNumber);
|
||||
const message = `Tagfer PIN: ${code}`;
|
||||
twilio.sendSMSTo(phoneNumber, message, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session for security of users and to maintain states of the requests.
|
||||
* @returns Session identifier to manage state of the user
|
||||
*/
|
||||
function createNewSessionId(tagferId) {
|
||||
const sessionId = uuid();
|
||||
const sessions = loki.getCollection('sessions');
|
||||
sessions.insert({ id: sessionId, tagferId });
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new verification code for the phone number
|
||||
*/
|
||||
function createNewVerificationCode(phoneNumber) {
|
||||
const code = Math.floor(((Math.random() * 899999) + 100000));
|
||||
const verifications = loki.getCollection('verifications');
|
||||
|
||||
try {
|
||||
verifications.insert({ phoneNumber, code });
|
||||
} catch (error){
|
||||
const record = verifications.by('phoneNumber', phoneNumber);
|
||||
record.code = code;
|
||||
verifications.update(record);
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the verification code from loki by phone number
|
||||
* @param {string} phoneNumber phone number
|
||||
*/
|
||||
function getVerificationCode(phoneNumber) {
|
||||
const verifications = loki.getCollection('verifications');
|
||||
|
||||
const record = verifications.by('phoneNumber', phoneNumber);
|
||||
if (!record) {
|
||||
throw errors.AUTH_PHONE_NOT_CACHED;
|
||||
}
|
||||
|
||||
return record.code;
|
||||
}
|
||||
|
||||
function _doesKeyValueExist({email, tagferId, phoneNumber}) {
|
||||
let promise;
|
||||
|
||||
if (email) { promise = admin.auth().getUserByEmail(email); }
|
||||
if (tagferId) { promise = admin.auth().getUser(tagferId.toLowerCase());}
|
||||
if (phoneNumber) {promise = admin.auth().getUserByPhoneNumber(phoneNumber); }
|
||||
|
||||
return promise.then(() => { return true; })
|
||||
.catch(error => {
|
||||
if (error.code === errors.AUTH_USER_NOT_FOUND) {
|
||||
return false;
|
||||
} else {
|
||||
throw { error: error.code };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
doesEmailExist,
|
||||
doesTagferIdExist,
|
||||
doesPhoneExist,
|
||||
sendVerificationCode,
|
||||
isVerificationCodeCorrect,
|
||||
signinWithTagferId,
|
||||
signinWithEmail,
|
||||
createNewUser,
|
||||
createNewSessionId
|
||||
};
|
||||
|
||||
// function signinWithTagferId1(tagferId, password, callback) {
|
||||
// const path = `users/${tagferId}/hash`;
|
||||
|
||||
// db.ref(path).once('value').then( data => {
|
||||
// if(!data.exists()) {
|
||||
// callback({error: errors.AUTH_USER_NOT_FOUND});
|
||||
// }
|
||||
// const hash = data.val();
|
||||
// bcrypt.compare(password, hash).then(result => callback({result}));
|
||||
// });
|
||||
// }
|
||||
116
src/ups/auth/handlers.js
Normal file
116
src/ups/auth/handlers.js
Normal file
@ -0,0 +1,116 @@
|
||||
const dao = require('./dao');
|
||||
const utils = require('../utils/utils');
|
||||
const errors = require('../../config/errors');
|
||||
|
||||
// Handlers
|
||||
/**
|
||||
* Endpoints: auth/email/:email/exists or auth/tagferId/:tagferId/exists
|
||||
* Extracts the parameter from the request and calls the appropriate function to check if the attribute exists.
|
||||
* @param {Object} req
|
||||
* @param {Object} res {result: Boolean} | {error: String}
|
||||
*/
|
||||
function doesAttributeExist(req, res) {
|
||||
const { email, tagferId } = req.params;
|
||||
if (!utils.isAppSecretValid(req,res)) {
|
||||
return;
|
||||
}
|
||||
var promise = email? dao.doesEmailExist(email) : dao.doesTagferIdExist(tagferId.toLowerCase());
|
||||
|
||||
promise.then((result) => res.json({ result }) ).catch(error => res.json(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request/response for auth/phone/code. Uses the data layer to generate a unique code,
|
||||
* if the phone number is not in our system.
|
||||
* @param {Object} req request body contains number
|
||||
* @param {Object} res {code: String} | {error: String}
|
||||
*/
|
||||
function sendPhoneCode(req, res) {
|
||||
const verifier = () => req.body.phoneNumber;
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneNumber = req.body.phoneNumber;
|
||||
dao.doesPhoneExist(phoneNumber).then( result => {
|
||||
if (result === false) {
|
||||
dao.sendVerificationCode(phoneNumber, status => res.json(status));
|
||||
} else{
|
||||
throw { error: errors.AUTH_PHONE_ALREADY_EXISTS };
|
||||
}
|
||||
}).catch( error => res.json(error) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request/response for auth/phone/verify. Uses the data layer to verify that the code is correct.
|
||||
* @param {Object} req request is a json that has both attributes { phone, code }
|
||||
* @param {Object} res express request object
|
||||
*/
|
||||
function verifyPhoneCode(req, res) {
|
||||
const verifier = () => req.body.phoneNumber && req.body.code;
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneNumber = req.body.phoneNumber;
|
||||
const code = req.body.code;
|
||||
res.json(dao.isVerificationCodeCorrect(phoneNumber, code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint: auth/signin
|
||||
* Signs the user in using firebase auth
|
||||
* @param {Object} req {email: String, password: String} | {tagferId: String, password: String}
|
||||
* @param {Object} res {sessionId: String} | {error: String}
|
||||
*/
|
||||
function signin(req, res) {
|
||||
const { email, tagferId, password } = req.body;
|
||||
const verifier = () => (email || tagferId) && password;
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var promise;
|
||||
|
||||
if (email) {
|
||||
promise = dao.signinWithEmail(email, password);
|
||||
} else {
|
||||
promise = dao.signinWithTagferId(tagferId, password);
|
||||
}
|
||||
|
||||
promise.then( ({ user })=> {
|
||||
const sessionId = dao.createNewSessionId(user.uid);
|
||||
res.json({sessionId});
|
||||
}).catch(error => {
|
||||
res.json( {error: error.code} );
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint: auth/signup
|
||||
* Signs up the user by creating a new user in firebase admin.
|
||||
* @param {Object} req { tagferId: String, email: String, password: String, phoneNumber: String, fullName: String }
|
||||
* @param {Object} res { sessionId: String } | { error: String }
|
||||
*/
|
||||
function signup(req, res) {
|
||||
const user = req.body;
|
||||
const verifier = () => user.tagferId && user.email && user.password && user.phoneNumber && user.fullName;
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dao.createNewUser(user).then( user => {
|
||||
const sessionId = dao.createNewSessionId(user.uid);
|
||||
res.json({sessionId});
|
||||
}).catch(error => {
|
||||
res.json( {error: error.code} );
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
doesAttributeExist,
|
||||
sendPhoneCode,
|
||||
verifyPhoneCode,
|
||||
signin,
|
||||
signup
|
||||
};
|
||||
47
src/ups/users/dao.js
Normal file
47
src/ups/users/dao.js
Normal file
@ -0,0 +1,47 @@
|
||||
const admin = require('firebase-admin');
|
||||
|
||||
const errors = require('../../config/errors');
|
||||
|
||||
/**
|
||||
* Filters contacts into three batches:
|
||||
* 1. Contacts that are in the tagfer network.
|
||||
* 2. Contacts that are out of the network.
|
||||
* 3. Contacts that failed to be processed.
|
||||
* @param {Array} contacts
|
||||
* @returns {Object} groups inNetwork, outNetwork, failedBatch
|
||||
*/
|
||||
async function filterContactsIntoBatches(contacts) {
|
||||
const promises = [];
|
||||
const inNetworkBatch = [];
|
||||
const outNetworkBatch = [];
|
||||
const failedBatch = [];
|
||||
|
||||
const addToBatch = (contact) => contact.inNetwork? inNetworkBatch.push(contact.tagferId) : contact.outNetwork? outNetworkBatch.push(contact.phoneNumber) : failedBatch.push(contact.phoneNumber);
|
||||
|
||||
for (let i = 0; i < contacts.length; i++) {
|
||||
if (contacts[i]) {
|
||||
promises.push(_getContactStatus(contacts[i]));
|
||||
}
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
for(let i = 0; i < results.length; i++) {
|
||||
if (results[i])
|
||||
addToBatch(results[i]);
|
||||
}
|
||||
|
||||
return {inNetworkBatch, outNetworkBatch, failedBatch};
|
||||
}
|
||||
|
||||
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};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
filterContactsIntoBatches
|
||||
};
|
||||
26
src/ups/users/handlers.js
Normal file
26
src/ups/users/handlers.js
Normal file
@ -0,0 +1,26 @@
|
||||
const phone = require('phone');
|
||||
|
||||
const dao = require('./dao');
|
||||
const utils = require('../utils/utils');
|
||||
|
||||
/**
|
||||
* Endpoint: users/by/phone
|
||||
* Seperates the phone numbers into batches in/out of Tagfer network, receiver should use this information to either
|
||||
* add people who already in network, or send invites or repeat calls for failed batches.
|
||||
* @param {Object} req {contacts: Array<PhoneNumber>}
|
||||
* @param {Object} res {inNetworkBatch : Array<PhoneNumber>, outNetworkBatch: Array<PhoneNumber>, failedBatch: Array<PhoneNumber> }
|
||||
*/
|
||||
async function findNetworkByPhone(req, res) {
|
||||
const contacts = req.body.contacts;
|
||||
const verifier = () => contacts && Array.isArray(contacts);
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batches = await dao.filterContactsIntoBatches(contacts.map((number) => phone(number)[0]));
|
||||
res.json(batches);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findNetworkByPhone
|
||||
};
|
||||
38
src/ups/utils/utils.js
Normal file
38
src/ups/utils/utils.js
Normal file
@ -0,0 +1,38 @@
|
||||
const appConfig = require('../../config/app.json');
|
||||
const http = require('../../config/http');
|
||||
const errors = require('../../config/errors');
|
||||
|
||||
/**
|
||||
* Verifies if the request is valid by checking if the request has the right app secret.
|
||||
* @param {Object} req express request object
|
||||
* @param {Object} res express response object
|
||||
* @returns true if the app secret is valid and false otherwise
|
||||
*/
|
||||
function isAppSecretValid(req, res) {
|
||||
const usrToken = req.headers.authorization;
|
||||
const sysToken = appConfig.keys.appSecret;
|
||||
|
||||
if( usrToken !== sysToken) {
|
||||
res.status(http.UNAUTHORIZED).json({ error: 'Unauthorized access into api' });
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Checks if the body is valid
|
||||
* @param {Function} isValid verifier
|
||||
*/
|
||||
function isBodyValid(isValid, response) {
|
||||
if (!isValid()) {
|
||||
response.status(http.BAD_REQUEST).json({error: errors.MISSING_BODY_ATTRIBUTES});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAppSecretValid,
|
||||
isBodyValid
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user