Skip to content

Commit

Permalink
Some File Changes
Browse files Browse the repository at this point in the history
  • Loading branch information
PriyanshuValiya committed Oct 26, 2024
1 parent b563fe2 commit 3eddb51
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 49 deletions.
60 changes: 35 additions & 25 deletions backend/controller/contact.controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
const { z } = require("zod");
const nodemailer = require("nodemailer");
const logger = require("../utils/logger");
require("dotenv").config();

// data require form .env file : EMAIL_USER, EMAIL_PASS
const requiredEnvVars = ["EMAIL_USER", "EMAIL_PASS"];
const missingEnvVars = requiredEnvVars.filter((envVar) => !process.env[envVar]);

if (missingEnvVars.length > 0) {
throw new Error(
`Missing required environment variables: ${missingEnvVars.join(", ")}`
);
}

// Define the Zod schema for contact form validation
const contactSchema = z.object({
Expand Down Expand Up @@ -34,39 +42,41 @@ const createContactUs = async (req, res) => {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
tls: {
rejectUnauthorized: false, // Disable strict SSL verification
},
tls: {
rejectUnauthorized: false, // Disable strict SSL verification
},
});

const sanitizeInput = (str) => {
return str.replace(/[<>]/g, "").trim();
};

const mailOptions = {
from: mail,
from: `"Contact Form" <${process.env.EMAIL_USER}>`,
replyTo: sanitizeInput(mail),
to: process.env.EMAIL_USER,
subject: subject,
text: message,
subject: sanitizeInput(subject),
text: sanitizeInput(message),
};

// Send mail with defined transport object
await new Promise((resolve, reject) => {
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.error("Error occurred: " + error.message);
reject(error);
}
resolve(info);
});
// Use built-in promise interface
await transporter.sendMail(mailOptions);
} catch (err) {
logger.error("Validation failed", {
errors: validation.error.errors,
requestId: req.id,
});

res.status(200).json({
status: "success",
message: "Your contact request has been successfully received.",
});
} catch (err) {
console.error(`Error at transport: ${err}`);
res.status(500).json({
const statusCode = err.code === "EAUTH" ? 401 : 500;
const errorMessage =
process.env.NODE_ENV === "production"
? "There was an error sending your message. Please try again later."
: err.message;

res.status(statusCode).json({
status: "error",
message:
"There was an error sending your message. Please try again later.",
message: errorMessage,
requestId: req.id,
});
}
};
Expand Down
43 changes: 41 additions & 2 deletions backend/routes/contactUsRouter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
const express = require("express");
const router = express.Router();
const { createContactUs } = require("../controller/contact.controller");
const { createContactUs } = require("../controller/contact.controller");
const rateLimit = require("express-rate-limit");
const { body } = require("express-validator");

router.post("/contactus", createContactUs);
// Error handling middleware
router.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const errorType = err.type || "InternalServerError";
res.status(500).json({
status: "error",
message: err.message || "An error occurred processing your request",
type: errorType,
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
});
});

const contactFormLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per window
message: {
status: "error",
message: "Too many requests, please try again later",
},
});

router.post(
"/",
contactFormLimiter,
[
body("email").isEmail().normalizeEmail(),
body("name").trim().isLength({ min: 2 }).escape(),
body("message").trim().isLength({ min: 10, max: 1000 }).escape(),
],
async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ status: "error", errors: errors.array() });
}
await createContactUs(req, res, next);
}
);

module.exports = router;
78 changes: 66 additions & 12 deletions backend/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,42 @@ const safeRequire = (modulePath, fallbackMessage) => {
try {
return require(modulePath);
} catch (error) {
logger.error(`Error loading ${modulePath}:`, error);
return (req, res) => {
res.status(500).json({ error: fallbackMessage });
const errorDetails = {
module: modulePath.split("/").pop(),
message: error.message,
stack: process.env.NODE_ENV === "development" ? error.stack : undefined,
};
logger.error("Module loading error:", errorDetails);

// Return a pre-defined handler to avoid creating closures
return safeRequire.errorHandler(fallbackMessage);
}
};

// Pre-defined error handler to avoid creating closures
safeRequire.errorHandler = (message) => (req, res) => {
res.status(503).json({
status: "error",
message:
process.env.NODE_ENV === "production"
? message
: `Service unavailable: ${message}`,
});
};

// Safely load routers with error handling
const feedbackRouter = safeRequire("./feedbackRouter", "Feedback functionality is currently unavailable");
const contactUsRouter = safeRequire("./contactUsRouter", "Contact Us functionality is currently unavailable");
const eventRouter = safeRequire("./eventRouter", "Event functionality is currently unavailable");
const feedbackRouter = safeRequire(
"./feedbackRouter",
"Feedback functionality is currently unavailable"
);
const contactUsRouter = safeRequire(
"./contactUsRouter",
"Contact Us functionality is currently unavailable"
);
const eventRouter = safeRequire(
"./eventRouter",
"Event functionality is currently unavailable"
);

router.get("/", (req, res) => {
return res.json({
Expand All @@ -33,13 +58,42 @@ router.get("/", (req, res) => {
});
});

// Authentication routes
router.use(
"/admin",
safeRequire("./adminRouter", "Admin functionality is currently unavailable")
);
router.use(
"/user",
safeRequire("./customerRouter", "User functionality is currently unavailable")
);
router.use(
"/forgot",
safeRequire(
"./forgotRouter",
"Forgot password functionality is currently unavailable"
)
);

// Core feature routes
router.use(
"/reservation",
safeRequire(
"./reservationRouter",
"Reservation functionality is currently unavailable"
)
);
router.use("/event", eventRouter);
router.use("/admin", safeRequire("./adminRouter", "Admin functionality is currently unavailable"));

// Feedback and communication routes
router.use("/feedback", feedbackRouter);
router.use("/user", safeRequire("./customerRouter", "User functionality is currently unavailable"));
router.use("/reservation", safeRequire("./reservationRouter", "Reservation functionality is currently unavailable"));
router.use("/newsletter", safeRequire("./newsletterRoute", "Newsletter functionality is currently unavailable"));
router.use("/forgot", safeRequire("./forgotRouter", "Forgot password functionality is currently unavailable"));
router.use("/contact", contactUsRouter);
router.use("/contact", contactUsRouter);
router.use(
"/newsletter",
safeRequire(
"./newsletterRoute",
"Newsletter functionality is currently unavailable"
)
);

module.exports = router;
90 changes: 80 additions & 10 deletions frontend/src/components/Pages/ContactUs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,21 @@ const ContactUs = () => {
};

// Use an environment variable for backend URL
const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000';
const DEFAULT_URL = 'http://localhost:3000';

const API_URL = (() => {
try {
const url = new URL(import.meta.env.VITE_BACKEND_URL || DEFAULT_URL);
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('Invalid protocol');
}
return url.toString();
} catch (e) {
console.warn('Invalid VITE_BACKEND_URL, using default:', DEFAULT_URL);
return DEFAULT_URL;
}
})();

const [mail, setMail] = useState('');
const [subject, setSubject] = useState('');
const [message, setMessage] = useState('');
Expand All @@ -27,7 +41,32 @@ const ContactUs = () => {
e.preventDefault();

// Basic client-side validation for security
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const emailRegex =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

const sanitizeInput = (input) => {
return input.replace(/[<>]/g, '');
};

// Add to state
const [lastSubmissionTime, setLastSubmissionTime] = useState(0);

if (!emailRegex.test(mail)) {
setError('Please enter a valid email address.');
return;
}

// Rate limiting
const now = Date.now();
if (now - lastSubmissionTime < 60000) {
// 1 minute
setError('Please wait before submitting again.');
return;
}

// Sanitize inputs before sending
const sanitizedSubject = sanitizeInput(subject);
const sanitizedMessage = sanitizeInput(message);

if (!mail || !subject || !message) {
setError('All fields are required.');
Expand All @@ -43,7 +82,7 @@ const ContactUs = () => {
setError('Subject must be less than 100 characters.');
return;
}

if (message.length > 1000) {
setError('Message must be less than 1000 characters.');
return;
Expand All @@ -52,14 +91,45 @@ const ContactUs = () => {
setError(null);
setIsLoading(true);

const fetchWithTimeout = async (url, options, timeout = 5000) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
throw error;
}
};
const retryWithBackoff = async (fn, retries = 3, backoff = 300) => {
try {
return await fn();
} catch (error) {
if (retries === 0) throw error;
await new Promise((resolve) => setTimeout(resolve, backoff));
return retryWithBackoff(fn, retries - 1, backoff * 2);
}
};

try {
const response = await fetch(`${API_URL}/api/contact/contactus`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ mail, subject, message }),
});
const response = await retryWithBackoff(() =>
fetchWithTimeout(`${API_URL}/api/contact/contactus`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mail,
subject: sanitizedSubject,
message: sanitizedMessage,
}),
})
);

if (!response.ok) {
throw new Error(`Network response was not ok: ${response.status}`);
Expand Down

0 comments on commit 3eddb51

Please sign in to comment.