Skip to content

Commit 6f3d332

Browse files
authored
Merge pull request #58 from indrasuthar07/auth-fix
feat: authentication, forgot password flow fix
2 parents e787e70 + e22ca3b commit 6f3d332

File tree

14 files changed

+448
-116
lines changed

14 files changed

+448
-116
lines changed

backend/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ SMTP_PASS=your_app_password
1414
EMAIL_FROM="MailMERN no-reply@example.com"
1515
EMAIL_TEST_TO=reciever@example.com
1616
NODE_ENV=development
17+
JWT_SECRET=mailmern2323

backend/src/controllers/userController.js

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ exports.login = async (req, res) => {
3939
return res.status(401).json({ success: false, message: "Invalid email or password" });
4040
}
4141

42+
const jwtSecret = process.env.JWT_SECRET || 'mailmern-secret';
4243
const token = jwt.sign(
4344
{ id: user._id, email: user.email },
44-
process.env.JWT_SECRET,
45+
jwtSecret,
4546
{ expiresIn: "1d" }
4647
);
4748

@@ -55,66 +56,88 @@ exports.login = async (req, res) => {
5556
res.status(400).json({ success: false, error: err.message });
5657
}
5758
};
58-
exports.sendOtp = async (req,res) => {
59+
exports.sendOtp = async (req, res) => {
5960
try {
60-
const {email} = req.body;
61-
const user = await User.findOne({email});
62-
if (!user) return res.status(400).json({message:"User does not exist."});
61+
const { email } = req.body;
62+
if (!email) {
63+
return res.status(400).json({ success: false, message: "Email is required" });
64+
}
65+
const user = await User.findOne({ email });
66+
if (!user) {
67+
return res.status(400).json({ success: false, message: "User does not exist." });
68+
}
6369

6470
const otp = Math.floor(1000 + Math.random() * 9000).toString(); // 4-digit OTP
6571
user.resetOtp = otp;
66-
user.otpExpires = Date.now() + 5*60*1000; // 5 minutes
72+
user.otpExpires = Date.now() + 5*60*1000;
6773
user.isOtpVerified = false;
6874
await user.save();
6975

70-
await sendEmail
71-
72-
76+
await sendEmail({ to: email, otp });
7377

74-
75-
({ to: email, otp }); // ✅ Correct usage
76-
return res.status(200).json({message:"OTP sent successfully"});
78+
return res.status(200).json({ success: true, message: "OTP sent successfully" });
7779
} catch (error) {
78-
return res.status(500).json({message: `send otp error ${error}`});
79-
}
80+
console.error("Send OTP error:", error);
81+
return res.status(500).json({ success: false, message: `Failed to send OTP: ${error.message}` });
82+
}
8083
};
8184

82-
83-
exports.verifyOtp = async (req,res) => {
85+
exports.verifyOtp = async (req, res) => {
8486
try {
85-
const {email, otp} = req.body;
86-
const user = await User.findOne({email});
87-
if (!user || user.resetOtp !== otp || user.otpExpires < Date.now()) {
88-
return res.status(400).json({message:"Invalid/expired OTP"});
87+
const { email, otp } = req.body;
88+
if (!email || !otp) {
89+
return res.status(400).json({ success: false, message: "Email and OTP are required" });
90+
}
91+
const user = await User.findOne({ email });
92+
if (!user) {
93+
return res.status(400).json({ success: false, message: "User does not exist" });
94+
}
95+
if (!user.resetOtp || user.resetOtp !== otp) {
96+
return res.status(400).json({ success: false, message: "Invalid OTP" });
97+
}
98+
if (user.otpExpires < Date.now()) {
99+
return res.status(400).json({ success: false, message: "OTP has expired" });
89100
}
90101

91102
user.isOtpVerified = true;
92103
user.resetOtp = undefined;
93104
user.otpExpires = undefined;
94105
await user.save();
95106

96-
return res.status(200).json({message:"OTP verified successfully"});
107+
return res.status(200).json({ success: true, message: "OTP verified successfully" });
97108
} catch (error) {
98-
return res.status(500).json({message: `verify otp error ${error}`});
109+
console.error("Verify OTP error:", error);
110+
return res.status(500).json({ success: false, message: `Failed to verify OTP: ${error.message}` });
99111
}
100112
};
101113

102-
exports.resetPassword = async (req,res) => {
114+
exports.resetPassword = async (req, res) => {
103115
try {
104-
const {email, newPassword} = req.body;
105-
const user = await User.findOne({email});
106-
if (!user || !user.isOtpVerified) {
107-
return res.status(400).json({message:"OTP verification required"});
116+
const { email, newPassword } = req.body;
117+
if (!email || !newPassword) {
118+
return res.status(400).json({ success: false, message: "Email and new password are required" });
119+
}
120+
if (newPassword.length < 6) {
121+
return res.status(400).json({ success: false, message: "Password must be at least 6 characters" });
122+
}
123+
const user = await User.findOne({ email });
124+
if (!user) {
125+
return res.status(400).json({ success: false, message: "User does not exist" });
126+
}
127+
if (!user.isOtpVerified) {
128+
return res.status(400).json({ success: false, message: "OTP verification required. Please verify OTP first." });
108129
}
109130

110131
const hashedPassword = await bcrypt.hash(newPassword, 10);
111132
user.password = hashedPassword;
112133
user.isOtpVerified = false; // reset verification
134+
user.resetOtp = undefined;
135+
user.otpExpires = undefined;
113136
await user.save();
114137

115-
return res.status(200).json({message:"Password reset successfully"});
138+
return res.status(200).json({ success: true, message: "Password reset successfully" });
116139
} catch (error) {
117-
return res.status(500).json({message: `reset password error ${error}`});
140+
console.error("Reset password error:", error);
141+
return res.status(500).json({ success: false, message: `Failed to reset password: ${error.message}` });
118142
}
119-
};
120-
143+
};
Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,56 @@
1-
// Simple auth middleware placeholder
2-
module.exports = (req, res, next) => {
3-
// In real app, validate JWT or session here
4-
next();
1+
const jwt = require('jsonwebtoken');
2+
const User = require('../models/User');
3+
4+
//JWT Auth Middleware
5+
const authMiddleware = async (req, res, next) => {
6+
try {
7+
const authHeader = req.headers.authorization;
8+
9+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
10+
return res.status(401).json({
11+
success: false,
12+
message: 'No token provided. Authorization denied.'
13+
});
14+
}
15+
const token = authHeader.substring(7);
16+
if (!token) {
17+
return res.status(401).json({
18+
success: false,
19+
message: 'No token provided. Authorization denied.'
20+
});
21+
}
22+
23+
try {
24+
const jwtSecret = process.env.JWT_SECRET || 'mailmern-secret';
25+
const decoded = jwt.verify(token, jwtSecret);
26+
const user = await User.findById(decoded.id).select('-password');
27+
28+
if (!user) {
29+
return res.status(401).json({
30+
success: false,
31+
message: 'Token is not valid. User not found.'
32+
});
33+
}
34+
req.user = user;
35+
next();
36+
} catch (err) {
37+
if (err.name === 'TokenExpiredError') {
38+
return res.status(401).json({
39+
success: false,
40+
message: 'Token expired. Please login again.'
41+
});
42+
}
43+
return res.status(401).json({
44+
success: false,
45+
message: 'Token is not valid.'
46+
});
47+
}
48+
} catch (err) {
49+
return res.status(500).json({
50+
success: false,
51+
message: 'Server error in authentication'
52+
});
53+
}
554
};
55+
56+
module.exports = authMiddleware;
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
2-
export function errorMiddleware(err, req, res, next) {
3-
4-
const statusCode = err.statusCode || 500;
5-
const message = err.message || 'Internal Server Error';
6-
res.status(statusCode).json({
7-
success: false,
8-
message,
9-
stack: process.env.NODE_ENV === 'production' ? null : err.stack
10-
11-
});
12-
next();
13-
}
14-
1+
const errorMiddleware = (err, req, res, next) => {
2+
const statusCode = err.statusCode || 500;
3+
const message = err.message || 'Internal Server Error';
4+
5+
res.status(statusCode).json({
6+
success: false,
7+
message,
8+
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
9+
});
10+
};
11+
module.exports = { errorMiddleware };

backend/src/server.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ const userRoutes = require('./routes/userRoutes');
66
const { errorMiddleware } = require('./middlewares/errorMiddleware');
77
const chatbotRoutes = require('./routes/chatbotRoutes');
88
const emailRoutes = require('./routes/emailRoutes');
9-
const googleRoutes = require('./routes/googleRoute');
109
const trackRoutes = require('./routes/trackRoutes');
11-
const { configDotenv } = require('dotenv');
1210
const contactRoutes = require('./routes/contactRoutes');
1311
const googleRoutes = require('./routes/googleRoute');
1412
const app = express();
13+
14+
if (!process.env.JWT_SECRET) {
15+
process.env.JWT_SECRET = 'mailmern-dev-secret-key-change-in-production-' + Date.now();
16+
console.warn('WARNING: JWT_SECRET not found in environment variables. Using default development secret.');
17+
console.warn('Please set JWT_SECRET in your .env file for production use!');
18+
}
1519
app.use(
1620
cors({
1721
origin: ["http://localhost:5173"],
@@ -31,12 +35,13 @@ app.use('/api/contacts',contactRoutes);
3135
app.use("/api/google-calendar", googleRoutes);
3236
app.use('/api/track', trackRoutes);
3337

34-
const PORT = process.env.PORT || 5000;
35-
38+
// 404 handler
3639
app.use('*', (req, res) => {
3740
res.status(404).json({ success: false, message: 'Route not found' });
3841
});
3942

43+
app.use(errorMiddleware);
44+
const PORT = process.env.PORT || 5000;
4045
const start = async () => {
4146
try {
4247
await connectDB();
@@ -47,5 +52,4 @@ const start = async () => {
4752
};
4853

4954
start();
50-
app.use(errorMiddleware);
5155
module.exports = app;

backend/src/services/emailService.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ const EmailLog = require('../models/EmailLog'); // import your model
33

44
const transporter = nodemailer.createTransport({
55
host: process.env.SMTP_HOST || 'smtp.gmail.com',
6-
port: process.env.SMTP_PORT || 465,
7-
secure: true,
6+
port: parseInt(process.env.SMTP_PORT || '587'),
7+
secure: process.env.SMTP_SECURE === 'true' || false,
88
auth: {
9-
user: process.env.EMAIL,
10-
pass: process.env.PASS,
9+
user: process.env.SMTP_USER || process.env.EMAIL_USER || process.env.EMAIL,
10+
pass: process.env.SMTP_PASS || process.env.EMAIL_PASS || process.env.PASS,
1111
},
1212
});
1313

frontend/src/App.jsx

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import TemplateBuilder from "./pages/Campaign";
1515
import { AuthProvider } from "./context/AuthContext";
1616
import ForgotPassword from "./pages/Forgotpassword";
1717
import Contacts from "./pages/Contact";
18-
import BulkEmail from "./pages/BulkEmail";
18+
import BulkEmail from "./pages/BulkEmail";
19+
import ProtectedRoute from "./components/ProtectedRoute";
1920

2021
export default function App() {
2122
return (
@@ -27,14 +28,50 @@ export default function App() {
2728

2829
<Routes>
2930
<Route path="/" element={<Home />} />
30-
<Route path="/dashboard" element={<Dashboard />} />
31-
<Route path="/chatbot" element={<Chatbot />} />
3231
<Route path="/login" element={<Login />} />
3332
<Route path="/register" element={<Register />} />
34-
<Route path="/builder" element={<TemplateBuilder />} />
35-
<Route path='/forgot-password' element={<ForgotPassword/>}/>
36-
<Route path='/contacts' element={<Contacts/>}/>
37-
<Route path='/bulk-email' element={<BulkEmail/>}/>
33+
<Route path="/forgot-password" element={<ForgotPassword />} />
34+
{/* Protected Routes */}
35+
<Route
36+
path="/dashboard"
37+
element={
38+
<ProtectedRoute>
39+
<Dashboard />
40+
</ProtectedRoute>
41+
}
42+
/>
43+
<Route
44+
path="/chatbot"
45+
element={
46+
<ProtectedRoute>
47+
<Chatbot />
48+
</ProtectedRoute>
49+
}
50+
/>
51+
<Route
52+
path="/builder"
53+
element={
54+
<ProtectedRoute>
55+
<TemplateBuilder />
56+
</ProtectedRoute>
57+
}
58+
/>
59+
<Route
60+
path="/contacts"
61+
element={
62+
<ProtectedRoute>
63+
<Contacts />
64+
</ProtectedRoute>
65+
}
66+
/>
67+
<Route
68+
path="/bulk-email"
69+
element={
70+
<ProtectedRoute>
71+
<BulkEmail />
72+
</ProtectedRoute>
73+
}
74+
/>
3875
<Route path="*" element={<NotFound />} />
3976
</Routes>
4077

0 commit comments

Comments
 (0)