From 472503972aaa558a0062684ddf01c82f0674f340 Mon Sep 17 00:00:00 2001 From: Alain Date: Tue, 8 Oct 2019 15:01:18 +0200 Subject: [PATCH] Feature(Login): user login - creates endpoint for user to login - gives user a token upon signin [Finishes#168781679] --- .eslintignore | 2 + package-lock.json | 1 - package.json | 2 + src/controllers/userController.js | 26 ++++++++++ .../20191008073714-addPasswordColumn.js | 13 +++++ .../20191008112757-addVerifiedColumn.js | 13 +++++ src/database/models/users.js | 4 +- .../20191002153158-usersTableSeeder.js | 2 + .../seeders/20191008111708-defaultUser.js | 19 +++++++ src/helpers/responseHelper.js | 1 + src/helpers/responseMessages.js | 13 +++++ src/index.js | 2 + src/routes/api/index.js | 2 + src/routes/api/userRoutes.js | 10 ++++ src/tests/index.js | 8 +++ src/tests/userTest.js | 52 +++++++++++++++++++ 16 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 src/controllers/userController.js create mode 100644 src/database/migrations/20191008073714-addPasswordColumn.js create mode 100644 src/database/migrations/20191008112757-addVerifiedColumn.js create mode 100644 src/database/seeders/20191008111708-defaultUser.js create mode 100644 src/helpers/responseHelper.js create mode 100644 src/helpers/responseMessages.js create mode 100644 src/routes/api/userRoutes.js create mode 100644 src/tests/index.js create mode 100644 src/tests/userTest.js diff --git a/.eslintignore b/.eslintignore index d8200dfb..62a2fc18 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,3 +11,5 @@ package-lock.json # Seeders src/database/seeders +# Migrations +/src/database/migrations diff --git a/package-lock.json b/package-lock.json index 4d30734b..52f2f87f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1114,7 +1114,6 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.2" } diff --git a/package.json b/package.json index d8bfd9a9..23a4f4ec 100755 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/preset-env": "^7.4.5", "@babel/register": "^7.4.4", + "@babel/runtime": "^7.6.2", "babel-watch": "^7.0.0", "chai": "^4.2.0", "chai-http": "^4.3.0", @@ -61,6 +62,7 @@ "path": "^0.12.7", "pg": "^7.12.1", "pg-hstore": "^2.3.3", + "regenerator-runtime": "^0.13.3", "request": "^2.87.0", "sequelize": "^5.19.1", "swagger-jsdoc": "^3.4.0", diff --git a/src/controllers/userController.js b/src/controllers/userController.js new file mode 100644 index 00000000..51c5a543 --- /dev/null +++ b/src/controllers/userController.js @@ -0,0 +1,26 @@ +import jwt from 'jsonwebtoken'; +import models from '../database/models'; +import responseHelper from '../helpers/responseHelper'; +import messages from '../helpers/responseMessages'; + +class UserController { + static async signIn(req, res) { + const { email, password } = req.body; + const user = await models.users.findOne({ where: { email, password } }); + if (!user) { + return responseHelper(res, 400, messages.user.error.LOGIN_FAILURE); + } + if (user.verified === false) { + return responseHelper(res, 400, messages.user.error.VERIFY_FIRST); + } + const userToken = jwt.sign({ email }, process.env.SECRET_KEY, { expiresIn: '1h' }); + const userInfo = { + userID: user.id, + username: user.username, + email: user.email, + token: userToken + }; + return responseHelper(res, 200, messages.user.success.SUCCESSFUL_LOGIN, userInfo); + } +} +export default UserController; diff --git a/src/database/migrations/20191008073714-addPasswordColumn.js b/src/database/migrations/20191008073714-addPasswordColumn.js new file mode 100644 index 00000000..f1d40df9 --- /dev/null +++ b/src/database/migrations/20191008073714-addPasswordColumn.js @@ -0,0 +1,13 @@ + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.addColumn( + 'users', + 'password', + Sequelize.STRING + ), + + down: (queryInterface, Sequelize) => queryInterface.removeColumn( + 'users', + 'password' + ) +}; diff --git a/src/database/migrations/20191008112757-addVerifiedColumn.js b/src/database/migrations/20191008112757-addVerifiedColumn.js new file mode 100644 index 00000000..68423421 --- /dev/null +++ b/src/database/migrations/20191008112757-addVerifiedColumn.js @@ -0,0 +1,13 @@ + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.addColumn( + 'users', + 'verified', + Sequelize.BOOLEAN + ), + + down: (queryInterface, Sequelize) => queryInterface.removeColumn( + 'users', + 'verified' + ) +}; diff --git a/src/database/models/users.js b/src/database/models/users.js index 8e707080..ddceb555 100644 --- a/src/database/models/users.js +++ b/src/database/models/users.js @@ -1,7 +1,9 @@ module.exports = (sequelize, Datatypes) => { const Users = sequelize.define('users', { username: Datatypes.STRING, - email: Datatypes.STRING + email: Datatypes.STRING, + password: Datatypes.STRING, + verified: Datatypes.BOOLEAN }, { tableName: 'users' }); diff --git a/src/database/seeders/20191002153158-usersTableSeeder.js b/src/database/seeders/20191002153158-usersTableSeeder.js index 1874d6a6..4d1bab9a 100644 --- a/src/database/seeders/20191002153158-usersTableSeeder.js +++ b/src/database/seeders/20191002153158-usersTableSeeder.js @@ -5,6 +5,8 @@ module.exports = { queryInterface.bulkInsert('users', [{ username: 'johndoe', email: 'johndoe@test.com', + password: 'default', + verified: false, createdAt: new Date(), updatedAt: new Date() }]) diff --git a/src/database/seeders/20191008111708-defaultUser.js b/src/database/seeders/20191008111708-defaultUser.js new file mode 100644 index 00000000..9a6214c7 --- /dev/null +++ b/src/database/seeders/20191008111708-defaultUser.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.bulkInsert('users', [{ + username: 'caretUser', + email: 'user@caretbn.com', + password: 'default', + verified: true, + createdAt: new Date(), + updatedAt: new Date() + }], {}); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.bulkDelete('users', null, {}); + + } +}; diff --git a/src/helpers/responseHelper.js b/src/helpers/responseHelper.js new file mode 100644 index 00000000..376b4252 --- /dev/null +++ b/src/helpers/responseHelper.js @@ -0,0 +1 @@ +export default (res, status, message, data) => res.status(status).json({ status, message, data }); diff --git a/src/helpers/responseMessages.js b/src/helpers/responseMessages.js new file mode 100644 index 00000000..c0e3bad8 --- /dev/null +++ b/src/helpers/responseMessages.js @@ -0,0 +1,13 @@ +const messages = { + user: { + success: { + SUCCESSFUL_LOGIN: 'User logged in successfully!' + }, + error: { + LOGIN_FAILURE: 'Incorrect email or password!', + VERIFY_FIRST: 'Please verify your email first!', + } + } +}; + +export default messages; diff --git a/src/index.js b/src/index.js index 7f71a3f4..cea15e47 100755 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +// eslint-disable-next-line no-unused-vars +import regeneratorRuntime from 'regenerator-runtime'; import dotenv from 'dotenv'; import express from 'express'; import morgan from 'morgan'; diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 464dc297..5b4916a1 100755 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -1,11 +1,13 @@ import Router from 'express'; import usersRoutes from './users'; import swaggerRoute from '../swagger-doc'; +import user from './userRoutes'; const router = new Router(); router.use('/users', usersRoutes); router.use('/api-docs', swaggerRoute); +router.use('/', user); router.use((err, req, res, next) => { if (err.name === 'ValidationError') { diff --git a/src/routes/api/userRoutes.js b/src/routes/api/userRoutes.js new file mode 100644 index 00000000..4125a05e --- /dev/null +++ b/src/routes/api/userRoutes.js @@ -0,0 +1,10 @@ +import express from 'express'; +import UserController from '../../controllers/userController'; + +const { signIn } = UserController; + +const router = express.Router(); + +router.post('/auth/login', signIn); + +export default router; diff --git a/src/tests/index.js b/src/tests/index.js new file mode 100644 index 00000000..c8bad8bd --- /dev/null +++ b/src/tests/index.js @@ -0,0 +1,8 @@ +import { describe } from 'mocha'; +import test from './test'; +import userTest from './userTest'; + +describe('Testing Barefoot Nomad...', () => { + describe('Initial Tests', test); + describe('User Tests', userTest); +}); diff --git a/src/tests/userTest.js b/src/tests/userTest.js new file mode 100644 index 00000000..30bd54b3 --- /dev/null +++ b/src/tests/userTest.js @@ -0,0 +1,52 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { describe, it } from 'mocha'; +import app from '../index'; +import messages from '../helpers/responseMessages'; + + +chai.should(); +chai.use(chaiHttp); + +const verifiedUser = { email: 'user@caretbn.com', password: 'default' }; +const unVerifiedUser = { email: 'johndoe@test.com', password: 'default' }; +const invalidData = { email: 'email@email.com', password: 'password' }; + +describe('User Login Test', () => { + it('it should should return 200 and log in a user successfully ', done => { + chai.request(app) + .post('/api/v1/auth/login') + .send(verifiedUser) + .end((err, res) => { + res.should.have.status(200); + res.body.should.be.a('object'); + res.body.should.have.property('status').eql(200); + res.body.should.have.property('message').eql(`${messages.user.success.SUCCESSFUL_LOGIN}`); + done(); + }); + }); + it('it should should return 400 and tell the user that email or password is incorrect ', done => { + chai.request(app) + .post('/api/v1/auth/login') + .send(invalidData) + .end((err, res) => { + res.should.have.status(400); + res.body.should.be.a('object'); + res.body.should.have.property('status').eql(400); + res.body.should.have.property('message').eql(`${messages.user.error.LOGIN_FAILURE}`); + done(); + }); + }); + it('it should should return 400 and tell the user to verify first ', done => { + chai.request(app) + .post('/api/v1/auth/login') + .send(unVerifiedUser) + .end((err, res) => { + res.should.have.status(400); + res.body.should.be.a('object'); + res.body.should.have.property('status').eql(400); + res.body.should.have.property('message').eql(`${messages.user.error.VERIFY_FIRST}`); + done(); + }); + }); +});