Skip to content

Commit

Permalink
[image-upload-backend] update recaptcha stuff
Browse files Browse the repository at this point in the history
Signed-off-by: danbugs <[email protected]>
  • Loading branch information
danbugs committed Jan 30, 2025
1 parent d4a985d commit b4d0546
Showing 1 changed file with 56 additions and 63 deletions.
119 changes: 56 additions & 63 deletions image-upload-backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,/);
Expand All @@ -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)) {
Expand All @@ -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(`
<html>
<head>
Expand All @@ -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}`);
});

0 comments on commit b4d0546

Please sign in to comment.