Skip to content

Commit

Permalink
[finishes #187354249] reset password via email
Browse files Browse the repository at this point in the history
  • Loading branch information
princenzmw committed May 11, 2024
1 parent 92a721f commit 04c3dc5
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 15 deletions.
90 changes: 88 additions & 2 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { Request, Response, NextFunction } from 'express';
import passport from 'passport';
import jwt, { JwtPayload } from 'jsonwebtoken';
import User, { UserAttributes } from '../database/models/user';
import { sendInternalErrorResponse, validateFields } from '../validations';
import { sendInternalErrorResponse, validateFields, validatePassword } from '../validations';
import logger from '../logs/config';
import { passwordCompare } from '../helpers/encrypt';
import { verifyIfSeller } from '../middlewares/authMiddlewares';
import { createOTPToken, saveOTPDB } from '../middlewares/otpAuthMiddleware';
import { userToken } from '../helpers/token.generator';
import { userToken, verifyToken } from '../helpers/token.generator';
import { sendErrorResponse } from '../helpers/helper';
// Additional imports
import { sendEmail } from '../helpers/send-email';
import { passwordEncrypt } from '../helpers/encrypt';

export const authenticateViaGoogle = (req: Request, res: Response, next: NextFunction) => {
passport.authenticate('google', (err: unknown, user: UserAttributes | null) => {
Expand Down Expand Up @@ -119,3 +122,86 @@ export const sendOTP = async (req: Request, res: Response, email: string) => {
}
}
};

// Function to Request Password reset token
export const forgotPassword = async (req: Request, res: Response) => {
try {
const { email } = req.body;

// Verify if user exists
const user = await User.findOne({ where: { email } });
if (!user) {
return res.status(404).json({ ok: false, error: 'User with this email does not exist' });
}

// Generate reset token
const token = await userToken(user.id, user.email);

// Send email with token
const link = `${process.env.URL_HOST}:${process.env.PORT}/api/auth/reset-password/${token}`;

await sendEmail('reset_password', {
name: `${user.firstName} ${user.lastName}`,
email: `${user.email}`,
link: link,
});

return res.status(200).json({
ok: true,
message: 'A password reset link has been sent to your email.',
});
} catch (error) {
logger.error('Error requesting password reset: ', error);
sendInternalErrorResponse(res, error);
return;
}
};

// Function to Reset Password
export const resetPassword = async (req: Request, res: Response) => {
try {
const { newPassword } = req.body;
const { token } = req.params;

// Verify token
const verifiedPayload: any = verifyToken(token);

if (typeof verifiedPayload === 'object' && verifiedPayload !== null && 'id' in verifiedPayload) {
const userPayload = verifiedPayload as UserAttributes;
if (!userPayload.id) {
return res.status(404).json({ ok: false, error: 'Password reset token is invalid or has expired' });
}

// Find user
const user = await User.findOne({ where: { id: userPayload.id } });
if (!user) {
return res.status(404).json({ ok: false, error: 'User does not exist' });
}

if (!validatePassword(newPassword)) {
return res.status(400).json({
ok: false,
error:
'Ensure the New Password contains at least 1 letter, 1 number, and 1 special character, minumun 8 characters',
});
}

// Hash new password
const hashPassword = await passwordEncrypt(newPassword);

// Update user's password
await user.update({ password: hashPassword });

return res.status(200).json({
ok: true,
message: 'Password reset successfully',
});
} else {
return res.status(404).json({ ok: false, error: 'Invalid token structure' });
}
} catch (error) {
logger.error('Error resetting password: ', error);
sendInternalErrorResponse(res, error);
return;
}
};
99 changes: 90 additions & 9 deletions src/docs/auth.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
paths:

/api/auth/google:
get:
summary: Authenticate with Google
Expand All @@ -16,7 +15,7 @@ paths:
/api/auth/login:
post:
summary: User Login
tags:
tags:
- Login
description: Authenticate user email and password and generate access token
requestBody:
Expand Down Expand Up @@ -48,16 +47,16 @@ paths:
description: Access token for authentication
400:
description: Invalid request parameters
404:
404:
description: User not found or incorrect credentials
500:
description: Internal server error
/api/auth/{token}/otp:
post:
summary: "Endpoint to verify OTP sent to you"
summary: 'Endpoint to verify OTP sent to you'
tags:
- Login
parameters:
parameters:
- in: path
name: token
required: true
Expand All @@ -74,14 +73,96 @@ paths:
type: string
responses:
200:
description: "Get new to token for login"
description: 'Get new to token for login'
404:
description: "No user sent token"
description: 'No user sent token'
400:
description: "OTP Enter is wrong"
description: 'OTP Enter is wrong'
403:
description: "Token expired"
description: 'Token expired'

/api/auth/forgot-password:
post:
summary: Request Password Reset
tags:
- Password Reset
description: |
Allows users to request a password reset link. The user must provide their email address. If the user exists, a password reset link will be sent to the provided email.
requestBody:
description: User's email address to request a password reset link
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
format: email
description: User's email address
responses:
200:
description: Password reset link sent successfully
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
message:
type: string
description: Confirmation message indicating the email was sent
404:
description: User with this email does not exist
500:
description: Internal server error

/api/auth/reset-password/{token}:
post:
summary: Reset Password
tags:
- Password Reset
description: |
Allows users to reset their password using a valid token received in the password reset link. The token is passed as a URL parameter, and the new password is provided by the user in the request body.
parameters:
- in: path
name: token
required: true
schema:
type: string
description: Token received for resetting password via email
requestBody:
description: New password to be set for the user
required: true
content:
application/json:
schema:
type: object
properties:
newPassword:
type: string
description: User's new password
responses:
200:
description: Password reset successfully
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
message:
type: string
description: Confirmation message indicating the password reset was successful
404:
description: Password reset token is invalid or has expired, or user does not exist
500:
description: Internal server error

tags:
- name: Login
description: Login a user
- name: Password Reset
description: Endpoints for password recovery and reset functionality
8 changes: 4 additions & 4 deletions src/helpers/send-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,13 @@ export const sendEmail = async (type: string, data: IData) => {
email = {
body: {
name: data.name,
intro: "Welcome to Mailgen! We're very excited to have you on board.",
intro: 'You have requested a password reset. Please follow the link below to change your password:',
action: {
instructions: 'To get started with Mailgen, please click here:',
instructions: 'Click on the button below to reset your password:',
button: {
color: '#22BC66',
text: 'Confirm your account',
link: 'https://mailgen.js/confirm?s=d9729feb74992cc3482b350163a1a010',
text: 'Reset password',
link: `${data.link}`,
},
},
outro: "Need help, or have questions? Just reply to this email, we'd love to help.",
Expand Down
7 changes: 7 additions & 0 deletions src/routes/authRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import passport from 'passport';
import { authenticateViaGoogle, verifyOTP } from '../controllers/authController';
import { login } from '../controllers/authController';
import { isCheckedOTP } from '../middlewares/otpAuthMiddleware';
import { forgotPassword, resetPassword } from '../controllers/authController';

const router = Router();
// redirect user to google for authentication
Expand All @@ -11,4 +12,10 @@ router.get('/google/callback', authenticateViaGoogle);
router.post('/login', login);
router.post('/:token/otp', isCheckedOTP, verifyOTP);

// Route to request password reset
router.post('/forgot-password', forgotPassword);

// Route to reset password using provided token
router.post('/reset-password/:token', resetPassword);

export default router;

0 comments on commit 04c3dc5

Please sign in to comment.