const nodemailer = require('nodemailer'); const express = require('express'); const path = require('path'); const { Pool } = require('pg'); const rateLimit = require('express-rate-limit'); require('dotenv').config(); const { body, validationResult } = require('express-validator'); const app = express(); const port = 3000; // Middleware to parse incoming JSON data from the frontend app.use(express.json()); // Middleware to serve static files (like index.html, styles.css, script.js) app.use(express.static(path.join(__dirname, 'public'))); // Database connection pool setup using environment variables for security const pool = new Pool({ user: process.env.DB_USER, host: process.env.DB_HOST, database: process.env.DB_DATABASE, password: process.env.DB_PASSWORD, port: process.env.DB_PORT, }); // Nodemailer transporter setup for sending emails const transporter = nodemailer.createTransport({ host: 'smtp-relay.brevo.com', port: 2525, secure: false, requireTLS: true, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS, }, }); // A simple test to check if the database connection is working pool.query('SELECT NOW()', (err, res) => { if (err) { console.error('Database connection error:', err); } else { console.log('Database connected successfully!'); } }); // Rate limiting middleware to prevent brute-force attacks const formLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 50, // Limit each IP to 50 requests per window message: 'Too many form submissions from this IP. Please try again later.' }); // Define the POST route for form submissions with rate limiting applied app.post('/submit-form', formLimiter, // Middleware for sanitation and validation [ body('firstName').trim().escape(), body('lastName').trim().escape(), body('email').isEmail().normalizeEmail(), body('organization').trim().escape(), body('phone').trim(), body('message').trim().escape(), ], async (req, res) => { // Check for validation errors from the middleware above const errors = validationResult(req); if (!errors.isEmpty()) { console.error('Validation failed:', errors.array()); return res.status(400).json({ success: false, message: 'Invalid form data.' }); } // 1. Honeypot check (first line of defense) if (req.body.url) { console.warn('Bot detected! Honeypot field was filled.'); // Respond with a success status to avoid alerting the bot return res.status(200).json({ success: true, message: 'Thank you for your submission.' }); } // Get the data from the request body const { firstName, lastName, organization, email, phone, contactMethod, message, hCaptchaResponse } = req.body; // Use a single try-catch block for all core logic try { // 2. hCaptcha verification (second line of defense) if (!hCaptchaResponse) { return res.status(400).json({ success: false, message: 'CAPTCHA token missing.' }); } const secretKey = process.env.HCAPTCHA_SECRET; const verificationUrl = 'https://hcaptcha.com/siteverify'; const verificationResponse = await fetch(verificationUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ secret: secretKey, response: hCaptchaResponse }) }); const verificationData = await verificationResponse.json(); if (!verificationData.success) { console.error('hCaptcha verification failed:', verificationData['error-codes']); return res.status(400).json({ success: false, message: 'CAPTCHA verification failed. Please try again.' }); } // 3. Insert form data into the database const result = await pool.query( `INSERT INTO submissions(first_name, last_name, organization, email, phone, contact_method, message) VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING *`, [firstName, lastName, organization, email, phone, contactMethod, message] ); console.log('Successfully saved submission to the database:', result.rows[0]); // 4. Send the email notification const mailOptions = { from: `"Contact Form" `, to: process.env.EMAIL_RCPT, subject: 'New Contact Form Submission', html: `

New Submission

Message:

${message}

`, }; const emailInfo = await transporter.sendMail(mailOptions); console.log('Message sent: %s', emailInfo.messageId); // 5. Send a success response back to the client res.status(200).json({ message: 'Form submitted successfully and saved to the database!', dataReceived: req.body }); } catch (err) { // This single catch block will handle errors from hCaptcha, the database, or nodemailer console.error('An error occurred during form submission:', err.stack); res.status(500).json({ message: 'An error occurred. Please try again.', error: err.message }); } }); // Start the server app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); });