From 4ab608178cb00982ac28354fb27685af00a11570 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Fri, 21 Jan 2022 22:05:26 +0700 Subject: [PATCH 01/12] redefine Models --- src/models/productModel.js | 12 ++++-------- src/models/userModel.js | 6 ------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/models/productModel.js b/src/models/productModel.js index 4b9f054..f8ee4e7 100644 --- a/src/models/productModel.js +++ b/src/models/productModel.js @@ -31,6 +31,10 @@ const productSchema = mongoose.Schema({ type: Date, default: Date.now, }, + countInStocks: { + type: Number, + required: true, + }, userID: { type: mongoose.Schema.Types.ObjectId, ref: 'User', @@ -38,12 +42,4 @@ const productSchema = mongoose.Schema({ }, }); -productSchema.virtual('id').get(function () { - return this._id.toHexString(); -}); - -productSchema.set('toJSON', { - virtuals: true, -}); - exports.Product = mongoose.model('Product', productSchema); diff --git a/src/models/userModel.js b/src/models/userModel.js index 15595a1..569eebe 100644 --- a/src/models/userModel.js +++ b/src/models/userModel.js @@ -121,12 +121,6 @@ userSchema.pre('save', function (next) { next(); }); -userSchema.pre(/^find/, function (next) { - // this points to the current query - this.find({ active: { $ne: false } }); - next(); -}); - userSchema.methods.correctPassword = async function (candidatePassword, userPassword) { return await bcrypt.compare(candidatePassword, userPassword); }; From 146e438726538e51459c4bb9cba3bf1d945aa399 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Fri, 21 Jan 2022 22:05:55 +0700 Subject: [PATCH 02/12] Authentication API --- src/api/auth/auth.controller.js | 143 ++++++++++------------ src/api/auth/auth.service.js | 204 ++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 81 deletions(-) diff --git a/src/api/auth/auth.controller.js b/src/api/auth/auth.controller.js index d1c21ff..9c1dd4b 100644 --- a/src/api/auth/auth.controller.js +++ b/src/api/auth/auth.controller.js @@ -1,82 +1,63 @@ -const { promisify } = require('util'); -const jwt = require('jsonwebtoken'); -const User = require('../models/userModel'); -const catchAsync = require('../utils/catchAsync'); -const AppError = require('../utils/appError'); - -const signToken = (id) => { - return jwt.sign({ id }, process.env.JWT_SECRET, { - expiresIn: process.env.JWT_EXPIRES_IN, - }); +const authService = require('./auth.service'); +const AppError = require('./../utils/appError'); + +module.exports = { + signup: async (req, res, next) => { + try { + let DTO = await authService.signup(req.body); + res.status(201).json(DTO); + } catch (error) { + return next(error); + } + }, + login: async (req, res, next) => { + try { + let DTO = await authService.login(req.body); + res.status(200).json(DTO); + } catch (error) { + return next(error); + } + }, + protect: async (req, res, next) => { + try { + await authService.protect(req); + next(); + } catch (error) { + return next(error); + } + }, + restrictTo: (...roles) => { + return (req, res, next) => { + try { + authService.restrictTo(req.user.role, roles); + next(); + } catch (error) { + return next(error); + } + }; + }, + forgotPassword: async (req, res, next) => { + try { + let DTO = await authService.forgotPassword(req); + res.status(200).json(DTO); + } catch (error) { + return next(error); + } + }, + resetPassword: async (req, res, next) => { + try { + let DTO = await authService.resetPassword(req.body, req.params.token); + res.status(200).json(DTO); + } catch (error) { + return next(error); + } + }, + updatePassword: async (req, res, next) => { + try { + let DTO = await authService.updatePassword(req.user.id, req.body); + res.status(200).json(DTO); + } catch (error) { + return next(error); + } + }, }; - -exports.signup = catchAsync(async (req, res, next) => { - const newUser = await User.create({ - name: req.body.name, - email: req.body.email, - password: req.body.password, - passwordConfirm: req.body.passwordConfirm, - }); - - const token = signToken(newUser.id); - - res.status(201).json({ - status: 'success', - token, - data: { - user: newUser, - }, - }); -}); - -exports.login = async (req, res, next) => { - const { email, password } = req.body; - - // 1) Check if email and password exist - if (!email || !password) { - next(new AppError('Please provide both an email and password!', 400)); - } - // 2) Check if user exists and password are correct - const user = await User.findOne({ email }).select('+password'); - - if (!user || !(await user.correctPassword(password, user.password))) { - return next(new AppError('Incorrect email or password!', 401)); - } - - // 3) If everything ok, send token to client - const token = signToken(user._id); - res.status(200).json({ - status: 'success', - token, - }); -}; - -exports.protect = catchAsync(async (req, res, next) => { - let token; - // 1) Getting token and check of it's there - if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { - token = req.headers.authorization.split(' ')[1]; - } - - if (!token) { - return next(new AppError('You are not logged in! Please log in to get access', 401)); - } - - // 2) Verification token - const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET); - - // 3) Check if user still exists - const currentUser = await User.findById(decoded.id); - if (!currentUser) { - return next(new AppError('The token belonging to this token does no longer exist!', 401)); - } - - // 4) Check if user changed password after the token was issued - if (currentUser.changedPasswordAfter(decoded.iat)) { - return next(new AppError('User recently changed password! Please log in again.', 401)); - } - - // GRANT ACCESS TO PROTECTED ROUTE - req.user = currentUser; - next(); -}); diff --git a/src/api/auth/auth.service.js b/src/api/auth/auth.service.js index e69de29..687b54b 100644 --- a/src/api/auth/auth.service.js +++ b/src/api/auth/auth.service.js @@ -0,0 +1,204 @@ +const crypto = require('crypto'); +const { promisify } = require('util'); +const jwt = require('jsonwebtoken'); +const User = require('./../models/userModel'); +const catchAsync = require('./../utils/catchAsync'); +const AppError = require('./../utils/appError'); +const sendEmail = require('./../utils/email'); + +// const signToken = (id) => { +// return jwt.sign({ id }, process.env.JWT_SECRET, { +// expiresIn: process.env.JWT_EXPIRES_IN, +// }); +// }; + +// const createSendToken = (user, req, res) => { +// const token = signToken(user._id); + +// res.cookie('jwt', token, { +// expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000), +// httpOnly: true, +// secure: req.secure || req.headers['x-forwarded-proto'] === 'https', +// }); + +// // Remove password from output +// user.password = undefined; + +// return { +// msg: 'Success', +// }; +// }; + +exports.signup = async (body) => { + try { + const newUser = await User.create({ + name: body.name, + email: body.email, + password: body.password, + passwordConfirm: body.passwordConfirm, + }); + // createSendToken(newUser, req, res); + return { + status: 'success', + message: 'Your account has been created', + }; + } catch (err) { + throw new AppError(500, error.message); + } +}; + +exports.login = async (body) => { + try { + const { email, password } = body; + // 1) Check if email and password exist + if (!email || !password) { + throw new AppError('Please provide email and password!', 400); + } + // 2) Check if user exists && password is correct + const user = await User.findOne({ email }).select('+password'); + + if (!user || !(await user.correctPassword(password, user.password))) { + throw new AppError(401, 'Incorrect email or password'); + } + + return { + status: 'success', + message: 'Your account has logged in', + }; + + // 3) If everything ok, send token to client + // createSendToken(user, req, res); + } catch (err) { + throw new AppError(500, error.message); + } +}; + +exports.protect = async (req) => { + try { + // 1) Getting token and check of it's there + let token; + if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { + token = req.headers.authorization.split(' ')[1]; + } else if (req.cookies.jwt) { + token = req.cookies.jwt; + } + + if (!token) { + throw new AppError(401, 'You are not logged in! Please log in to get access.'); + } + + // 2) Verification token + const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET); + + // 3) Check if user still exists + const currentUser = await User.findById(decoded.id); + if (!currentUser) { + throw new AppError(401, 'The user belonging to this token does no longer exist.'); + } + + // 4) Check if user changed password after the token was issued + if (currentUser.changedPasswordAfter(decoded.iat)) { + throw new AppError(401, 'User recently changed password! Please log in again.'); + } + + // GRANT ACCESS TO PROTECTED ROUTE + req.user = currentUser; + } catch (err) { + throw new AppError(500, error.message); + } +}; + +exports.restrictTo = (userRole, roles) => { + // roles ['admin',...] not 'user' + if (!roles.includes(userRole)) { + throw new AppError(403, 'You do not have permission to perform this action'); + } +}; + +exports.forgotPassword = async (req) => { + // 1) Get user based on POST email + const user = await User.findOne({ email: req.body.email }); + if (!user) { + throw new AppError(404, 'There is no user with email address.'); + } + + // 2) Generate the random reset token + const resetToken = user.createPasswordResetToken(); + await user.save({ validateBeforeSave: false }); + + // 3) Send it to user's email + const resetURL = `${req.protocol}://${req.get('host')}/api/v1/users/resetPassword/${resetToken}`; + + const message = `Forgot your password? Submit a PATCH request with your new password and passwordConfirm to: ${resetURL}.\nIf you didn't forget your password, please ignore this email!`; + + try { + await sendEmail({ + email: user.email, + subject: 'Your password reset token (valid for 10 min)', + message, + }); + + return { + status: 'success', + message: 'Token sent to email!', + }; + } catch (err) { + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; + await user.save({ validateBeforeSave: false }); + + throw new AppError(500, 'There was an error sending the email. Try again later!'); + } +}; + +exports.resetPassword = async (body, token) => { + try { + // 1) Get user based on the token + const hashedToken = crypto.createHash('sha256').update(token).digest('hex'); + + const user = await User.findOne({ + passwordResetToken: hashedToken, + passwordResetExpires: { $gt: Date.now() }, + }); + + // 2) If token has not expired, and there is user, set the new password + if (!user) { + throw new AppError(400, 'Token is invalid or has expired'); + } + user.password = body.password; + user.passwordConfirm = body.passwordConfirm; + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; + await user.save(); + + return { status: 'success', message: 'Your password has been reset' }; + + // 3) Update changedPasswordAt property for the user + // 4) Log the user in, send JWT + // createSendToken(user, req, res); + } catch { + throw new AppError(500, error.message); + } +}; + +exports.updatePassword = async (userID, body) => { + try { + // 1) Get user from collection + const user = await User.findById(userID).select('+password'); + + // 2) Check if POSTed current password is correct + if (!(await user.correctPassword(body.passwordCurrent, user.password))) { + throw new AppError(401, 'Your current password is wrong.'); + } + + // 3) If so, update password + user.password = body.password; + user.passwordConfirm = body.passwordConfirm; + await user.save(); + + // 4) Log user in, send JWT + // createSendToken(user, req, res); + } catch (err) { + throw new AppError(500, error.message); + } +}; From 131b7cebf3e3392de3c848ad7c7fabc55647a105 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Fri, 21 Jan 2022 22:06:09 +0700 Subject: [PATCH 03/12] utils --- src/common/appError.js | 13 +++++++++++++ src/common/email.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/common/appError.js create mode 100644 src/common/email.js diff --git a/src/common/appError.js b/src/common/appError.js new file mode 100644 index 0000000..2e3efe5 --- /dev/null +++ b/src/common/appError.js @@ -0,0 +1,13 @@ +class AppError extends Error { + constructor(statusCode, message) { + super(message); + + this.statusCode = statusCode; + this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; + this.isOperational = true; + + Error.captureStackTrace(this, this.constructor); + } +} + +module.exports = AppError; diff --git a/src/common/email.js b/src/common/email.js new file mode 100644 index 0000000..4434ec9 --- /dev/null +++ b/src/common/email.js @@ -0,0 +1,28 @@ +const nodemailer = require('nodemailer'); + +const sendEmail = async (options) => { + // 1) Create a transporter + const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_HOST, + port: process.env.EMAIL_PORT, + auth: { + user: process.env.EMAIL_USERNAME, + pass: process.env.EMAIL_PASSWORD, + }, + // Activate in email "less secure app" option + }); + + // 2) Define the email options + const mailOptions = { + from: 'LongChau ', + to: options.email, + subject: options.subject, + text: options.message, + // html + }; + + // 3) Actually send the email + await transporter.sendMail(mailOptions); +}; + +module.exports = sendEmail; From 21c95b220be53546c44a4b48e0f0251cb4c74097 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Fri, 21 Jan 2022 22:11:17 +0700 Subject: [PATCH 04/12] fix bug --- src/api/auth/auth.controller.js | 1 - src/api/auth/auth.service.js | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/api/auth/auth.controller.js b/src/api/auth/auth.controller.js index 9c1dd4b..dd955c8 100644 --- a/src/api/auth/auth.controller.js +++ b/src/api/auth/auth.controller.js @@ -1,5 +1,4 @@ const authService = require('./auth.service'); -const AppError = require('./../utils/appError'); module.exports = { signup: async (req, res, next) => { diff --git a/src/api/auth/auth.service.js b/src/api/auth/auth.service.js index 687b54b..9f076f9 100644 --- a/src/api/auth/auth.service.js +++ b/src/api/auth/auth.service.js @@ -2,9 +2,8 @@ const crypto = require('crypto'); const { promisify } = require('util'); const jwt = require('jsonwebtoken'); const User = require('./../models/userModel'); -const catchAsync = require('./../utils/catchAsync'); -const AppError = require('./../utils/appError'); -const sendEmail = require('./../utils/email'); +const AppError = require('./../common/appError'); +const sendEmail = require('./../common/email'); // const signToken = (id) => { // return jwt.sign({ id }, process.env.JWT_SECRET, { From 12046d5f72f7ddadc6396b9475cb401a7ffa348e Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Fri, 21 Jan 2022 22:20:01 +0700 Subject: [PATCH 05/12] Routing --- server.js | 2 -- src/api/auth/index.js | 13 +++++++++++++ src/api/index.js | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index fcaf9d7..05358a1 100644 --- a/server.js +++ b/server.js @@ -16,5 +16,3 @@ app.use('/', (req, res) => { app.use((req, res) => { res.status(404).send('404'); }); - -app.use((error, req, res, next) => {}); diff --git a/src/api/auth/index.js b/src/api/auth/index.js index e69de29..b82c9fa 100644 --- a/src/api/auth/index.js +++ b/src/api/auth/index.js @@ -0,0 +1,13 @@ +// route endpoint /auth +const authController = require('./auth.controller'); +const router = require('express').Router(); + +router.post('/signup', authController.signup); +router.post('/login', authController.login); + +router.post('forgotPassword', authController.forgotPassword); +router.patch('/resetPassword', authController.resetPassword); + +router.patch('/updateMyPassword', authController.protect, authController.updatePassword); + +module.exports = router; diff --git a/src/api/index.js b/src/api/index.js index e69de29..bedc76b 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -0,0 +1,6 @@ +const auth = require('./auth'); +const router = require('express').Router(); + +router.use('/auth', auth); + +module.exports = router; From cfeba92eb74c7d0fb8797028eb4685728beb0ce2 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Fri, 21 Jan 2022 23:03:19 +0700 Subject: [PATCH 06/12] orderModel --- src/models/order.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/models/order.js diff --git a/src/models/order.js b/src/models/order.js new file mode 100644 index 0000000..04d3c8d --- /dev/null +++ b/src/models/order.js @@ -0,0 +1,25 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const orderSchema = new Schema({ + products: [ + { + product: { type: Object, required: true }, + quantity: { type: Number, required: true }, + }, + ], + user: { + name: { + type: String, + required: true, + }, + userId: { + type: mongoose.Schema.ObjectId, + ref: 'User', + required: true, + }, + }, +}); + +module.exports = mongoose.model('Order', orderSchema); From f714399ed6a54805441335197f198150f129a966 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Sat, 22 Jan 2022 08:17:22 +0700 Subject: [PATCH 07/12] verifyJWT --- src/common/protectRoute.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/common/protectRoute.js diff --git a/src/common/protectRoute.js b/src/common/protectRoute.js new file mode 100644 index 0000000..5081ada --- /dev/null +++ b/src/common/protectRoute.js @@ -0,0 +1,34 @@ +const jwt = require('jsonwebtoken'); +const AppError = require('./appError'); + +exports.protect = catchAsync(async (req, res, next) => { + // 1) Getting token and check of it's there + let token; + if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { + token = req.headers.authorization.split(' ')[1]; + } else if (req.cookies.jwt) { + token = req.cookies.jwt; + } + + if (!token) { + throw new AppError(401, 'You are not logged in! Please log in to get access.'); + } + + // 2) Verification token + const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET); + + // 3) Check if user still exists + const currentUser = await User.findById(decoded.id); + if (!currentUser) { + throw new AppError(401, 'The user belonging to this token does no longer exist.'); + } + + // 4) Check if user changed password after the token was issued + if (currentUser.changedPasswordAfter(decoded.iat)) { + throw new AppError(401, 'User recently changed password! Please log in again.'); + } + + // GRANT ACCESS TO PROTECTED ROUTE + req.user = currentUser; + next(); +}); From 6723a866367177b554ced9c630acb90b1594528d Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Sat, 22 Jan 2022 08:21:22 +0700 Subject: [PATCH 08/12] require some modules --- src/common/protectRoute.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/protectRoute.js b/src/common/protectRoute.js index 5081ada..1e0ac16 100644 --- a/src/common/protectRoute.js +++ b/src/common/protectRoute.js @@ -1,7 +1,7 @@ const jwt = require('jsonwebtoken'); const AppError = require('./appError'); -exports.protect = catchAsync(async (req, res, next) => { +exports.protect = async (req, res, next) => { // 1) Getting token and check of it's there let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { @@ -31,4 +31,4 @@ exports.protect = catchAsync(async (req, res, next) => { // GRANT ACCESS TO PROTECTED ROUTE req.user = currentUser; next(); -}); +}; From a43e38a98048b2a8bd8a42d0412397d23661f5ad Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Sat, 22 Jan 2022 08:21:38 +0700 Subject: [PATCH 09/12] require some modules --- src/common/protectRoute.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/protectRoute.js b/src/common/protectRoute.js index 1e0ac16..f7a5698 100644 --- a/src/common/protectRoute.js +++ b/src/common/protectRoute.js @@ -1,5 +1,7 @@ const jwt = require('jsonwebtoken'); const AppError = require('./appError'); +const { promisify } = require('util'); +const User = require('./../models/userModel'); exports.protect = async (req, res, next) => { // 1) Getting token and check of it's there From 2c2ec8bebddd71aa0cd2fef6cbf3c0ae8656c444 Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Sat, 22 Jan 2022 13:20:09 +0700 Subject: [PATCH 10/12] userPermission --- src/common/userPermission.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/common/userPermission.js diff --git a/src/common/userPermission.js b/src/common/userPermission.js new file mode 100644 index 0000000..93e8220 --- /dev/null +++ b/src/common/userPermission.js @@ -0,0 +1,8 @@ +exports.restrictTo = (...roles) => { + return (req, res, next) => { + if (!roles.includes(req.user.role)) { + return next(new AppError('You do not have permission to perform this action', 403)); + } + next(); + }; +}; From d6734168f0c25635600d6b7c466957abe33a23ff Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Sat, 22 Jan 2022 13:20:48 +0700 Subject: [PATCH 11/12] auth api --- src/api/auth/auth.controller.js | 22 ++++++---------------- src/api/auth/auth.service.js | 21 +++++++++++++-------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/api/auth/auth.controller.js b/src/api/auth/auth.controller.js index dd955c8..404312e 100644 --- a/src/api/auth/auth.controller.js +++ b/src/api/auth/auth.controller.js @@ -6,7 +6,7 @@ module.exports = { let DTO = await authService.signup(req.body); res.status(201).json(DTO); } catch (error) { - return next(error); + next(error); } }, login: async (req, res, next) => { @@ -14,7 +14,7 @@ module.exports = { let DTO = await authService.login(req.body); res.status(200).json(DTO); } catch (error) { - return next(error); + next(error); } }, protect: async (req, res, next) => { @@ -22,25 +22,15 @@ module.exports = { await authService.protect(req); next(); } catch (error) { - return next(error); + next(error); } }, - restrictTo: (...roles) => { - return (req, res, next) => { - try { - authService.restrictTo(req.user.role, roles); - next(); - } catch (error) { - return next(error); - } - }; - }, forgotPassword: async (req, res, next) => { try { let DTO = await authService.forgotPassword(req); res.status(200).json(DTO); } catch (error) { - return next(error); + next(error); } }, resetPassword: async (req, res, next) => { @@ -48,7 +38,7 @@ module.exports = { let DTO = await authService.resetPassword(req.body, req.params.token); res.status(200).json(DTO); } catch (error) { - return next(error); + next(error); } }, updatePassword: async (req, res, next) => { @@ -56,7 +46,7 @@ module.exports = { let DTO = await authService.updatePassword(req.user.id, req.body); res.status(200).json(DTO); } catch (error) { - return next(error); + next(error); } }, }; diff --git a/src/api/auth/auth.service.js b/src/api/auth/auth.service.js index 9f076f9..0f67ac0 100644 --- a/src/api/auth/auth.service.js +++ b/src/api/auth/auth.service.js @@ -5,11 +5,11 @@ const User = require('./../models/userModel'); const AppError = require('./../common/appError'); const sendEmail = require('./../common/email'); -// const signToken = (id) => { -// return jwt.sign({ id }, process.env.JWT_SECRET, { -// expiresIn: process.env.JWT_EXPIRES_IN, -// }); -// }; +const signToken = (id) => { + return jwt.sign({ id }, process.env.JWT_SECRET, { + expiresIn: process.env.JWT_EXPIRES_IN, + }); +}; // const createSendToken = (user, req, res) => { // const token = signToken(user._id); @@ -40,6 +40,7 @@ exports.signup = async (body) => { return { status: 'success', message: 'Your account has been created', + data: newUser, }; } catch (err) { throw new AppError(500, error.message); @@ -60,13 +61,17 @@ exports.login = async (body) => { throw new AppError(401, 'Incorrect email or password'); } + // 3) If everything ok, send token to client + // createSendToken(user, req, res); + + const token = signToken(user._id); + user.password = undefined; return { status: 'success', message: 'Your account has logged in', + data: { user }, + token, }; - - // 3) If everything ok, send token to client - // createSendToken(user, req, res); } catch (err) { throw new AppError(500, error.message); } From a232cbbb9919e357a638b9ee3a75ff230ddb8cdd Mon Sep 17 00:00:00 2001 From: chaunhutlong Date: Sat, 22 Jan 2022 21:07:02 +0700 Subject: [PATCH 12/12] fix middleware --- public/404.html | 148 ++++++++++++++++++++++++++++++++ server.js | 41 +++++++-- src/api/auth/auth.controller.js | 6 +- src/api/auth/auth.service.js | 103 +++++++++------------- src/api/auth/index.js | 8 +- src/common/appError.js | 11 +-- src/common/protectRoute.js | 2 +- src/common/userPermission.js | 6 +- src/models/userModel.js | 4 + 9 files changed, 246 insertions(+), 83 deletions(-) create mode 100644 public/404.html diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..efacd41 --- /dev/null +++ b/public/404.html @@ -0,0 +1,148 @@ + + + + + + + 404 + + + + +
+ + +

Oh no!!

+

+ You’re either misspelling the URL
+ or requesting a page that's no longer here. +

+ + + + + diff --git a/server.js b/server.js index 05358a1..0a0b61a 100644 --- a/server.js +++ b/server.js @@ -1,18 +1,45 @@ +const mongoose = require('mongoose'); const express = require('express'); +const dotenv = require('dotenv'); +const path = require('path'); + +dotenv.config({ path: './config.env' }); + const app = express(); -const path = require('path'); +const DB = process.env.DATABASE.replace('', process.env.DATABASE_PASSWORD); + +mongoose + .connect(DB, { + useUnifiedTopology: true, + useNewUrlParser: true, + }) + .then(() => console.log('DB connection successful!')); + +const api = require('./src/api'); -app.listen('3000', () => { - console.log('Runing'); +const port = process.env.PORT || 3000; +app.listen(port, () => { + console.log(`App running on port ${port}...`); }); +// ROUTING app.use(express.json()); -app.use('/', (req, res) => { - res.send('Hello!'); -}); +app.use('/api/v1', api); +// ERROR HANDLER app.use((req, res) => { - res.status(404).send('404'); + res.status(404).sendFile(path.join(__dirname, '/public/404.html')); +}); + +app.use((error, req, res, next) => { + let { statusCode, message } = error; + + statusCode = statusCode ? statusCode : 500; + + res.status(statusCode).json({ + statusCode, + message, + }); }); diff --git a/src/api/auth/auth.controller.js b/src/api/auth/auth.controller.js index 404312e..c5f7754 100644 --- a/src/api/auth/auth.controller.js +++ b/src/api/auth/auth.controller.js @@ -3,16 +3,14 @@ const authService = require('./auth.service'); module.exports = { signup: async (req, res, next) => { try { - let DTO = await authService.signup(req.body); - res.status(201).json(DTO); + res.send(await authService.signup(req.body)); } catch (error) { next(error); } }, login: async (req, res, next) => { try { - let DTO = await authService.login(req.body); - res.status(200).json(DTO); + res.send(await authService.login(req.body)); } catch (error) { next(error); } diff --git a/src/api/auth/auth.service.js b/src/api/auth/auth.service.js index 0f67ac0..3a42793 100644 --- a/src/api/auth/auth.service.js +++ b/src/api/auth/auth.service.js @@ -1,9 +1,9 @@ const crypto = require('crypto'); const { promisify } = require('util'); const jwt = require('jsonwebtoken'); -const User = require('./../models/userModel'); -const AppError = require('./../common/appError'); -const sendEmail = require('./../common/email'); +const User = require('./../../models/userModel'); +const AppError = require('./../../common/appError'); +const sendEmail = require('./../../common/email'); const signToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { @@ -30,7 +30,11 @@ const signToken = (id) => { exports.signup = async (body) => { try { - const newUser = await User.create({ + const user = await User.findOne({ email: body.email }); + if (user) { + throw new AppError(409, 'Email already exists! Please try another.'); + } + await User.create({ name: body.name, email: body.email, password: body.password, @@ -38,12 +42,12 @@ exports.signup = async (body) => { }); // createSendToken(newUser, req, res); return { - status: 'success', + statusCode: 200, message: 'Your account has been created', - data: newUser, }; - } catch (err) { - throw new AppError(500, error.message); + } catch (error) { + errorStatusCode = error.statusCode ? error.statusCode : 500; + throw new AppError(errorStatusCode, error.message); } }; @@ -67,55 +71,14 @@ exports.login = async (body) => { const token = signToken(user._id); user.password = undefined; return { - status: 'success', + statusCode: 200, message: 'Your account has logged in', - data: { user }, token, + data: user, }; - } catch (err) { - throw new AppError(500, error.message); - } -}; - -exports.protect = async (req) => { - try { - // 1) Getting token and check of it's there - let token; - if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { - token = req.headers.authorization.split(' ')[1]; - } else if (req.cookies.jwt) { - token = req.cookies.jwt; - } - - if (!token) { - throw new AppError(401, 'You are not logged in! Please log in to get access.'); - } - - // 2) Verification token - const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET); - - // 3) Check if user still exists - const currentUser = await User.findById(decoded.id); - if (!currentUser) { - throw new AppError(401, 'The user belonging to this token does no longer exist.'); - } - - // 4) Check if user changed password after the token was issued - if (currentUser.changedPasswordAfter(decoded.iat)) { - throw new AppError(401, 'User recently changed password! Please log in again.'); - } - - // GRANT ACCESS TO PROTECTED ROUTE - req.user = currentUser; - } catch (err) { - throw new AppError(500, error.message); - } -}; - -exports.restrictTo = (userRole, roles) => { - // roles ['admin',...] not 'user' - if (!roles.includes(userRole)) { - throw new AppError(403, 'You do not have permission to perform this action'); + } catch (error) { + errorStatusCode = error.statusCode ? error.statusCode : 500; + throw new AppError(errorStatusCode, error.message); } }; @@ -143,10 +106,10 @@ exports.forgotPassword = async (req) => { }); return { - status: 'success', + statusCode: 200, message: 'Token sent to email!', }; - } catch (err) { + } catch (error) { user.passwordResetToken = undefined; user.passwordResetExpires = undefined; await user.save({ validateBeforeSave: false }); @@ -175,13 +138,20 @@ exports.resetPassword = async (body, token) => { user.passwordResetExpires = undefined; await user.save(); - return { status: 'success', message: 'Your password has been reset' }; - // 3) Update changedPasswordAt property for the user // 4) Log the user in, send JWT // createSendToken(user, req, res); + token = signToken(user._id); + user.password = undefined; + return { + statusCode: 200, + message: 'Your password has been reset', + token, + data: user, + }; } catch { - throw new AppError(500, error.message); + errorStatusCode = error.statusCode ? error.statusCode : 500; + throw new AppError(errorStatusCode, error.message); } }; @@ -201,8 +171,19 @@ exports.updatePassword = async (userID, body) => { await user.save(); // 4) Log user in, send JWT + const token = signToken(user._id); + user.password = undefined; + + return { + statusCode: 200, + message: 'Your password has been reset', + token, + data: user, + }; + // createSendToken(user, req, res); - } catch (err) { - throw new AppError(500, error.message); + } catch (error) { + errorStatusCode = error.statusCode ? error.statusCode : 500; + throw new AppError(errorStatusCode, error.message); } }; diff --git a/src/api/auth/index.js b/src/api/auth/index.js index b82c9fa..4d1c729 100644 --- a/src/api/auth/index.js +++ b/src/api/auth/index.js @@ -1,13 +1,15 @@ // route endpoint /auth const authController = require('./auth.controller'); const router = require('express').Router(); +const { protectRoute } = require('../../common/protectRoute'); +// const { userPermission } = require('../../common/userPermission'); router.post('/signup', authController.signup); router.post('/login', authController.login); -router.post('forgotPassword', authController.forgotPassword); -router.patch('/resetPassword', authController.resetPassword); +router.route('/forgotPassword').post(authController.forgotPassword); +router.patch('/resetPassword/:token', authController.resetPassword); -router.patch('/updateMyPassword', authController.protect, authController.updatePassword); +router.route('/updateMyPassword').patch(protectRoute, authController.updatePassword); module.exports = router; diff --git a/src/common/appError.js b/src/common/appError.js index 2e3efe5..21c5678 100644 --- a/src/common/appError.js +++ b/src/common/appError.js @@ -1,12 +1,13 @@ class AppError extends Error { constructor(statusCode, message) { - super(message); + super(); - this.statusCode = statusCode; - this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; - this.isOperational = true; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, AppError); + } - Error.captureStackTrace(this, this.constructor); + this.message = message; + this.statusCode = statusCode; } } diff --git a/src/common/protectRoute.js b/src/common/protectRoute.js index f7a5698..9dddf83 100644 --- a/src/common/protectRoute.js +++ b/src/common/protectRoute.js @@ -3,7 +3,7 @@ const AppError = require('./appError'); const { promisify } = require('util'); const User = require('./../models/userModel'); -exports.protect = async (req, res, next) => { +exports.protectRoute = async (req, res, next) => { // 1) Getting token and check of it's there let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { diff --git a/src/common/userPermission.js b/src/common/userPermission.js index 93e8220..2865c63 100644 --- a/src/common/userPermission.js +++ b/src/common/userPermission.js @@ -1,7 +1,9 @@ -exports.restrictTo = (...roles) => { +const AppError = require('./appError'); + +exports.userPermission = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { - return next(new AppError('You do not have permission to perform this action', 403)); + throw new AppError(403, 'You do not have permission to perform this action'); } next(); }; diff --git a/src/models/userModel.js b/src/models/userModel.js index 569eebe..cd2087f 100644 --- a/src/models/userModel.js +++ b/src/models/userModel.js @@ -125,6 +125,10 @@ userSchema.methods.correctPassword = async function (candidatePassword, userPass return await bcrypt.compare(candidatePassword, userPassword); }; +userSchema.methods.duplicateEmail = async function (candidateEmail, userEmail) { + return candidateEmail === userEmail; +}; + userSchema.methods.changedPasswordAfter = function (JWTTimestamp) { if (this.passwordChangedAt) { const changedTimestamp = parseInt(this.passwordChangedAt.getTime() / 1000, 10);