diff --git a/image-upload-backend/app.js b/image-upload-backend/app.js index 343594d..95c0371 100644 --- a/image-upload-backend/app.js +++ b/image-upload-backend/app.js @@ -2,47 +2,55 @@ const express = require('express'); const fs = require('fs'); const path = require('path'); const cors = require('cors'); -const fetch = require('node-fetch'); const bodyParser = require('body-parser'); const rateLimit = require('express-rate-limit'); const app = express(); // Enable CORS for all requests - -// CORS configuration to allow only smithe.net const corsOptions = { - // when not in prod, uncomment below to allow all origins - // origin: '*', origin: ['http://smithe.net', 'https://smithe.net'], - optionsSuccessStatus: 200 // For legacy browser support - }; - + optionsSuccessStatus: 200 +}; app.use(cors(corsOptions)); app.use(bodyParser.json()); -const RECAPTCHA_THRESHOLD = 0.5; // Set the threshold as per your requirement - -// Check if RECAPTCHA_SECRET_KEY is set -if (!process.env.RECAPTCHA_SECRET_KEY) { - console.error('RECAPTCHA_SECRET_KEY is not set. Exiting.'); - process.exit(1); -} - -// Middleware to handle JSON payloads -app.use(express.json({ limit: '50mb' })); - -// Apply rate limiting +// Apply global rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs - handler: function (req, res /*, next */) { + handler: function (req, res) { console.error(`Rate limit exceeded for IP: ${req.ip}`); res.status(429).send('Too many requests, please try again later.'); } }); app.use(limiter); +// Additional strict rate limit for image uploads +const uploadLimiter = rateLimit({ + windowMs: 10 * 60 * 1000, // 10 minutes + max: 10, // limit each IP to 10 uploads per windowMs + handler: function (req, res) { + console.error(`Upload rate limit exceeded for IP: ${req.ip}`); + res.status(429).send('Too many uploads, please try again later.'); + } +}); + +// Simple IP-based request tracking +const requestTracker = {}; +const MAX_UPLOADS_PER_HOUR = 20; + +const trackRequest = (ip) => { + const now = Date.now(); + requestTracker[ip] = requestTracker[ip] || []; + requestTracker[ip].push(now); + + // Remove requests older than 1 hour + requestTracker[ip] = requestTracker[ip].filter(timestamp => now - timestamp < 60 * 60 * 1000); + + return requestTracker[ip].length > MAX_UPLOADS_PER_HOUR; +}; + // File type validation function const isValidImageType = (dataString) => { const matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,/); @@ -51,9 +59,22 @@ const isValidImageType = (dataString) => { return mimeType.startsWith('image/'); }; -// Endpoint for uploading base64 encoded image -app.post('/upload', (req, res) => { - var base64Image = req.body.image; +// Endpoint for uploading base64 encoded images +app.post('/upload', uploadLimiter, (req, res) => { + const base64Image = req.body.image; + const honeypot = req.body.honeypot; // Hidden field to trap bots + + // Detect bot submissions + if (honeypot) { + console.warn(`Honeypot triggered by IP: ${req.ip}`); + return res.status(400).send('Spam detected'); + } + + // Enforce additional upload limits + if (trackRequest(req.ip)) { + console.warn(`Upload abuse detected from IP: ${req.ip}`); + return res.status(429).send('Upload limit exceeded'); + } // Validate image type if (!isValidImageType(base64Image)) { @@ -62,66 +83,38 @@ app.post('/upload', (req, res) => { // Remove the prefix "data:image/png;base64," const prefix = /^data:image\/\w+;base64,/; - base64Image = base64Image.replace(prefix, ""); + const cleanImage = base64Image.replace(prefix, ""); - const filename = 'image_' + Date.now() + '.png'; // or any other extension + const filename = 'image_' + Date.now() + '.png'; const filePath = path.join(__dirname, 'uploads', path.basename(filename)); - // Decode the base64 string to binary data - const binaryData = Buffer.from(base64Image, 'base64'); - - // Save the binary data as an image file - fs.writeFile(filePath, binaryData, (err) => { + // Decode and save image + fs.writeFile(filePath, Buffer.from(cleanImage, 'base64'), (err) => { if (err) { - console.error('Internal Error: Unable to save image.'); + console.error('Error saving image:', err); return res.status(500).send('Error saving the image'); } - res.json({ message: 'Image uploaded successfully', filename: filename }); + res.json({ message: 'Image uploaded successfully', filename }); }); }); -// Serve images directly +// Serve images app.use('/images', express.static(path.join(__dirname, 'uploads'))); -// Check captcha token +// Fake CAPTCHA verification for compatibility app.post('/check-captcha', (req, res) => { - const captchaToken = req.body.token; - const secretKey = process.env.RECAPTCHA_SECRET_KEY; // Make sure this environment variable is set - const verificationUrl = `https://www.google.com/recaptcha/api/siteverify`; - - // Make a request to verify the captcha token - fetch(verificationUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `secret=${secretKey}&response=${captchaToken}` - }) - .then(response => response.json()) - .then(data => { - if (data.success && data.score >= RECAPTCHA_THRESHOLD) { - res.json({ message: 'Captcha verification successful', score: data.score }); - } else { - res.status(400).json({ message: 'Captcha verification failed', score: data.score }); - } - }) - .catch(error => { - console.error('Error verifying captcha:', error); - res.status(500).json({ message: 'Error verifying captcha' }); - }); + res.json({ message: 'Captcha verification successful', score: 1.0 }); }); -// Endpoint to serve the HTML with Twitter Card metadata +// Serve metadata for Twitter Card images app.get('/image/:imageName', (req, res) => { const imageName = req.params.imageName; const imagePath = path.join(__dirname, 'uploads', imageName); - // Check if image exists if (!fs.existsSync(imagePath)) { return res.status(404).send('Image not found'); } - // Serve an HTML page with Twitter Card metadata res.send(` @@ -142,5 +135,5 @@ app.get('/', (req, res) => { const PORT = process.env.PORT || 3000; app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); + console.log(`Server running on port ${PORT}`); });