Skip to content

Commit 5220c55

Browse files
authored
Merge pull request #39 from abhijit1859/feat/middleware-rbac
Implement Secure Password Reset Flow
2 parents da58e49 + 38a1a26 commit 5220c55

File tree

7 files changed

+144
-46
lines changed

7 files changed

+144
-46
lines changed

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,11 @@ PORT=5000
6868
6969
# Database Configuration
7070
MONGO_URI=mongodb://localhost:27017/rbac
71+
JWT_SECRET=your-secret-key
72+
RESEND_API_KEY=your-resend-api-key
7173
72-
# JWT Configuration
73-
JWT_SECRET=your-super-secret-jwt-key-here
74-
JWT_EXPIRY=1d
75-
76-
# Refresh Token Configuration
77-
REFRESH_TOKEN_SECRET=your-super-secret-refresh-token-key-here
78-
REFRESH_TOKEN_EXPIRY=7d
79-
80-
# CORS Configuration
81-
CORS_URL=http://localhost:3000
74+
🔑 Note: The RESEND_API_KEY can be obtained by creating an account on Resend Mail
75+
and generating an API key.
8276
```
8377

8478
### 4️⃣ Run the Project

package-lock.json

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"jsonwebtoken": "^9.0.2",
2727
"mongoose": "^8.19.1",
2828
"nodemon": "^3.1.10",
29-
"readdirp": "^4.1.2"
29+
"readdirp": "^4.1.2",
30+
"resend": "^6.1.3"
3031
}
3132
}

src/controllers/authController.js

Lines changed: 89 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { registerUserService, loginUserService, refreshTokenService, logoutService } from '../services/authService.js';
1+
import { User } from '../models/user.model.js';
2+
import { registerUserService, loginUserService } from '../services/authService.js';
3+
import jwt from 'jsonwebtoken'
4+
import { sendEmail } from '../utils/sendEmail.js';
25

36
export const registerUser = async (req, res) => {
47
try {
@@ -17,6 +20,7 @@ export const registerUser = async (req, res) => {
1720
export const loginUser = async (req, res) => {
1821
try {
1922
const { email, password } = req.body;
23+
2024
const result = await loginUserService({ email, password });
2125

2226
return res.status(200).json({
@@ -33,42 +37,98 @@ export const loginUser = async (req, res) => {
3337
}
3438
};
3539

36-
export const refreshToken = async (req, res) => {
40+
export const forgotPassword = async (req, res) => {
41+
const { email } = req.body;
42+
3743
try {
38-
const { refreshToken } = req.body;
39-
const result = await refreshTokenService(refreshToken);
44+
const user = await User.findOne({ email });
45+
46+
if (!user) {
47+
return res.status(401).json({
48+
success: false,
49+
message: "User not found"
50+
})
51+
}
52+
53+
const resetToken = jwt.sign(
54+
{ id: user._id },
55+
process.env.JWT_SECRET,
56+
{
57+
expiresIn: '1h'
58+
}
59+
);
60+
61+
62+
user.refreshToken = resetToken;
63+
await user.save();
64+
65+
const resetUrl = `http://localhost:5000/api/auth/resetPassword/${resetToken}`
66+
67+
const html = `
68+
<p>Hello ${user.fullname},</p>
69+
<p>You requested a password reset. Click below to reset your password:</p>
70+
<a href="${resetUrl}" target="_blank">${resetUrl}</a>
71+
<p>This link will expire in <b>1 hour</b>.</p>
72+
73+
`
4074

75+
await sendEmail(user.email,html);
76+
77+
console.log(resetUrl);
4178
return res.status(200).json({
4279
success: true,
43-
message: 'Token refreshed successfully',
44-
accessToken: result.accessToken,
45-
user: result.user,
46-
});
80+
message: 'Password reset link sent'
81+
})
4782
} catch (error) {
48-
console.error('Error in refreshToken:', error);
49-
const status = error.statusCode || 401;
50-
return res.status(status).json({
51-
success: false,
52-
message: error.message || 'Token refresh failed'
53-
});
83+
console.error(error)
84+
return res.status(500).json({
85+
success: false,
86+
message: 'server error'
87+
})
88+
5489
}
55-
};
90+
}
5691

57-
export const logout = async (req, res) => {
92+
93+
export const resetPassword = async (req, res) => {
5894
try {
59-
const { refreshToken } = req.body;
60-
const result = await logoutService(refreshToken);
95+
const { token } = req.params;
96+
const { password } = req.body;
6197

62-
return res.status(200).json({
63-
success: true,
64-
message: result.message,
65-
});
98+
if (!password) {
99+
return res.status(400).json({
100+
success: false,
101+
message: 'Password is required'
102+
})
103+
}
104+
105+
let decoded;
106+
try {
107+
decoded = jwt.verify(token, process.env.JWT_SECRET)
108+
} catch (error) {
109+
110+
return res.status(400).json({
111+
success: false,
112+
message: 'Invalid or expired token'
113+
})
114+
}
115+
116+
117+
118+
const user = await User.findById(decoded.id);
119+
console.log(user)
120+
if (!user || user.refreshToken !== token) {
121+
return res.status(400).json({ success: false, message: 'Invalid or expired token' });
122+
}
123+
user.password = password;
124+
125+
126+
user.refreshToken = undefined;
127+
await user.save();
128+
return res.status(200).json({ success: true, message: 'Password reset successful' });
66129
} catch (error) {
67-
console.error('Error in logout:', error);
68-
const status = error.statusCode || 400;
69-
return res.status(status).json({
70-
success: false,
71-
message: error.message || 'Logout failed'
72-
});
130+
131+
return res.status(500).json({ success: false, message: 'Server error' });
132+
73133
}
74-
};
134+
}

src/routes/authRoutes.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import express from 'express';
2-
import { registerUser, loginUser, refreshToken, logout } from '../controllers/authController.js';
2+
import { registerUser,loginUser, forgotPassword, resetPassword } from '../controllers/authController.js';
3+
import { authMiddleware } from '../middlewares/auth.middleware.js';
34

45
const router = express.Router();
56

67
router.post('/register', registerUser);
78
router.post('/login', loginUser);
8-
router.post('/refresh', refreshToken);
9-
router.post('/logout', logout);
9+
router.post('/forgotPassword',forgotPassword);
10+
router.post('/resetPassword/:token',resetPassword);
1011

1112
export default router;

src/services/authService.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,17 @@ export const loginUserService = async ({ email, password }) => {
5353
}
5454

5555
const user = await User.findOne({ email }).populate('role');
56+
5657
if (!user) {
5758
const err = new Error('Invalid credentials');
5859
err.statusCode = 401;
5960
throw err;
6061
}
62+
6163

62-
const isMatch = bcrypt.compare(password, user.password);
63-
64+
const isMatch = await bcrypt.compare(password, user.password);
65+
66+
6467
if (!isMatch) {
6568
const err = new Error('Invalid credentials');
6669
err.statusCode = 401;

src/utils/sendEmail.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Resend } from "resend";
2+
import dotenv from "dotenv";
3+
dotenv.config();
4+
const resend = new Resend(process.env.RESEND_API_KEY);
5+
6+
export const sendEmail = async (to,html) => {
7+
try {
8+
const response = await resend.emails.send({
9+
from: "Acme <[email protected]>",
10+
to: [to],
11+
subject:"Password reset Link",
12+
html,
13+
});
14+
15+
console.log("Email sent successfully:", response);
16+
return response;
17+
} catch (error) {
18+
console.error("Error sending email:", error);
19+
throw new Error("Failed to send email");
20+
}
21+
}

0 commit comments

Comments
 (0)