diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index e601588c..50dbbc52 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Request, Response, NextFunction } from 'express'; import passport from 'passport'; import jwt from 'jsonwebtoken'; @@ -93,5 +95,61 @@ const login = async (req: Request, res: Response): Promise => { sendInternalErrorResponse(res, err); } }; +const updatePassword = async (req: Request, res: Response): Promise => { + try { + const { oldPassword, newPassword } = req.body; + const token = req.headers.authorization?.split(' ')[1]; + if (!token) { + res.status(401).json({ + ok: false, + message: 'Unauthorized', + }); + return; + } + const decode = jwt.verify(token, process.env.JWT_SECRET as string) as { id: string }; + if (!decode) { + res.status(400).json({ + ok: false, + message: 'Invalid token', + }); + } + const saltRound = await bcrypt.genSalt(10); + const user = await User.findOne({ + where: { + id: decode.id, + }, + }); + if (!user) { + res.status(400).json({ + ok: false, + message: 'User not found', + }); + return; + } + const match = await bcrypt.compare(oldPassword, user.password); + if (!match) { + res.status(400).json({ + ok: false, + message: 'The old password is incorrect!', + }); + } + const hashedNewPassword = await bcrypt.hash(newPassword, saltRound); + await User.update( + { password: hashedNewPassword }, + { + where: { + id: decode.id, + }, + } + ); + res.status(200).json({ + ok: true, + message: 'Successfully updated user password!', + }); + } catch (error) { + logger.error('Error updating user:', error); + sendInternalErrorResponse(res, error); + } +}; -export { login, authenticateViaGoogle }; +export { login, updatePassword, authenticateViaGoogle }; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 26c915ea..c6db3c33 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -59,7 +59,7 @@ export const signupUser = async (req: Request, res: Response) => { if (createdUser) { token = await userToken(createdUser.id as string, createdUser.email as string); } - const link: string = `${process.env.URL_HOST}:${process.env.PORT}/api/users/${token}/verify-email`; + const link: string = `${process.env.URL_HOST}/api/users/${token}/verify-email`; sendEmail('account_verify', { name: `${createdUser.firstName} ${createdUser.lastName}`, @@ -235,3 +235,38 @@ export const userVerify = async (req: Request, res: Response) => { } } }; +// Function for resend verification link +export const resendVerifyLink = async (req: Request, res: Response) => { + try { + const { email } = req.body; + + if (!validateEmail(email)) { + return res.status(400).json({ ok: false, error: 'Invalid email format' }); + } + if (validateFields(req, ['email']).length !== 0) { + res.status(400).json({ ok: false, error: 'Email is required' }); + return; + } + const user = await User.findOne({ where: { email } }); + if (!user) { + return res.status(400).json({ ok: false, error: 'User with this email does not exit, Sign up to continue' }); + } + const notVerifiedUser = await User.findOne({ where: { email, verified: false } }); + if (!notVerifiedUser) { + return res.status(202).json({ ok: false, error: `${email} is already verified. Login to continue` }); + } + + const token = await userToken(user.dataValues.id as string, user.dataValues.email as string); + const verificationLink: string = `${process.env.URL_HOST}/api/users/${token}/verify-email`; + + sendEmail('account_verify', { + name: `${user.dataValues.firstName} ${user.dataValues.lastName}`, + email, + link: verificationLink, + }); + res.status(201).json({ ok: true, message: 'Check your email to verify.' }); + } catch (error) { + logger.error('Resend-verify: ', error); + sendInternalErrorResponse(res, error); + } +}; diff --git a/src/docs/users.yaml b/src/docs/users.yaml index c1f4c00f..99c0ab00 100644 --- a/src/docs/users.yaml +++ b/src/docs/users.yaml @@ -160,5 +160,29 @@ paths: description: "Verification failed. Try again later" 403: description: "Verification link has expired. Please request a new one." + 500: + description: "Internal Server Error" + + /api/users/resend-verify: + post: + summary: Endpoint for resend link to verify your email + tags: + - User + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: + type: string + responses: + 201: + description: Check your email to verify. + 202: + description: User is already verified. Login to continue + 400: + description: Email is already used, Login to continuue 500: description: "Internal Server Error" \ No newline at end of file diff --git a/src/routes/authRoute.ts b/src/routes/authRoute.ts index d6c7fdc4..20e0ee80 100644 --- a/src/routes/authRoute.ts +++ b/src/routes/authRoute.ts @@ -1,8 +1,8 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ import { Router } from 'express'; import passport from 'passport'; -import { authenticateViaGoogle } from '../controllers/authController'; -import { login } from '../controllers/authController'; - +import { authenticateViaGoogle, login, updatePassword } from '../controllers/authController'; +import { isAuthenticated } from '../middlewares/authMiddlewares'; const router = Router(); // redirect user to google for authentication router.get( @@ -18,4 +18,6 @@ router.get('/google/callback', authenticateViaGoogle); // Route to login a user router.post('/login', login); +router.put('/update-password', updatePassword, isAuthenticated); + export default router; diff --git a/src/routes/userRoute.ts b/src/routes/userRoute.ts index b7d61964..05e8c98d 100644 --- a/src/routes/userRoute.ts +++ b/src/routes/userRoute.ts @@ -7,6 +7,7 @@ import { editUserRole, getAllUser, getOneUser, + resendVerifyLink, signupUser, userVerify, } from '../controllers/userController'; @@ -22,6 +23,7 @@ router.delete('/:id', isAuthenticated, checkUserRoles('admin'), deleteUser); router.patch('/edit/:id', isAuthenticated, multerUpload.single('profileImage'), editUser); // remove id param router.put('/role/:userId', isAuthenticated, checkUserRoles('admin'), editUserRole); router.get('/:token/verify-email', userVerify); +router.post('/resend-verify', resendVerifyLink); router.put('/deactivate/:userId', isAuthenticated, deactivateUserAccount); router.put('/activate/:userId', isAuthenticated, checkUserRoles('admin'), activateUserAccount); diff --git a/src/validations/index.ts b/src/validations/index.ts index 197da309..9fefd81d 100644 --- a/src/validations/index.ts +++ b/src/validations/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Response } from 'express'; // isEmpty function