Skip to content

Commit

Permalink
🔀 Update User login feature (#8)
Browse files Browse the repository at this point in the history
* ✨ feat: password reset

* ✨ feat: Add Update Profile

* 🩹 Update ErroMiddleware
  • Loading branch information
nmdra authored Aug 8, 2024
1 parent 136598d commit 6628410
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 37 deletions.
119 changes: 101 additions & 18 deletions backend/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,49 @@ export const logoutUser = (_req, res) => {
res.status(200).json({ message: 'Logged out successfully' })
}

export const updateUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id)

if (user) {
user.name = req.body.name || user.name
user.email = req.body.email || user.email
user.password = req.body.password || user.password
user.defaultAddress = req.body.defaultAddress || user.defaultAddress
user.contactNumber = req.body.contactNumber || user.contact
// user.picture = req.body.picture || user.picture
// user.role = user.role;
// user.password = req.body.password || user.password

if (req.body.password) {
user.password = req.body.password
}

const updatedUser = await user.save()

res.status(201).json({
message: 'User updated successfully',
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
role: updatedUser.role,
contactNumber: updatedUser.contactNumber
})
} else {
res.status(404)
throw new Error('User not found')
}
} catch (error) {
return next(error)
}
}

// @desc Get user by ID
// @route GET /api/users/:id
// @access Private/Admin
export const getUserById = async (req, res, next) => {
if (req.user.role !== 'admin') {
res.status(403)
throw new Error('Unauthorized access')
return res.status(403).json('Unauthorized')
}

try {
Expand All @@ -104,8 +140,7 @@ export const getUserById = async (req, res, next) => {
if (user) {
res.json(user);
} else {
res.status(404);
throw new Error('User not found');
return res.status(404).json('User not found')
}
} catch (error) {
next(error)
Expand Down Expand Up @@ -133,19 +168,18 @@ export const getUserProfile = async (req, res) => {
// @desc Send Verify Email
// @route GET /api/users/verify
// @access Private

export const sendVerifyEmail = async (user) => {

const token = tokenToVerify(user.email);
const body = {
from: `'FarmCart 🌱' <${process.env.EMAIL_USER}>`,
to: `${user.email}`,
subject: 'FarmCart: Email Activation',
html: `
export const sendVerifyEmail = async (user, res) => {
try {
const token = tokenToVerify(user.email);
const body = {
from: `'FarmCart 🌱' <${process.env.EMAIL_USER}>`,
to: `${user.email}`,
subject: 'FarmCart: Email Activation',
html: `
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<h2 style="color: #22c55e;">Hello ${user.name},</h2>
<p>Thank you for signing up with <strong>FarmCart</strong>. Please verify your email address to complete your registration.</p>
<p>This link will expire in <strong>2 days</strong>.</p>
<p>This link will expire in <strong>2 minutes</strong>.</p>
<p style="margin-bottom: 20px;">Click the button below to activate your account:</p>
<a href="${process.env.SITE_URL}/api/users/verify?token=${token}"
style="background: #22c55e; color: white; border: 1px solid #22c55e; padding: 10px 15px; border-radius: 4px; text-decoration: none; display: inline-block;">Verify Account</a>
Expand All @@ -154,10 +188,16 @@ export const sendVerifyEmail = async (user) => {
<p style="font-weight: bold;">The FarmCart Team</p>
</div>
`,
};
};

const message = 'Please check your email to verify!';
sendEmail(body, message);
const message = 'Please check your email to verify!';
await sendEmail(body, message);
return res.status(200).json({ success: true, message });
} catch (error) {
console.error(`Error in sending verification email: ${error.message}`);
res.status(500)
throw new Error(`Error in sending verification email`)
}
}

export const verifyEmail = async (req, res) => {
Expand All @@ -182,4 +222,47 @@ export const verifyEmail = async (req, res) => {
} catch (error) {
return res.status(400).json({ success: false, message: error.message })
}
}
}

// @desc Send Password Reset Email
// @route GET /api/users/forgot-password
// @access Private

export const forgotPassword = async (req, res) => {
try {
const isAdded = await User.findOne({ email: req.body.verifyEmail })
if (!isAdded) {
return res.status(404).json({ success: false, message: 'No user found with this email' })
}

const token = await tokenToVerify(isAdded.email)

const body = {
from: `'FarmCart 🌱' <${process.env.EMAIL_USER}>`,
to: `${req.body.verifyEmail}`,
subject: 'FarmCart: Password Reset',
html: `<h2>Hello ${req.body.verifyEmail}</h2>
<p>A request has been received to change the password for your <strong>FarmCart</strong> account </p>
<p>This link will expire in <strong> 15 minutes</strong>.</p>
<p style="margin-bottom:20px;">Click this link for reset your password</p>
<a href="${process.env.SITE_URL}/api/users/reset-pass?token=${token}"
style="background: #ff0000; color: white; border: 1px solid #ff0000; padding: 10px 15px; border-radius: 4px; text-decoration: none; display: inline-block;">Reset Password</a>
<p style="margin-top: 35px;">If you did not initiate this request, please contact us immediately at support@farmcart.com</p>
<p style="margin-bottom:0px;">Thank you</p>
<strong>FarmCart Team</strong>
`,
};

const message = 'Please check your email to reset your password!';
await sendEmail(body);

return res.status(200).json({ success: true, message });
} catch (error) {
console.error(`Error in forgotPassword: ${error.message}`);
res.status(500)
throw new Error('Internal server error');
}
}
5 changes: 5 additions & 0 deletions backend/middlewares/asyncHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
}

export default asyncHandler;
12 changes: 7 additions & 5 deletions backend/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import jwt from 'jsonwebtoken'
import CustomError from '../utils/customError.js'
import User from '../model/userModel.js'
import User from '../models/userModel.js'

const protect = async (req, res, next) => {
let token = req.cookies.jwt
Expand All @@ -12,16 +11,19 @@ const protect = async (req, res, next) => {
req.user = await User.findById(decoded.userId).select('-password')

if (!req.user) {
throw new CustomError('User not found', 403)
res.status(403)
throw new Error('User not found')
}

next()
} else {
throw new CustomError('Not authorized. No token provided', 401)
res.status(401)
throw new Error('Not authorized. No token provided', 401)
}
} catch (error) {
if (error instanceof jwt.JsonWebTokenError) {
return next(new CustomError('Invalid token signature', 401))
res.status(401)
return next(new Error('Invalid token signature'))
}
next(error)
}
Expand Down
18 changes: 15 additions & 3 deletions backend/middlewares/errorMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
const notFound = (req, res, next) => {
const error = new Error(`Not Found -${req.originalUrl}`);
res.status(404);
next(error);
};

const errorHandler = (err, _req, res, next) => {
let statusCode = res.statusCode === 200 ? 500 : res.statusCode
let message = err.message

//check for Mongoose bad Object
if(err.name === 'CastError' && err.kind === 'ObjectId') {
message = `Resource not founded`;
statusCode = 404;
}

res.status(statusCode).json({
message: message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack,
message,
stack: process.env.NODE_ENV === 'production' ? 'null' : err.stack,
})

next()
}

export { errorHandler }
export { errorHandler, notFound }
11 changes: 8 additions & 3 deletions backend/routes/userRoute.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import express from 'express'
import { authUser, logoutUser, registerUser, verifyEmail } from '../controllers/userController.js'
import { authUser, forgotPassword, getUserById, logoutUser, registerUser, updateUserProfile, verifyEmail } from '../controllers/userController.js'
import protect from '../middlewares/authMiddleware.js'

const router = express.Router()

router.route('').post(registerUser)
router.route('').post(registerUser).put(protect, updateUserProfile)
router.route('/auth').post(authUser)
router.route('/logout').post(logoutUser)
router.route('/logout').post(protect, logoutUser)
router.route('/verify').get(verifyEmail)
router.route('/forgot-password').post(forgotPassword)

router.route('/:id').get(protect, getUserById)

export default router
21 changes: 14 additions & 7 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import cors from 'cors'
import cookieParser from 'cookie-parser'
import connectDB from './config/db.js'
import userRoute from './routes/userRoute.js'
import { errorHandler } from './middlewares/errorMiddleware.js'
import { errorHandler, notFound } from './middlewares/errorMiddleware.js'

// load environment variables
const PORT = process.env.PORT || 8000
Expand All @@ -21,17 +21,24 @@ app.use(express.urlencoded({ extended: true }))
app.use(cookieParser())

// routes
app.get('/', (_req, res) => {
res.send('FarmCart API is Running...')
})

// User API routes
app.use('/api/users', userRoute)

app.all('*', (_req, res) => {
res.status(404).json({
message: 'Page not found',
statusCode: 404,
})
})
// Shop API routes
// app.use('/api/shops', shopRoute);
// app.use('/api/shops', productRoutes)

// Middleware to handle 404 errors (route not found)
app.use(notFound)

// Middleware to handle errors and send appropriate responses
app.use(errorHandler)

// Start the server and listen on the specified port
app.listen(PORT, () => {
console.log(`Server currently is running on port ${PORT}`)
}).on('error', (error) => {
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/generateToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const generateToken = (res, userId) => {

export const tokenToVerify = (email) => {
return jwt.sign({ email }, process.env.JWT_SECRET,{
expiresIn: '48h'
expiresIn: '15m'
});
};

1 change: 1 addition & 0 deletions backend/utils/sendEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const sendEmail = async (body) => {
});

await transporter.sendMail(body);

return { success: true, message: 'Email sent successfully' };
} catch (err) {
return { success: false, message: `Error sending email: ${err.message}` };
Expand Down

0 comments on commit 6628410

Please sign in to comment.