From 9f247154f98e5cc69c7230b9f2466164b17b4161 Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Thu, 10 Jan 2019 10:37:07 -0800 Subject: [PATCH] all setup with jwt and bcrypt with whole file structure with auth middleware and login --- app/config/db.config.js | 47 ++++++------ app/controllers/auth.controller.js | 60 +++++++++++++++ app/controllers/donors.controller.js | 110 +++++++++++++++++++-------- app/middleware/check-auth.js | 15 ++++ app/models/donors.model.js | 3 +- app/routes/auth.route.js | 10 +++ app/routes/donors.route.js | 9 ++- package-lock.json | 95 +++++++++++++++++++++++ package.json | 52 +++++++------ server.js | 74 +++++++++++------- 10 files changed, 360 insertions(+), 115 deletions(-) create mode 100644 app/controllers/auth.controller.js create mode 100644 app/middleware/check-auth.js create mode 100644 app/routes/auth.route.js diff --git a/app/config/db.config.js b/app/config/db.config.js index 0f33f0d..b78ef2d 100644 --- a/app/config/db.config.js +++ b/app/config/db.config.js @@ -1,32 +1,29 @@ const Sequelize = require('sequelize'), - DonorsModel = require('../models/donors.model.js'), - CampaignsModel = require('../models/campaigns.model.js'); - + DonorsModel = require('../models/donors.model.js'), + CampaignsModel = require('../models/campaigns.model.js'); const host = 'am1shyeyqbxzy8gc.cbetxkdyhwsb.us-east-1.rds.amazonaws.com', - username = 'fyro63k2989tyibh', - password = 'ykjkyenyvxig208z', - port = '3306', - database = 'n0j9gxnf4ijr7g8t'; + username = 'fyro63k2989tyibh', + password = 'ykjkyenyvxig208z', + port = '3306', + database = 'n0j9gxnf4ijr7g8t'; -const sequelize = new Sequelize( - database, - username, - password, - { - host: host, - dialect: 'mysql', - operatorsAliases: false, +const sequelize = new Sequelize(database, username, password, { + host: host, + dialect: 'mysql', + operatorsAliases: false, - // research for pool/connections - pool: { - max: 5, - min: 0, - acquire: 30000, - idle: 10000 - } - } -); + // research for pool/connections + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + }, + + // disable logging; default: console.log + logging: false +}); const db = {}; @@ -39,4 +36,4 @@ db.campaigns = Campaigns; db.Sequelize = Sequelize; db.sequelize = sequelize; -module.exports = db; \ No newline at end of file +module.exports = db; diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js new file mode 100644 index 0000000..ac80ec2 --- /dev/null +++ b/app/controllers/auth.controller.js @@ -0,0 +1,60 @@ +const db = require('../config/db.config.js'), + bcrypt = require('bcrypt-nodejs'), + jwt = require('jsonwebtoken'), + checkAuth = require('../middleware/check-auth'); + +const Donors = db.donors; + +// Find a Donor by email + login with JWT +exports.login = (req, res) => { + Donors.findAll({ + where: { + email: req.body.email + } + }) + .then(donors => { + if (donors.length < 1) { + return res.status(401).json({ + message: 'Auth failed' + }); + } + bcrypt.compare( + req.body.password, + donors[0].password, + (error, result) => { + if (error) { + return res.status(401).json({ + message: 'Auth failed' + }); + } + if (result) { + const token = jwt.sign( + { + email: donors[0].email, + id: donors[0].id + }, + process.env.JWT_KEY, + { + expiresIn: '1h' + } + ); + return res.status(200).json({ + message: 'Auth successful', + donor: donors[0], + token + }); + } + return res.status(401).json({ + message: 'Auth failed' + }); + } + ); + }) + .catch(error => { + console.log(error); + + return res.status(500).json({ + error + }); + }); +}; diff --git a/app/controllers/donors.controller.js b/app/controllers/donors.controller.js index 5d0dd91..96a5646 100644 --- a/app/controllers/donors.controller.js +++ b/app/controllers/donors.controller.js @@ -1,58 +1,104 @@ -const db = require('../config/db.config.js'); +const db = require('../config/db.config.js'), + bcrypt = require('bcrypt-nodejs'); + const Donors = db.donors; - -// Post a Donor -exports.create = (req, res) => { - // Save to MySQL database - Donors.create({ - first_name: req.body.first_name, - middle_name: req.body.middle_name, - last_name: req.body.last_name, - email: req.body.email, - age: req.body.age, - phone: req.body.phone, - address: req.body.address, - city: req.body.city, - state: req.body.state, - country: req.body.country - }).then(donor => { - // Send created donor to client - res.send(donor); - }); + +// Create/post a Donor +exports.create = (req, res) => { + // see if user already in db + Donors.findAll({ + where: { + email: req.body.email + } + }) + .then(donors => { + if (donors.length >= 1) { + return res.status(409).json({ + message: 'Email exists' + }); + } else { + // hash and store + bcrypt.hash(req.body.password, null, null, function( + error, + hash + ) { + // Store hash in your password DB. + if (error) { + return res.status(500).json({ + error + }); + } else { + Donors.create({ + first_name: req.body.first_name, + middle_name: req.body.middle_name, + last_name: req.body.last_name, + email: req.body.email, + password: hash, //hashed password + age: req.body.age, + phone: req.body.phone, + address: req.body.address, + city: req.body.city, + state: req.body.state, + country: req.body.country + }) + .then(donor => { + // Send created donor to client + return res.status(201).json({ + message: 'User created', + donor + }); + }) + .catch(error => { + console.log(error); + + return res.status(500).json({ + error + }); + }); + } + }); + } + }) + .catch(error => { + console.log(error); + + return res.status(500).json({ + error + }); + }); }; - + // FETCH all Donors exports.findAll = (req, res) => { Donors.findAll().then(donors => { - // Send all donors to Client - res.send(donors); + // Send all donors to Client + res.status(200).send(donors); }); }; - + // Find a Donor by Id -exports.findById = (req, res) => { +exports.findById = (req, res) => { Donors.findById(req.params.donor_id).then(donor => { res.send(donor); - }) + }); }; - + // Delete a Donor by Id exports.delete = (req, res) => { const id = req.params.donor_id; Donors.destroy({ - where: { id: id } + where: { id: id } }).then(() => { - res.status(200).send('deleted successfully a donor with id = ' + id); + res.status(200).send('deleted successfully a donor with id = ' + id); }); }; - // // Update a Donor // exports.update = (req, res) => { // const id = req.params.donor_id; -// Donors.update( { firstname: req.body.firstname, lastname: req.body.lastname, age: req.body.age }, +// Donors.update( { firstname: req.body.firstname, lastname: req.body.lastname, age: req.body.age }, // { where: {id: req.params.donorId} } // ).then(() => { // res.status(200).send("updated successfully a donor with id = " + id); // }); -// }; \ No newline at end of file +// }; diff --git a/app/middleware/check-auth.js b/app/middleware/check-auth.js new file mode 100644 index 0000000..8496680 --- /dev/null +++ b/app/middleware/check-auth.js @@ -0,0 +1,15 @@ +const jwt = require('jsonwebtoken'); + +module.exports = (req, res, next) => { + try { + const token = req.headers.authorization.split(' ')[1]; + const decoded = jwt.verify(token, process.env.JWT_KEY); + req.donorData = decoded; //for use till end of request + + next(); + } catch (error) { + return res.status(401).json({ + message: 'Auth failed' + }); + } +}; diff --git a/app/models/donors.model.js b/app/models/donors.model.js index 58d106a..26c8ca0 100644 --- a/app/models/donors.model.js +++ b/app/models/donors.model.js @@ -9,6 +9,7 @@ module.exports = (sequelize, DataTypes) => { middle_name: DataTypes.STRING, last_name: DataTypes.STRING, email: DataTypes.STRING, + password: DataTypes.STRING, age: DataTypes.INTEGER, phone: DataTypes.BIGINT, address: DataTypes.STRING, @@ -21,4 +22,4 @@ module.exports = (sequelize, DataTypes) => { } ); return Donors; -} \ No newline at end of file +} diff --git a/app/routes/auth.route.js b/app/routes/auth.route.js new file mode 100644 index 0000000..d1d5e3c --- /dev/null +++ b/app/routes/auth.route.js @@ -0,0 +1,10 @@ +const express = require('express'), + router = express.Router(), + checkAuth = require('../middleware/check-auth'); + +const auth = require('../controllers/auth.controller.js'); + +// Check database for donor +router.post('/donor/login', auth.login); + +module.exports = router; diff --git a/app/routes/donors.route.js b/app/routes/donors.route.js index 371e9d9..3f7aa17 100644 --- a/app/routes/donors.route.js +++ b/app/routes/donors.route.js @@ -1,13 +1,14 @@ const express = require('express'), - router = express.Router(); + router = express.Router(), + checkAuth = require('../middleware/check-auth'); const donors = require('../controllers/donors.controller.js'); // Create a new Donor router.post('/', donors.create); -// Retrieve all Donor -router.get('/', donors.findAll); +// Retrieve all Donors +router.get('/', checkAuth, donors.findAll); // Retrieve a single Donor by Id router.get('/:DonorId', donors.findById); @@ -18,4 +19,4 @@ router.delete('/:DonorId', donors.delete); // // Update a Donor with Id // router.put('/api/donors/:DonorId', donors.update); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/package-lock.json b/package-lock.json index 11361d1..659cac2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "bcrypt-nodejs": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", + "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" + }, "bluebird": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", @@ -50,6 +55,11 @@ "type-is": "~1.6.16" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -112,6 +122,14 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -250,11 +268,88 @@ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, + "jsonwebtoken": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", diff --git a/package.json b/package.json index 01597f5..23fefdd 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,29 @@ { - "name": "ucharify_api", - "version": "1.0.0", - "description": "Backend API for Ucharify. Completely separate infrastructure that connects to DB.", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "PORT=4200 & node server.js", - "dev": "set NODE_ENV=test & set PORT=4205 & nodemon server.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/talksik/Ucharify_api.git" - }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/talksik/Ucharify_api/issues" - }, - "homepage": "https://github.com/talksik/Ucharify_api#readme", - "dependencies": { - "body-parser": "^1.18.3", - "express": "^4.16.4", - "mysql2": "^1.6.4", - "sequelize": "^4.42.0" - } + "name": "ucharify_api", + "version": "1.0.0", + "description": "Backend API for Ucharify. Completely separate infrastructure that connects to DB.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "PORT=4200 & set JWT_KEY=secretkey & node server.js", + "dev": "set NODE_ENV=test & set PORT=4205 & set JWT_KEY=secretkey & nodemon server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/talksik/Ucharify_api.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/talksik/Ucharify_api/issues" + }, + "homepage": "https://github.com/talksik/Ucharify_api#readme", + "dependencies": { + "bcrypt-nodejs": "0.0.3", + "body-parser": "^1.18.3", + "express": "^4.16.4", + "jsonwebtoken": "^8.4.0", + "mysql2": "^1.6.4", + "sequelize": "^4.42.0" + } } diff --git a/server.js b/server.js index fafd2cd..7a3fc6d 100644 --- a/server.js +++ b/server.js @@ -1,44 +1,62 @@ //require dependencies const express = require('express'), - app = express(), - router = express.Router(), - bodyParser = require('body-parser'), - port = process.env.PORT || 4200, - db = require('./app/config/db.config.js'); + app = express(), + router = express.Router(), + bodyParser = require('body-parser'), + port = process.env.PORT || 4200, + db = require('./app/config/db.config.js'); -app.use(bodyParser.json()) +app.use(bodyParser.json()); + +//handle CORS errors +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header( + 'Access-Control-Allow-Headers', + 'Origin, X-Requested-With, Content-Type, Accept, Authorization' + ); + + if (req.method == 'OPTIONS') { + res.header( + 'Access-Control-Allow-Methods', + 'PUT, POST, GET, DELETE, PATCH' + ); + return res.status(200).json({}); + } + + next(); +}); //verify connection to db db.sequelize - .authenticate() - .then(() => { - console.log('Connection has been established successfully.'); - }) - .catch(err => { - console.error('Unable to connect to the database:', err); - }); + .authenticate() + .then(() => { + console.log('Connection has been established successfully.'); + }) + .catch(err => { + console.error('Unable to connect to the database:', err); + }); // force: true will drop the table if it already exists -const drop_tables = true; -db.sequelize.sync({force: drop_tables}).then(() => { - console.log(`Drop and Resync with { force: ${drop_tables} }`); - }); - +const drop_tables = false; +db.sequelize.sync({ force: drop_tables }).then(() => { + console.log(`Drop and Resync with { force: ${drop_tables} }`); +}); //define a route, usually this would be a bunch of routes imported from another file -app.get('/', function (req, res, next) { - res.send('Welcome to the Ucharify API'); +app.get('/', function(req, res, next) { + res.send('Welcome to the Ucharify API'); }); //adding routes to Express app app.use('/api/donors', require('./app/routes/donors.route.js')); +app.use('/api/auth', require('./app/routes/auth.route.js')); // Create a Server -var server = app.listen(port, function () { - - var host = server.address().address; - var port = server.address().port; - - //server is successful - console.log(`App listening at port: ${port}`) -}) \ No newline at end of file +var server = app.listen(port, function() { + var host = server.address().address; + var port = server.address().port; + + //server is successful + console.log(`App listening at port: ${port}`); +});