120 lines
3.4 KiB
TypeScript
120 lines
3.4 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import nodemailer from 'nodemailer'
|
|
import { rateLimit } from '@/lib/rate-limit'
|
|
|
|
interface FormData {
|
|
name: string
|
|
email: string
|
|
service: string
|
|
message: string
|
|
}
|
|
|
|
const validateEmail = (email: string): boolean => {
|
|
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
return pattern.test(email)
|
|
}
|
|
|
|
const validateInput = (data: FormData): { isValid: boolean; error?: string } => {
|
|
if (!data.name || data.name.length < 2 || data.name.length > 50) {
|
|
return { isValid: false, error: 'Invalid name length' }
|
|
}
|
|
|
|
if (!data.email || !validateEmail(data.email)) {
|
|
return { isValid: false, error: 'Invalid email format' }
|
|
}
|
|
|
|
if (!data.service) {
|
|
return { isValid: false, error: 'Service selection required' }
|
|
}
|
|
|
|
if (!data.message || data.message.length < 10 || data.message.length > 1000) {
|
|
return { isValid: false, error: 'Invalid message length' }
|
|
}
|
|
|
|
return { isValid: true }
|
|
}
|
|
|
|
const sanitizeInput = (input: string): string => {
|
|
return input
|
|
.trim()
|
|
.replace(/[<>]/g, '') // Basic XSS prevention
|
|
.slice(0, 1000) // Length limitation
|
|
}
|
|
|
|
export async function POST(req: Request) {
|
|
try {
|
|
// Rate limiting check
|
|
const rateLimitResult = await rateLimit()
|
|
if (!rateLimitResult.success) {
|
|
return NextResponse.json(
|
|
{ success: false, message: 'Too many requests. Please try again later.' },
|
|
{ status: 429 }
|
|
)
|
|
}
|
|
|
|
const data: FormData = await req.json()
|
|
|
|
// Input validation
|
|
const validation = validateInput(data)
|
|
if (!validation.isValid) {
|
|
return NextResponse.json(
|
|
{ success: false, message: validation.error },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
// Sanitize inputs
|
|
const sanitizedData = {
|
|
name: sanitizeInput(data.name),
|
|
email: sanitizeInput(data.email),
|
|
service: sanitizeInput(data.service),
|
|
message: sanitizeInput(data.message)
|
|
}
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
host: process.env.SMTP_HOST,
|
|
port: Number(process.env.SMTP_PORT),
|
|
secure: process.env.SMTP_SECURE === 'true',
|
|
requireTLS: true,
|
|
auth: {
|
|
user: process.env.GOOGLE_EMAIL,
|
|
pass: process.env.GOOGLE_APP_PASSWORD,
|
|
},
|
|
})
|
|
|
|
const mailOptions = {
|
|
from: {
|
|
name: 'NO REPLY',
|
|
address: process.env.GOOGLE_EMAIL!
|
|
},
|
|
to: process.env.RECIPIENT_EMAIL!,
|
|
subject: `New Contact Form Submission - ${sanitizedData.service}`,
|
|
html: `
|
|
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
|
<h2 style="color: #2563eb;">New Contact Form Submission</h2>
|
|
<div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px;">
|
|
<p><strong>Name:</strong> ${sanitizedData.name}</p>
|
|
<p><strong>Email:</strong> ${sanitizedData.email}</p>
|
|
<p><strong>Service:</strong> ${sanitizedData.service}</p>
|
|
<p><strong>Message:</strong></p>
|
|
<p style="white-space: pre-wrap;">${sanitizedData.message}</p>
|
|
</div>
|
|
</div>
|
|
`,
|
|
}
|
|
|
|
await transporter.sendMail(mailOptions)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Email sent successfully'
|
|
})
|
|
|
|
} catch (error) {
|
|
console.error('Error sending email:', error)
|
|
return NextResponse.json(
|
|
{ success: false, message: 'Failed to send email' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|