diff --git a/netlify/functions/contact.js b/netlify/functions/contact.js
index 9e6de2f..2ef9f10 100644
--- a/netlify/functions/contact.js
+++ b/netlify/functions/contact.js
@@ -4,6 +4,15 @@ import nodemailer from 'nodemailer';
const rateLimitMap = new Map();
const RATE_LIMIT_WINDOW = 60000; // 1 minute
+function escapeHtml(str) {
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
export async function handler(event) {
// Only allow POST
if (event.httpMethod !== 'POST') {
@@ -45,7 +54,7 @@ export async function handler(event) {
try {
const { name, email, subject, message } = JSON.parse(event.body);
- if (!name || !email || !subject || !message) {
+ if (!name?.trim() || !email?.trim() || !subject?.trim() || !message?.trim()) {
return {
statusCode: 400,
headers,
@@ -53,6 +62,20 @@ export async function handler(event) {
};
}
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(email)) {
+ return {
+ statusCode: 400,
+ headers,
+ body: JSON.stringify({ ok: false, error: 'Invalid email address' }),
+ };
+ }
+
+ const safeName = escapeHtml(name.trim());
+ const safeEmail = escapeHtml(email.trim());
+ const safeSubject = escapeHtml(subject.trim());
+ const safeMessage = escapeHtml(message.trim()).replace(/\n/g, '
');
+
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: +process.env.SMTP_PORT,
@@ -68,13 +91,13 @@ export async function handler(event) {
from: `Portfolio Contact <${process.env.SMTP_USER}>`,
to: process.env.RECIPIENT,
replyTo: email,
- subject: `[Portfolio] ${subject} (from ${name})`,
+ subject: `[Portfolio] ${safeName} - ${safeSubject}`,
html: `
Name: ${name}
-Email: ${email}
-Subject: ${subject}
-Message:
${message.replace(/\n/g, '
')}
Name: ${safeName}
+Email: ${safeEmail}
+Subject: ${safeSubject}
+Message:
${safeMessage}
Dear ${name},
+Dear ${safeName},
Thank you for taking the time to reach out through my portfolio website. I have successfully received your message and truly appreciate your interest.
Your Message Details:
-Subject: ${subject}
+Subject: ${safeSubject}
I make it a priority to respond to all inquiries promptly and will get back to you as soon as possible, typically within 24-48 hours.
diff --git a/project/server/index.js b/project/server/index.js index 2c486e0..cad0a4b 100644 --- a/project/server/index.js +++ b/project/server/index.js @@ -9,6 +9,15 @@ const app = express(); app.use(cors()); app.use(express.json()); +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: +process.env.SMTP_PORT, @@ -21,21 +30,36 @@ const transporter = nodemailer.createTransport({ app.post('/api/contact', async (req, res) => { const { name, email, subject, message } = req.body; + + if (!name?.trim() || !email?.trim() || !subject?.trim() || !message?.trim()) { + return res.status(400).json({ ok: false, error: 'All fields are required' }); + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ ok: false, error: 'Invalid email address' }); + } + + const safeName = escapeHtml(name.trim()); + const safeEmail = escapeHtml(email.trim()); + const safeSubject = escapeHtml(subject.trim()); + const safeMessage = escapeHtml(message.trim()).replace(/\n/g, 'Name: ${name}
-Email: ${email}
-Message:
${message.replace(/\n/g, '
')}
Name: ${safeName}
+Email: ${safeEmail}
+Message:
${safeMessage}
Name: ${name}
-Email: ${email}
-Message:
${message.replace(/\n/g, '
')}
Name: ${safeName}
+Email: ${safeEmail}
+Message:
${safeMessage}
Dear ${name},
+Dear ${safeName},
Thank you for taking the time to reach out through my portfolio website. I have successfully received your message and truly appreciate your interest.
Your Message Details:
-Subject: ${subject}
+Subject: ${safeSubject}
I make it a priority to respond to all inquiries promptly and will get back to you as soon as possible, typically within 24-48 hours.
@@ -82,7 +106,7 @@ app.post('/api/contact', async (req, res) => { res.json({ ok: true }); } catch (error) { console.error(error); - res.status(500).json({ ok: false, error: error.message }); + res.status(500).json({ ok: false, error: 'Failed to send email. Please try again later.' }); } });