265 lines
7.7 KiB
TypeScript
265 lines
7.7 KiB
TypeScript
import type { APIRoute } from 'astro';
|
|
import {
|
|
generateCsrfToken,
|
|
validateCsrfToken,
|
|
checkRateLimit,
|
|
sendAdminNotification,
|
|
sendUserConfirmation
|
|
} from '../../utils/email-handler';
|
|
|
|
// Enhanced email validation with more comprehensive regex
|
|
const isValidEmail = (email: string): boolean => {
|
|
// Simpler regex to avoid escape character issues
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
};
|
|
|
|
// Enhanced spam protection - check for common spam patterns and characteristics
|
|
const isSpam = (content: string, name: string, email: string): boolean => {
|
|
// Convert to lowercase for case-insensitive matching
|
|
const lowerContent = content.toLowerCase();
|
|
const lowerName = name.toLowerCase();
|
|
const lowerEmail = email.toLowerCase();
|
|
|
|
// Common spam keywords
|
|
const spamPatterns = [
|
|
'viagra', 'cialis', 'casino', 'lottery', 'prize', 'winner',
|
|
'free money', 'buy now', 'click here', 'earn money', 'make money',
|
|
'investment opportunity', 'bitcoin', 'cryptocurrency', 'forex',
|
|
'weight loss', 'diet pill', 'enlargement', 'cheap medication'
|
|
];
|
|
|
|
// Check for spam keywords in content
|
|
if (spamPatterns.some(pattern => lowerContent.includes(pattern))) {
|
|
return true;
|
|
}
|
|
|
|
// Check for spam keywords in name or email
|
|
if (spamPatterns.some(pattern => lowerName.includes(pattern) || lowerEmail.includes(pattern))) {
|
|
return true;
|
|
}
|
|
|
|
// Check for excessive capitalization (shouting)
|
|
const uppercaseRatio = (content.match(/[A-Z]/g) || []).length / content.length;
|
|
if (uppercaseRatio > 0.5 && content.length > 20) {
|
|
return true;
|
|
}
|
|
|
|
// Check for excessive special characters
|
|
const specialChars = "!@#$%^&*()_+-=[]{}\\|;:'\",.<>/?";
|
|
let specialCharCount = 0;
|
|
|
|
for (let i = 0; i < content.length; i++) {
|
|
if (specialChars.includes(content[i])) {
|
|
specialCharCount++;
|
|
}
|
|
}
|
|
|
|
const specialCharRatio = specialCharCount / content.length;
|
|
if (specialCharRatio > 0.3 && content.length > 20) {
|
|
return true;
|
|
}
|
|
|
|
// Check for excessive URLs - count http:// and https:// occurrences
|
|
const urlCount = content.split('http').length - 1;
|
|
if (urlCount > 2) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// GET handler for CSRF token generation and API testing
|
|
export const GET: APIRoute = async ({ request }) => {
|
|
const url = new URL(request.url);
|
|
const csrfRequested = url.searchParams.get('csrf') === 'true';
|
|
|
|
if (csrfRequested) {
|
|
// Generate and return a CSRF token
|
|
const csrfToken = generateCsrfToken();
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
csrfToken
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Default response for GET requests
|
|
return new Response(
|
|
JSON.stringify({
|
|
message: 'Contact API endpoint is working. Please use POST to submit the form.'
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
export const POST: APIRoute = async ({ request, clientAddress }) => {
|
|
try {
|
|
console.log('Contact form submission received');
|
|
|
|
// Get client IP address for rate limiting
|
|
const ipAddress = clientAddress || '0.0.0.0';
|
|
console.log('Client IP:', ipAddress);
|
|
|
|
// Check rate limit
|
|
const rateLimitCheck = await checkRateLimit(ipAddress);
|
|
console.log('Rate limit check:', rateLimitCheck);
|
|
|
|
if (rateLimitCheck.limited) {
|
|
console.log('Rate limit exceeded');
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
errors: {
|
|
rateLimit: rateLimitCheck.message
|
|
}
|
|
}),
|
|
{
|
|
status: 429,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Retry-After': '3600'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Get form data
|
|
const formData = await request.formData();
|
|
console.log('Form data received');
|
|
|
|
// Log all form data keys
|
|
console.log('Form data keys:', [...formData.keys()]);
|
|
|
|
const name = formData.get('name')?.toString() || '';
|
|
const email = formData.get('email')?.toString() || '';
|
|
const message = formData.get('message')?.toString() || '';
|
|
const disclaimer = formData.get('disclaimer')?.toString() === 'on';
|
|
const csrfToken = formData.get('csrf_token')?.toString() || '';
|
|
|
|
console.log('Form data values:', { name, email, messageLength: message.length, disclaimer, csrfToken: csrfToken ? 'present' : 'missing' });
|
|
|
|
// Get user agent for logging and spam detection
|
|
const userAgent = request.headers.get('user-agent') || 'Unknown';
|
|
|
|
// Validate form data
|
|
const errors: Record<string, string> = {};
|
|
|
|
// Validate CSRF token
|
|
if (!validateCsrfToken(csrfToken)) {
|
|
errors.csrf = 'Invalid or expired security token. Please refresh the page and try again.';
|
|
}
|
|
|
|
if (!name) {
|
|
errors.name = 'Please enter your name';
|
|
} else if (name.length < 2) {
|
|
errors.name = 'Your name must be at least 2 characters long';
|
|
}
|
|
|
|
if (!email) {
|
|
errors.email = 'Please enter your email address';
|
|
} else if (!isValidEmail(email)) {
|
|
errors.email = 'Please enter a valid email address (e.g., name@example.com)';
|
|
}
|
|
|
|
if (!message) {
|
|
errors.message = 'Please enter your message';
|
|
} else if (message.length < 10) {
|
|
errors.message = 'Your message must be at least 10 characters long';
|
|
}
|
|
|
|
if (!disclaimer) {
|
|
errors.disclaimer = 'Please check the required consent box before submitting';
|
|
}
|
|
|
|
// Check for spam
|
|
if (isSpam(message, name, email)) {
|
|
errors.spam = 'Your message was flagged as potential spam. Please revise your message and try again.';
|
|
}
|
|
|
|
// If there are validation errors, return them
|
|
if (Object.keys(errors).length > 0) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
errors
|
|
}),
|
|
{
|
|
status: 400,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Send emails
|
|
console.log('Attempting to send admin notification email');
|
|
const adminEmailSent = await sendAdminNotification(name, email, message, ipAddress, userAgent);
|
|
console.log('Admin email sent result:', adminEmailSent);
|
|
|
|
console.log('Attempting to send user confirmation email');
|
|
const userEmailSent = await sendUserConfirmation(name, email, message);
|
|
console.log('User email sent result:', userEmailSent);
|
|
|
|
// Check if emails were sent successfully
|
|
if (!adminEmailSent || !userEmailSent) {
|
|
console.error('Failed to send one or more emails:', { adminEmailSent, userEmailSent });
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
message: 'There was an issue sending your message. Please try again later.'
|
|
}),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Return success response
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: true,
|
|
message: 'Your message has been sent successfully. We will get back to you soon!'
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
|
|
} catch (error) {
|
|
console.error('Error processing contact form:', error);
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
message: 'An error occurred while processing your request. Please try again later.'
|
|
}),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}; |