From 661c73a60ac1ec2e30c2950a82f6efbdb47ec083 Mon Sep 17 00:00:00 2001 From: Samuel Owadayo Date: Mon, 3 Jul 2023 21:07:59 +0100 Subject: [PATCH] ready to use --- package.json | 4 +- src/app.js | 6 +- src/config/config.js | 2 + src/config/constant.js | 2 + src/config/database.js | 3 + src/config/logger.js | 2 + src/config/passport.js | 5 +- src/config/tokens.js | 1 + .../{AuthController.js => UserController.js} | 4 +- .../20230602054765_create_user_seeder.js | 2 +- src/routes/index.js | 2 +- src/routes/{authRoute.js => userRoute.js} | 2 +- src/server.js | 5 +- src/utilities/mediaUpload.js | 24 ++++ src/utilities/sanitize.js | 8 ++ tests/Authentication/AuthService.spec.js | 103 ++++++++++++++++++ 16 files changed, 161 insertions(+), 14 deletions(-) rename src/controllers/{AuthController.js => UserController.js} (98%) rename src/routes/{authRoute.js => userRoute.js} (92%) create mode 100644 src/utilities/mediaUpload.js create mode 100644 src/utilities/sanitize.js create mode 100644 tests/Authentication/AuthService.spec.js diff --git a/package.json b/package.json index 3556fad..26d77e6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "cross-env NODE_ENV=development nodemon src/server.js", "start": "cross-env NODE_ENV=production node src/server.js", - "test": "mocha './test/**/*.test.js' --watch" + "test": "mocha './tests/**/*.specs.js' --watch" }, "keywords": [ "nodejs", @@ -30,12 +30,14 @@ "mailgun.js": "^9.1.1", "md5": "^2.3.0", "moment": "^2.29.2", + "multer": "^1.4.5-lts.1", "mysql2": "^2.3.3", "node-cron": "^3.0.2", "nodemon": "^2.0.13", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "redis": "^4.6.7", + "sanitize-html": "^2.11.0", "sequelize": "^6.32.1", "sequelize-cli": "^6.2.0", "socket.io": "^4.4.1", diff --git a/src/app.js b/src/app.js index e40912c..ca93f14 100644 --- a/src/app.js +++ b/src/app.js @@ -25,7 +25,11 @@ app.use(passport.initialize()); passport.use('jwt', jwtStrategy); app.get('/', async (req, res) => { - res.status(200).send('Congratulations! You\'re Live!'); + const data = { + message: 'Congratulations! You\'re Live!', + uptime: 'Server Running since '+ Date() + } + res.status(200).send(data); }); app.use('/api', routes); diff --git a/src/config/config.js b/src/config/config.js index 81c2688..9330365 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -2,8 +2,10 @@ const dotenv = require('dotenv'); const path = require('path'); const Joi = require('joi'); +//link dotenv dotenv.config({ path: path.join(__dirname, '../../.env') }); +//env validation sequence const envValidation = Joi.object() .keys({ NODE_ENV: Joi.string().valid('development', 'production', 'test').required(), diff --git a/src/config/constant.js b/src/config/constant.js index c55c7fa..498edab 100644 --- a/src/config/constant.js +++ b/src/config/constant.js @@ -1,3 +1,4 @@ +//system constant for user const userConstant = { EMAIL_VERIFIED_TRUE: 1, EMAIL_VERIFIED_FALSE: 0, @@ -5,6 +6,7 @@ const userConstant = { STATUS_INACTIVE: 0, STATUS_REMOVED: 2, }; +//verification code constant const verificationCodeConstant = { TYPE_EMAIL_VERIFICATION: 1, TYPE_RESET_PASSWORD: 2, diff --git a/src/config/database.js b/src/config/database.js index 7a095d9..f165b19 100644 --- a/src/config/database.js +++ b/src/config/database.js @@ -1,6 +1,7 @@ const config = require('./config'); module.exports = { + //dev credentials development: { username: config.dbUser, password: config.dbPass, @@ -11,6 +12,7 @@ module.exports = { bigNumberStrings: true, }, }, + //staging credentials test: { username: config.dbUser, password: config.dbPass, @@ -21,6 +23,7 @@ module.exports = { bigNumberStrings: true, }, }, + //live credentials production: { username: config.dbUser, password: config.dbPass, diff --git a/src/config/logger.js b/src/config/logger.js index d40024f..589d63b 100644 --- a/src/config/logger.js +++ b/src/config/logger.js @@ -2,6 +2,7 @@ const winston = require('winston'); const DailyRotateFile = require('winston-daily-rotate-file'); const config = require('./config'); + const enumerateErrorFormat = winston.format((info) => { if (info.message instanceof Error) { info.message = { @@ -29,6 +30,7 @@ transport.on('rotate', (oldFilename, newFilename) => { // call function like upload to s3 or on cloud }); +//system logger const logger = winston.createLogger({ format: winston.format.combine(enumerateErrorFormat(), winston.format.json()), transports: [ diff --git a/src/config/passport.js b/src/config/passport.js index a4cda37..2c46bfc 100644 --- a/src/config/passport.js +++ b/src/config/passport.js @@ -13,6 +13,7 @@ const jwtOptions = { passReqToCallback: true, }; +//authentication sequence const jwtVerify = async (req, payload, done) => { try { if (payload.type !== tokenTypes.ACCESS) { @@ -36,7 +37,6 @@ const jwtVerify = async (req, payload, done) => { blacklisted: false, }); } - if (!tokenDoc) { return done(null, false); } @@ -44,17 +44,14 @@ const jwtVerify = async (req, payload, done) => { if (user) { user = new User(user); } - if (!user) { console.log('User Cache Missed!'); user = await userDaom.findOneByWhere({ uuid: payload.sub }); redisService.setUser(user); } - if (!user) { return done(null, false); } - done(null, user); } catch (error) { console.log(error); diff --git a/src/config/tokens.js b/src/config/tokens.js index 349337a..a7d886f 100644 --- a/src/config/tokens.js +++ b/src/config/tokens.js @@ -1,3 +1,4 @@ +//token type const tokenTypes = { ACCESS: 'access', REFRESH: 'refresh', diff --git a/src/controllers/AuthController.js b/src/controllers/UserController.js similarity index 98% rename from src/controllers/AuthController.js rename to src/controllers/UserController.js index d4f8512..53ce611 100644 --- a/src/controllers/AuthController.js +++ b/src/controllers/UserController.js @@ -5,7 +5,7 @@ const UserService = require('../services/UserService'); const logger = require('../config/logger'); const { tokenTypes } = require('../config/tokens'); -class AuthController { +class UserController { constructor() { this.userService = new UserService(); this.tokenService = new TokenService(); @@ -96,4 +96,4 @@ class AuthController { }; } -module.exports = AuthController; +module.exports = UserController; diff --git a/src/database/seeders/20230602054765_create_user_seeder.js b/src/database/seeders/20230602054765_create_user_seeder.js index fa3f38e..1f5d5f0 100644 --- a/src/database/seeders/20230602054765_create_user_seeder.js +++ b/src/database/seeders/20230602054765_create_user_seeder.js @@ -9,7 +9,7 @@ module.exports = { email: 'sowadayo@example.com', status: 1, email_verified: 1, - password: bcrypt.hashSync('123456', 8), + password: bcrypt.hashSync('secureNode123', 8), created_at: new Date(), updated_at: new Date(), }, diff --git a/src/routes/index.js b/src/routes/index.js index 299135a..d688353 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,5 +1,5 @@ const express = require('express'); -const authRoute = require('./authRoute'); +const authRoute = require('./userRoute'); const router = express.Router(); diff --git a/src/routes/authRoute.js b/src/routes/userRoute.js similarity index 92% rename from src/routes/authRoute.js rename to src/routes/userRoute.js index 2ee6ec1..ebb6173 100644 --- a/src/routes/authRoute.js +++ b/src/routes/userRoute.js @@ -1,5 +1,5 @@ const express = require('express'); -const AuthController = require('../controllers/AuthController'); +const AuthController = require('../controllers/UserController'); const UserValidator = require('../validators/UserValidator'); const router = express.Router(); diff --git a/src/server.js b/src/server.js index a541c03..15b874f 100644 --- a/src/server.js +++ b/src/server.js @@ -1,7 +1,7 @@ const app = require('./app'); const config = require('./config/config'); -console.log('Node Js Starter with Sequelize!!'); +console.log('Node Js Starter with Sequelize ORM!!'); require('./cronJobs'); // eslint-disable-next-line import/order const http = require('http'); @@ -14,6 +14,5 @@ global.io = io; require('./config/rootSocket')(io); server.listen(config.port, () => { - console.log('SERVER'); - console.log(`Listening to port ${config.port}`); + console.log(`Server Listening on port ${config.port}`); }); diff --git a/src/utilities/mediaUpload.js b/src/utilities/mediaUpload.js new file mode 100644 index 0000000..e96ed31 --- /dev/null +++ b/src/utilities/mediaUpload.js @@ -0,0 +1,24 @@ +const multer = require('multer'); +const fs = require('fs'); +const storage = multer.diskStorage({ + destination: function (req, file, callback) { + const destPath = req.uploadPath; + if (!fs.existsSync(destPath)) + fs.mkdirSync(destPath); + callback(null, destPath); + }, + filename: function (req, file, callback) { + const parts = file.originalname.split("."); + const extension = parts[parts.length - 1]; + let fileName = file.fieldname + '-' + Date.now(); + if (extension === 'png' || extension === 'jpeg' || extension === 'jpg') + fileName += '.' + extension; + + callback(null, fileName); + } +}); + + +const upload = multer({storage: storage}); + +module.exports = {upload}; diff --git a/src/utilities/sanitize.js b/src/utilities/sanitize.js new file mode 100644 index 0000000..24c04a4 --- /dev/null +++ b/src/utilities/sanitize.js @@ -0,0 +1,8 @@ +const sanitizeHtml = require('sanitize-html'); +exports.sanitizeInput = (dirty, options) => { + const htmlSanitizeOptions = { + allowedTags: [], allowedAttributes: [] + }; + + return sanitizeHtml(dirty, options || htmlSanitizeOptions); +}; \ No newline at end of file diff --git a/tests/Authentication/AuthService.spec.js b/tests/Authentication/AuthService.spec.js new file mode 100644 index 0000000..e2065c3 --- /dev/null +++ b/tests/Authentication/AuthService.spec.js @@ -0,0 +1,103 @@ +const chai = require('chai'); + +const { expect } = chai; +const sinon = require('sinon'); +const httpStatus = require('http-status'); +const AuthService = require('../../src/services/AuthService'); +const UserDaom = require('../../src/daom/UserDaom'); +const models = require('../../src/models'); + +const User = models.user; +const bcrypt = require('bcryptjs'); + +let authService; +const loginData = { + email: 'example@mail.com', + password: '123123Asd', +}; +const userData = { + first_name: 'Samuel', + last_name: 'Owadayo', + email: 'example@mail.com', + uuid: '4d85f12b-6e5b-468b-a971-eabe8acc9d08', +}; +describe('User Login test', () => { + beforeEach(() => { + authService = new AuthService(); + }); + afterEach(() => { + sinon.restore(); + }); + + it('User Login successfully', async () => { + const expectedResponse = { + statusCode: httpStatus.OK, + response: { + status: true, + code: httpStatus.OK, + message: 'Login Successful', + data: { + id: 1, + first_name: 'Samuel', + last_name: 'Owadayo', + email: 'example@mail.com', + email_verified: 1, + uuid: '4d85f12b-6e5b-468b-a971-eabe8acc9d08', + }, + }, + }; + userData.id = 1; + userData.password = bcrypt.hashSync(loginData.password, 8); + userData.email_verified = 1; + const userModel = new User(userData); + + sinon.stub(UserDaom.prototype, 'findByEmail').callsFake((email) => { + return userModel; + }); + const userLogin = await authService.loginWithEmailPassword( + loginData.email, + loginData.password, + ); + expect(userLogin).to.deep.include(expectedResponse); + }); + + it('should show INVALID EMAIL ADDRESS message', async () => { + const expectedResponse = { + statusCode: httpStatus.BAD_REQUEST, + response: { + status: false, + code: httpStatus.BAD_REQUEST, + message: 'Invalid Email Address!', + }, + }; + + sinon.stub(UserDaom.prototype, 'findByEmail').callsFake(() => { + return null; + }); + const response = await authService.loginWithEmailPassword('test@mail.com', '23232132'); + expect(response).to.deep.include(expectedResponse); + }); + + it('Wrong Password', async () => { + const expectedResponse = { + statusCode: httpStatus.BAD_REQUEST, + response: { + status: false, + code: httpStatus.BAD_REQUEST, + message: 'Wrong Password!', + }, + }; + userData.id = 1; + userData.password = bcrypt.hashSync('2322342343', 8); + userData.email_verified = 1; + const userModel = new User(userData); + sinon.stub(UserDaom.prototype, 'findByEmail').callsFake((email) => { + return userModel; + }); + const userLogin = await authService.loginWithEmailPassword( + loginData.email, + loginData.password, + ); + expect(userLogin).to.deep.include(expectedResponse); + }); +});