Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ SMTP_PASS=your_app_password
EMAIL_FROM="MailMERN no-reply@example.com"
EMAIL_TEST_TO=reciever@example.com
NODE_ENV=development
JWT_SECRET=mailmern2323
85 changes: 54 additions & 31 deletions backend/src/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ exports.login = async (req, res) => {
return res.status(401).json({ success: false, message: "Invalid email or password" });
}

const jwtSecret = process.env.JWT_SECRET || 'mailmern-secret';
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
jwtSecret,
{ expiresIn: "1d" }
);

Expand All @@ -55,66 +56,88 @@ exports.login = async (req, res) => {
res.status(400).json({ success: false, error: err.message });
}
};
exports.sendOtp = async (req,res) => {
exports.sendOtp = async (req, res) => {
try {
const {email} = req.body;
const user = await User.findOne({email});
if (!user) return res.status(400).json({message:"User does not exist."});
const { email } = req.body;
if (!email) {
return res.status(400).json({ success: false, message: "Email is required" });
}
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ success: false, message: "User does not exist." });
}

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

await sendEmail


await sendEmail({ to: email, otp });


({ to: email, otp }); // ✅ Correct usage
return res.status(200).json({message:"OTP sent successfully"});
return res.status(200).json({ success: true, message: "OTP sent successfully" });
} catch (error) {
return res.status(500).json({message: `send otp error ${error}`});
}
console.error("Send OTP error:", error);
return res.status(500).json({ success: false, message: `Failed to send OTP: ${error.message}` });
}
};


exports.verifyOtp = async (req,res) => {
exports.verifyOtp = async (req, res) => {
try {
const {email, otp} = req.body;
const user = await User.findOne({email});
if (!user || user.resetOtp !== otp || user.otpExpires < Date.now()) {
return res.status(400).json({message:"Invalid/expired OTP"});
const { email, otp } = req.body;
if (!email || !otp) {
return res.status(400).json({ success: false, message: "Email and OTP are required" });
}
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ success: false, message: "User does not exist" });
}
if (!user.resetOtp || user.resetOtp !== otp) {
return res.status(400).json({ success: false, message: "Invalid OTP" });
}
if (user.otpExpires < Date.now()) {
return res.status(400).json({ success: false, message: "OTP has expired" });
}

user.isOtpVerified = true;
user.resetOtp = undefined;
user.otpExpires = undefined;
await user.save();

return res.status(200).json({message:"OTP verified successfully"});
return res.status(200).json({ success: true, message: "OTP verified successfully" });
} catch (error) {
return res.status(500).json({message: `verify otp error ${error}`});
console.error("Verify OTP error:", error);
return res.status(500).json({ success: false, message: `Failed to verify OTP: ${error.message}` });
}
};

exports.resetPassword = async (req,res) => {
exports.resetPassword = async (req, res) => {
try {
const {email, newPassword} = req.body;
const user = await User.findOne({email});
if (!user || !user.isOtpVerified) {
return res.status(400).json({message:"OTP verification required"});
const { email, newPassword } = req.body;
if (!email || !newPassword) {
return res.status(400).json({ success: false, message: "Email and new password are required" });
}
if (newPassword.length < 6) {
return res.status(400).json({ success: false, message: "Password must be at least 6 characters" });
}
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ success: false, message: "User does not exist" });
}
if (!user.isOtpVerified) {
return res.status(400).json({ success: false, message: "OTP verification required. Please verify OTP first." });
}

const hashedPassword = await bcrypt.hash(newPassword, 10);
user.password = hashedPassword;
user.isOtpVerified = false; // reset verification
user.resetOtp = undefined;
user.otpExpires = undefined;
await user.save();

return res.status(200).json({message:"Password reset successfully"});
return res.status(200).json({ success: true, message: "Password reset successfully" });
} catch (error) {
return res.status(500).json({message: `reset password error ${error}`});
console.error("Reset password error:", error);
return res.status(500).json({ success: false, message: `Failed to reset password: ${error.message}` });
}
};

};
59 changes: 55 additions & 4 deletions backend/src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,56 @@
// Simple auth middleware placeholder
module.exports = (req, res, next) => {
// In real app, validate JWT or session here
next();
const jwt = require('jsonwebtoken');
const User = require('../models/User');

//JWT Auth Middleware
const authMiddleware = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;

if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: 'No token provided. Authorization denied.'
});
}
const token = authHeader.substring(7);
if (!token) {
return res.status(401).json({
success: false,
message: 'No token provided. Authorization denied.'
});
}

try {
const jwtSecret = process.env.JWT_SECRET || 'mailmern-secret';
const decoded = jwt.verify(token, jwtSecret);
const user = await User.findById(decoded.id).select('-password');

if (!user) {
return res.status(401).json({
success: false,
message: 'Token is not valid. User not found.'
});
}
req.user = user;
next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: 'Token expired. Please login again.'
});
}
return res.status(401).json({
success: false,
message: 'Token is not valid.'
});
}
} catch (err) {
return res.status(500).json({
success: false,
message: 'Server error in authentication'
});
}
};

module.exports = authMiddleware;
25 changes: 11 additions & 14 deletions backend/src/middlewares/errorMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@

export function errorMiddleware(err, req, res, next) {

const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
success: false,
message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack

});
next();
}

const errorMiddleware = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';

res.status(statusCode).json({
success: false,
message,
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
});
};
module.exports = { errorMiddleware };
14 changes: 9 additions & 5 deletions backend/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ const userRoutes = require('./routes/userRoutes');
const { errorMiddleware } = require('./middlewares/errorMiddleware');
const chatbotRoutes = require('./routes/chatbotRoutes');
const emailRoutes = require('./routes/emailRoutes');
const googleRoutes = require('./routes/googleRoute');
const trackRoutes = require('./routes/trackRoutes');
const { configDotenv } = require('dotenv');
const contactRoutes = require('./routes/contactRoutes');
const googleRoutes = require('./routes/googleRoute');
const app = express();

if (!process.env.JWT_SECRET) {
process.env.JWT_SECRET = 'mailmern-dev-secret-key-change-in-production-' + Date.now();
console.warn('WARNING: JWT_SECRET not found in environment variables. Using default development secret.');
console.warn('Please set JWT_SECRET in your .env file for production use!');
}
app.use(
cors({
origin: ["http://localhost:5173"],
Expand All @@ -31,12 +35,13 @@ app.use('/api/contacts',contactRoutes);
app.use("/api/google-calendar", googleRoutes);
app.use('/api/track', trackRoutes);

const PORT = process.env.PORT || 5000;

// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ success: false, message: 'Route not found' });
});

app.use(errorMiddleware);
const PORT = process.env.PORT || 5000;
const start = async () => {
try {
await connectDB();
Expand All @@ -47,5 +52,4 @@ const start = async () => {
};

start();
app.use(errorMiddleware);
module.exports = app;
8 changes: 4 additions & 4 deletions backend/src/services/emailService.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ const EmailLog = require('../models/EmailLog'); // import your model

const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST || 'smtp.gmail.com',
port: process.env.SMTP_PORT || 465,
secure: true,
port: parseInt(process.env.SMTP_PORT || '587'),
secure: process.env.SMTP_SECURE === 'true' || false,
auth: {
user: process.env.EMAIL,
pass: process.env.PASS,
user: process.env.SMTP_USER || process.env.EMAIL_USER || process.env.EMAIL,
pass: process.env.SMTP_PASS || process.env.EMAIL_PASS || process.env.PASS,
},
});

Expand Down
51 changes: 44 additions & 7 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import TemplateBuilder from "./pages/Campaign";
import { AuthProvider } from "./context/AuthContext";
import ForgotPassword from "./pages/Forgotpassword";
import Contacts from "./pages/Contact";
import BulkEmail from "./pages/BulkEmail";
import BulkEmail from "./pages/BulkEmail";
import ProtectedRoute from "./components/ProtectedRoute";

export default function App() {
return (
Expand All @@ -27,14 +28,50 @@ export default function App() {

<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/chatbot" element={<Chatbot />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/builder" element={<TemplateBuilder />} />
<Route path='/forgot-password' element={<ForgotPassword/>}/>
<Route path='/contacts' element={<Contacts/>}/>
<Route path='/bulk-email' element={<BulkEmail/>}/>
<Route path="/forgot-password" element={<ForgotPassword />} />
{/* Protected Routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/chatbot"
element={
<ProtectedRoute>
<Chatbot />
</ProtectedRoute>
}
/>
<Route
path="/builder"
element={
<ProtectedRoute>
<TemplateBuilder />
</ProtectedRoute>
}
/>
<Route
path="/contacts"
element={
<ProtectedRoute>
<Contacts />
</ProtectedRoute>
}
/>
<Route
path="/bulk-email"
element={
<ProtectedRoute>
<BulkEmail />
</ProtectedRoute>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>

Expand Down
Loading
Loading