diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 039330be..337d28e9 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,70 +1,34 @@ const httpStatus = require('http-status'); const catchAsync = require('../utils/catchAsync'); -const { User, Token } = require('../models'); -const { tokenService, emailService } = require('../services'); -const ApiError = require('../utils/ApiError'); +const { authService, userService, tokenService, emailService } = require('../services'); const register = catchAsync(async (req, res) => { - if (await User.isEmailTaken(req.body.email)) { - throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); - } - const user = await User.create(req.body); - const tokens = await tokenService.generateAuthTokens(user.id); - const response = { user, tokens }; - res.status(httpStatus.CREATED).send(response); + const user = await userService.createUser(req.body); + const tokens = await tokenService.generateAuthTokens(user); + res.status(httpStatus.CREATED).send({ user, tokens }); }); const login = catchAsync(async (req, res) => { const { email, password } = req.body; - const user = await User.findOne({ email }); - if (!user || !(await user.isPasswordMatch(password))) { - throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password'); - } - const tokens = await tokenService.generateAuthTokens(user.id); - const response = { user, tokens }; - res.send(response); + const user = await authService.loginUserWithEmailAndPassword(email, password); + const tokens = await tokenService.generateAuthTokens(user); + res.send({ user, tokens }); }); const refreshTokens = catchAsync(async (req, res) => { - try { - const refreshTokenDoc = await tokenService.verifyToken(req.body.refreshToken, 'refresh'); - const user = await User.findById(refreshTokenDoc.user); - if (!user) { - throw new Error(); - } - await refreshTokenDoc.remove(); - const tokens = await tokenService.generateAuthTokens(user.id); - const response = { ...tokens }; - res.send(response); - } catch (error) { - throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'); - } + const tokens = await authService.refreshAuth(req.body.refreshToken); + res.send({ ...tokens }); }); const forgotPassword = catchAsync(async (req, res) => { - const user = await User.findOne({ email: req.body.email }); - if (!user) { - throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email'); - } - const resetPasswordToken = await tokenService.generateResetPasswordToken(user.id); + const resetPasswordToken = await tokenService.generateResetPasswordToken(req.body.email); await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken); res.status(httpStatus.NO_CONTENT).send(); }); const resetPassword = catchAsync(async (req, res) => { - try { - const resetPasswordTokenDoc = await tokenService.verifyToken(req.query.token, 'resetPassword'); - const user = await User.findById(resetPasswordTokenDoc.user); - if (!user) { - throw new Error(); - } - user.password = req.body.password; - await user.save(); - await Token.deleteMany({ user: user.id, type: 'resetPassword' }); - res.status(httpStatus.NO_CONTENT).send(); - } catch (error) { - throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed'); - } + await authService.resetPassword(req.query.token, req.body.password); + res.status(httpStatus.NO_CONTENT).send(); }); module.exports = { diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index aa35f282..dc185746 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,28 +1,24 @@ const httpStatus = require('http-status'); const { pick } = require('lodash'); -const { User } = require('../models'); const ApiError = require('../utils/ApiError'); const catchAsync = require('../utils/catchAsync'); +const { userService } = require('../services'); const { getQueryOptions } = require('../utils/query.utils'); const createUser = catchAsync(async (req, res) => { - if (await User.isEmailTaken(req.body.email)) { - throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); - } - const user = await User.create(req.body); + const user = await userService.createUser(req.body); res.status(httpStatus.CREATED).send(user); }); const getUsers = catchAsync(async (req, res) => { const filter = pick(req.query, ['name', 'role']); const options = getQueryOptions(req.query); - const users = await User.find(filter, null, options); - const response = users; - res.send(response); + const users = await userService.getUsers(filter, options); + res.send(users); }); const getUser = catchAsync(async (req, res) => { - const user = await User.findById(req.params.userId); + const user = await userService.getUserById(req.params.userId); if (!user) { throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); } @@ -30,24 +26,12 @@ const getUser = catchAsync(async (req, res) => { }); const updateUser = catchAsync(async (req, res) => { - const user = await User.findById(req.params.userId); - if (!user) { - throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); - } - if (req.body.email && (await User.isEmailTaken(req.body.email, user.id))) { - throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); - } - Object.assign(user, req.body); - await user.save(); + const user = await userService.updateUserById(req.params.userId, req.body); res.send(user); }); const deleteUser = catchAsync(async (req, res) => { - const user = await User.findById(req.params.userId); - if (!user) { - throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); - } - await user.remove(); + await userService.deleteUserById(req.params.userId); res.status(httpStatus.NO_CONTENT).send(); }); diff --git a/src/services/auth.service.js b/src/services/auth.service.js new file mode 100644 index 00000000..5136561a --- /dev/null +++ b/src/services/auth.service.js @@ -0,0 +1,47 @@ +const httpStatus = require('http-status'); +const tokenService = require('./token.service'); +const userService = require('./user.service'); +const Token = require('../models/token.model'); +const ApiError = require('../utils/ApiError'); + +const loginUserWithEmailAndPassword = async (email, password) => { + const user = await userService.getUserByEmail(email); + if (!user || !(await user.isPasswordMatch(password))) { + throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password'); + } + return user; +}; + +const refreshAuth = async (refreshToken) => { + try { + const refreshTokenDoc = await tokenService.verifyToken(refreshToken, 'refresh'); + const user = await userService.getUserById(refreshTokenDoc.user); + if (!user) { + throw new Error(); + } + await refreshTokenDoc.remove(); + return tokenService.generateAuthTokens(user); + } catch (error) { + throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'); + } +}; + +const resetPassword = async (resetPasswordToken, newPassword) => { + try { + const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, 'resetPassword'); + const user = await userService.getUserById(resetPasswordTokenDoc.user); + if (!user) { + throw new Error(); + } + await Token.deleteMany({ user: user.id, type: 'resetPassword' }); + await userService.updateUserById(user.id, { password: newPassword }); + } catch (error) { + throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed'); + } +}; + +module.exports = { + loginUserWithEmailAndPassword, + refreshAuth, + resetPassword, +}; diff --git a/src/services/index.js b/src/services/index.js index b6466005..73547db9 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,2 +1,4 @@ +module.exports.authService = require('./auth.service'); module.exports.emailService = require('./email.service'); module.exports.tokenService = require('./token.service'); +module.exports.userService = require('./user.service'); diff --git a/src/services/token.service.js b/src/services/token.service.js index dd01df9c..19af2d4c 100644 --- a/src/services/token.service.js +++ b/src/services/token.service.js @@ -1,7 +1,10 @@ const jwt = require('jsonwebtoken'); const moment = require('moment'); +const httpStatus = require('http-status'); const config = require('../config/config'); +const userService = require('./user.service'); const { Token } = require('../models'); +const ApiError = require('../utils/ApiError'); const generateToken = (userId, expires, secret = config.jwt.secret) => { const payload = { @@ -32,13 +35,13 @@ const verifyToken = async (token, type) => { return tokenDoc; }; -const generateAuthTokens = async (userId) => { +const generateAuthTokens = async (user) => { const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes'); - const accessToken = generateToken(userId, accessTokenExpires); + const accessToken = generateToken(user.id, accessTokenExpires); const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days'); - const refreshToken = generateToken(userId, refreshTokenExpires); - await saveToken(refreshToken, userId, refreshTokenExpires, 'refresh'); + const refreshToken = generateToken(user.id, refreshTokenExpires); + await saveToken(refreshToken, user.id, refreshTokenExpires, 'refresh'); return { access: { @@ -52,10 +55,14 @@ const generateAuthTokens = async (userId) => { }; }; -const generateResetPasswordToken = async (userId) => { +const generateResetPasswordToken = async (email) => { + const user = await userService.getUserByEmail(email); + if (!user) { + throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email'); + } const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes'); - const resetPasswordToken = generateToken(userId, expires); - await saveToken(resetPasswordToken, userId, expires, 'resetPassword'); + const resetPasswordToken = generateToken(user.id, expires); + await saveToken(resetPasswordToken, user.id, expires, 'resetPassword'); return resetPasswordToken; }; diff --git a/src/services/user.service.js b/src/services/user.service.js new file mode 100644 index 00000000..44a08b5f --- /dev/null +++ b/src/services/user.service.js @@ -0,0 +1,55 @@ +const httpStatus = require('http-status'); +const { User } = require('../models'); +const ApiError = require('../utils/ApiError'); + +const createUser = async (userBody) => { + if (await User.isEmailTaken(userBody.email)) { + throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); + } + const user = await User.create(userBody); + return user; +}; + +const getUsers = async (filter, options) => { + const users = await User.find(filter, null, options); + return users; +}; + +const getUserById = async (id) => { + return User.findById(id); +}; + +const getUserByEmail = async (email) => { + return User.findOne({ email }); +}; + +const updateUserById = async (userId, updateBody) => { + const user = await getUserById(userId); + if (!user) { + throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); + } + if (updateBody.email && (await User.isEmailTaken(updateBody.email, userId))) { + throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); + } + Object.assign(user, updateBody); + await user.save(); + return user; +}; + +const deleteUserById = async (userId) => { + const user = await getUserById(userId); + if (!user) { + throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); + } + await user.remove(); + return user; +}; + +module.exports = { + createUser, + getUsers, + getUserById, + getUserByEmail, + updateUserById, + deleteUserById, +};