From 4b5a5b2477ba811a817b700cce97c1314671c8be Mon Sep 17 00:00:00 2001 From: rui hildt Date: Fri, 8 May 2020 13:12:10 +0200 Subject: [PATCH] Implement auth with jwt and add endpoints --- api/models/accountModel.js | 16 +++++++ api/routes/accountRoute.js | 19 +++++++- api/routes/authRoute.js | 48 +++++++++++++++++++ api/server.js | 2 + config/config.js | 3 +- helpers/authJwt.js | 36 ++++++++++++++ package-lock.json | 96 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 8 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 api/routes/authRoute.js create mode 100644 helpers/authJwt.js diff --git a/api/models/accountModel.js b/api/models/accountModel.js index e05afef..25dab5d 100644 --- a/api/models/accountModel.js +++ b/api/models/accountModel.js @@ -6,6 +6,7 @@ module.exports = { updateAccount, deleteAccount, getMeetingsByAccountId, + getAccountByEmail, }; function addAccount(data) { @@ -67,3 +68,18 @@ function getAccountById(id) { 'latest_time', ); } + +function getAccountByEmail(email) { + return db('account') + .where({ email }) + .first() + .select( + 'id', + 'username', + 'password', + 'email', + 'timezone', + 'earliest_time', + 'latest_time', + ); +} diff --git a/api/routes/accountRoute.js b/api/routes/accountRoute.js index 63afaef..494c5ff 100644 --- a/api/routes/accountRoute.js +++ b/api/routes/accountRoute.js @@ -3,10 +3,11 @@ const router = express.Router(); const bcrypt = require('bcryptjs'); const Account = require('../models/accountModel'); +const { saltingRounds } = require('../../config/config'); router.post('/', async (req, res) => { const data = { ...req.body }; - const hash = bcrypt.hashSync(data.password, 14); + const hash = bcrypt.hashSync(data.password, saltingRounds); data.password = hash; try { @@ -22,7 +23,7 @@ router.put('/:id', async (req, res) => { const id = req.params.id; if (data.password) { - const hash = bcrypt.hashSync(data.password, 14); + const hash = bcrypt.hashSync(data.password, 10); data.password = hash; } @@ -78,4 +79,18 @@ router.get('/:id', async (req, res) => { } }); +router.get('/:email', async (req, res) => { + const email = req.params.email; + + try { + const account = await Account.getAccountByEmail(email); + res.status(200).json(account); + } catch (error) { + res.status(500).json({ + message: `Account with ${email} doesn't exist.`, + error, + }); + } +}); + module.exports = router; diff --git a/api/routes/authRoute.js b/api/routes/authRoute.js new file mode 100644 index 0000000..48005bc --- /dev/null +++ b/api/routes/authRoute.js @@ -0,0 +1,48 @@ +const express = require('express'); +const router = express.Router(); +const bcrypt = require('bcryptjs'); + +const Account = require('../models/accountModel'); +const { saltingRounds } = require('../../config/config'); +const { generateToken } = require('../../helpers/authJwt'); + +router.post('/register', async (req, res) => { + const data = req.body; + data.password = bcrypt.hashSync(data.password, saltingRounds); + + try { + const [user] = await Account.addAccount(data); + const token = generateToken(user); + res.status(201).json({ user, token }); + } catch (error) { + res.status(500).json({ + message: `Failed to add new account.`, + error, + }); + } +}); + +router.post('/login', async (req, res) => { + let { email, password } = req.body; + + try { + const user = await Account.getAccountByEmail(email); + + if (bcrypt.compareSync(password, user.password)) { + const token = generateToken(user); + + res.status(200).json({ + message: `Welcome ${user.username}!`, + token, + }); + } else { + throw new Error(); + } + } catch (error) { + res.status(401).json({ + message: `Invalid Credentials`, + }); + } +}); + +module.exports = router; diff --git a/api/server.js b/api/server.js index 6db6818..9be3673 100644 --- a/api/server.js +++ b/api/server.js @@ -9,6 +9,7 @@ const meetingsRoute = require('./routes/meetingRoute'); const participantsRoute = require('./routes/participantRoute'); const possibleDatesRoute = require('./routes/possibleDateRoute'); const availibilityRoute = require('./routes/availibilityRoute'); +const authRoute = require('./routes/authRoute'); const server = express(); @@ -28,6 +29,7 @@ server.use('/api/meetings', meetingsRoute); server.use('/api/participants', participantsRoute); server.use('/api/possible-dates', possibleDatesRoute); server.use('/api/availibility', availibilityRoute); +server.use('/api/auth', authRoute); server.get('/', (req, res) => res diff --git a/config/config.js b/config/config.js index 92a4ae8..5219fe2 100644 --- a/config/config.js +++ b/config/config.js @@ -2,5 +2,6 @@ module.exports = { jwt_secret: process.env.JWT_SECRET, port: process.env.PORT || 3001, db_url: process.env.DATABASE_URL, - environment: process.env.NODE_ENV, + environment: process.env.NODE_ENV || 'development', + saltingRounds: process.env.SALTING_ROUNDS || 10, }; diff --git a/helpers/authJwt.js b/helpers/authJwt.js new file mode 100644 index 0000000..a6c12dd --- /dev/null +++ b/helpers/authJwt.js @@ -0,0 +1,36 @@ +const jwt = require('jsonwebtoken'); +const { jwt_secret } = require('../config/config'); + +module.exports = { + authenticate, + generateToken, +}; + +function generateToken(user) { + const payload = { + username: user.username, + email: user.email + }; + + const options = { + expiresIn: '30d', + }; + + return jwt.sign(payload, jwt_secret, options); +} + +function authenticate(req, res, next) { + const token = req.get('Authorization'); + + if (token) { + jwt.verify(token, jwt_secret, (err, decoded) => { + if (err) return res.status(401).json(err); + req.decoded = decoded; + next(); + }); + } else { + return res.status(401).json({ + error: 'No token provided, must be set on the Authorization Header', + }); + } +} diff --git a/package-lock.json b/package-lock.json index 7f931e6..a95ef08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,6 +172,11 @@ } } }, + "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=" + }, "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -400,6 +405,14 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "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", @@ -1090,6 +1103,54 @@ "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "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", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -1192,6 +1253,41 @@ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" }, + "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=" + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", diff --git a/package.json b/package.json index f532f05..5d2c73d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dotenv": "^8.2.0", "express": "^4.17.1", "helmet": "^3.22.0", + "jsonwebtoken": "^8.5.1", "knex": "^0.21.1", "pg": "^8.0.3", "pgtools": "^0.3.0",