Skip to content

Commit

Permalink
Merge pull request #45 from atlp-rwanda/resend-verification-link
Browse files Browse the repository at this point in the history
ft-resend-verification-link
  • Loading branch information
niyontwali authored and P-Rwirangira committed Apr 30, 2024
2 parents 01086fb + 86c1507 commit 13e35e1
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 5 deletions.
60 changes: 59 additions & 1 deletion src/controllers/authController.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -93,5 +95,61 @@ const login = async (req: Request, res: Response): Promise<void> => {
sendInternalErrorResponse(res, err);
}
};
const updatePassword = async (req: Request, res: Response): Promise<void> => {
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 };
37 changes: 36 additions & 1 deletion src/controllers/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand Down Expand Up @@ -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);
}
};
24 changes: 24 additions & 0 deletions src/docs/users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
8 changes: 5 additions & 3 deletions src/routes/authRoute.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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;
2 changes: 2 additions & 0 deletions src/routes/userRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
editUserRole,
getAllUser,
getOneUser,
resendVerifyLink,
signupUser,
userVerify,
} from '../controllers/userController';
Expand All @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/validations/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Response } from 'express';

// isEmpty function
Expand Down

0 comments on commit 13e35e1

Please sign in to comment.