chore: add example env file, improve documentation and code comments
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# dlseitz.dev: A Backend Demonstration
|
||||
|
||||
To learn about the front end of this two-part project, check out [**dlseitz.dev – A Frontend Demonstration**](https://git.dsnet.pro/dereklseitz/dlseitz.dev-frontend/src/branch/main/README.md).
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
@@ -1,12 +1,10 @@
|
||||
// contactController.js
|
||||
module.exports = (pool, transporter) => {
|
||||
|
||||
// The main function that handles the form submission
|
||||
const submitForm = async (req, res) => {
|
||||
const { firstName, lastName, organization, email, phone, contactMethod, message } = req.body;
|
||||
|
||||
try {
|
||||
// 1. Save submission to 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 *`,
|
||||
@@ -15,7 +13,6 @@ module.exports = (pool, transporter) => {
|
||||
|
||||
console.log('Successfully saved submission to the database:', result.rows[0]);
|
||||
|
||||
// 2. Send the email notification
|
||||
const mailOptions = {
|
||||
from: `"Contact Form" <contact@dlseitz.dev>`,
|
||||
to: process.env.EMAIL_RCPT,
|
||||
|
27
example.env
Normal file
27
example.env
Normal file
@@ -0,0 +1,27 @@
|
||||
# Example environment variables for dlseitz.dev backend
|
||||
# DO NOT PUT REAL CREDENTIALS IN HERE
|
||||
# DO NOT COMMIT ACTUAL .env FILES TO VERSION CONTROL
|
||||
|
||||
# Database-related
|
||||
#-----------------
|
||||
DB_HOST=your_database_host
|
||||
DB_PORT=5432
|
||||
DB_USER=your_database_user
|
||||
DB_PASSWORD=your_database_password
|
||||
DB_NAME=your_database_name
|
||||
|
||||
# Email-related
|
||||
#--------------
|
||||
EMAIL_HOST=smtp.yourmail.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=your_email_username
|
||||
EMAIL_PASS=your_email_password
|
||||
EMAIL_RCPT=recipient@example.com
|
||||
|
||||
# Security-related
|
||||
#-----------------
|
||||
HCAPTCHA_SECRET=your_hcaptcha_secret_key
|
||||
|
||||
# Other
|
||||
#------
|
||||
NODE_ENV=production
|
@@ -4,13 +4,13 @@ const fetch = require('node-fetch');
|
||||
|
||||
module.exports = {
|
||||
formSecurityCheck: async (req, res, next) => {
|
||||
// 1. Honeypot check (first line of defense)
|
||||
// 1. Honeypot check
|
||||
if (req.body.url) {
|
||||
console.warn('Bot detected! Honeypot field was filled.');
|
||||
return res.status(200).json({ success: true, message: 'Thank you for your submission.' });
|
||||
}
|
||||
|
||||
// 2. hCaptcha verification (second line of defense)
|
||||
// 2. hCaptcha verification
|
||||
const hCaptchaResponse = req.body.hCaptchaResponse;
|
||||
if (!hCaptchaResponse) {
|
||||
return res.status(400).json({ success: false, message: 'CAPTCHA token missing.' });
|
||||
@@ -36,7 +36,6 @@ module.exports = {
|
||||
return res.status(400).json({ success: false, message: 'CAPTCHA verification failed. Please try again.' });
|
||||
}
|
||||
|
||||
// If all checks pass, move to the next middleware or controller
|
||||
next();
|
||||
|
||||
} catch (error) {
|
||||
|
@@ -5,21 +5,18 @@ module.exports = (contactController, securityMw) => {
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
|
||||
// 🛡️ Configure rate limiting to prevent DDoS and spamming
|
||||
// Configure rate limiting to prevent spam
|
||||
const apiLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 5,
|
||||
message: "Too many requests from this IP, please try again after 15 minutes."
|
||||
});
|
||||
|
||||
// Define the route for form submissions with all middleware
|
||||
router.post('/submit-form',
|
||||
apiLimiter,
|
||||
// The security middleware is now a separate step,
|
||||
// containing both the honeypot check and hCaptcha verification.
|
||||
securityMw.formSecurityCheck,
|
||||
[
|
||||
// express-validator: sanitation and validation
|
||||
// Sanitize and validate form data
|
||||
body('firstName').trim().escape(),
|
||||
body('lastName').trim().escape(),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
@@ -27,7 +24,7 @@ module.exports = (contactController, securityMw) => {
|
||||
body('phone').trim(),
|
||||
body('message').trim().escape(),
|
||||
],
|
||||
// Middleware to handle the express-validator results
|
||||
// Handle validation results
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
@@ -36,10 +33,8 @@ module.exports = (contactController, securityMw) => {
|
||||
}
|
||||
next();
|
||||
},
|
||||
// The controller, which is the final step
|
||||
contactController.submitForm
|
||||
);
|
||||
|
||||
// Return the configured router
|
||||
return router;
|
||||
};
|
||||
|
13
server.js
13
server.js
@@ -8,20 +8,17 @@ require('dotenv').config();
|
||||
const app = express();
|
||||
const port = process.env.SERVER_PORT || 3000;
|
||||
|
||||
// add logging for troubleshooting
|
||||
app.use((req, res, next) => {
|
||||
console.log(`Incoming request: ${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// 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)
|
||||
const STATIC_DIR = process.env.STATIC_DIR || 'public'
|
||||
app.use(express.static(path.join(__dirname, STATIC_DIR)));
|
||||
|
||||
// Database connection pool setup using environment variables for security
|
||||
// Setup database connection pool using environment variables
|
||||
const pool = new Pool({
|
||||
user: process.env.DB_USER,
|
||||
host: process.env.DB_HOST,
|
||||
@@ -30,7 +27,7 @@ const pool = new Pool({
|
||||
port: process.env.DB_PORT,
|
||||
});
|
||||
|
||||
// Nodemailer transporter setup for sending emails
|
||||
// Configure Nodemailer for sending emails via Brevo relay
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: 2525,
|
||||
@@ -43,17 +40,11 @@ const transporter = nodemailer.createTransport({
|
||||
});
|
||||
|
||||
const contactController = require('./controllers/contactController')(pool, transporter);
|
||||
|
||||
// Import the security middleware
|
||||
const securityMw = require('./middleware/securityMw');
|
||||
|
||||
// Import contactRoutes and contactController, and pass in securityMw
|
||||
const contactRoutes = require('./routes/contactRoutes')(contactController, securityMw);
|
||||
|
||||
// Use contactRoutes to connect the modular router to the main app
|
||||
app.use('/api', contactRoutes);
|
||||
|
||||
// Start the server
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening at http://localhost:${port}`);
|
||||
});
|
Reference in New Issue
Block a user