pasteboard/server.js
Max W. c92c85b3f0 Update server.js
- Add rate limiting
- Add paste size log
2025-04-12 01:24:09 +02:00

110 lines
3.5 KiB
JavaScript

const express = require('express');
const path = require('path');
const PORT = process.env.PORT || 3000;
const PASTE_TTL_HOURS = 3;
const PASTE_TTL_MS = PASTE_TTL_HOURS * 60 * 60 * 1000;
const pastes = new Map();
const ipLastPaste = new Map(); // Tracks the last paste time for each IP
const RATE_LIMIT_MS = 10 * 1000; // 10 seconds rate limit
const IP_CLEANUP_INTERVAL = 30 * 60 * 1000; // Clean up IP records every 30 minutes
const app = express();
app.use(express.text({ limit: '10mb', type: 'text/plain' }));
app.use(express.json());
app.use(express.static('static'));
function generateUniqueId() {
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).substring(2, 10);
return `${timestamp}-${randomPart}`;
}
app.post('/api/pastes', (req, res) => {
const content = req.body;
const clientIp = req.ip;
const now = Date.now();
// Check rate limit
if (ipLastPaste.has(clientIp)) {
const lastPasteTime = ipLastPaste.get(clientIp);
const timeElapsed = now - lastPasteTime;
if (timeElapsed < RATE_LIMIT_MS) {
return res.status(429).json({
error: 'Rate limit exceeded. Please wait before creating another paste.',
retryAfter: Math.ceil((RATE_LIMIT_MS - timeElapsed) / 1000)
});
}
}
if (!content || typeof content !== 'string' || content.trim() === '') {
return res.status(400).json({ error: 'Paste content cannot be empty.' });
}
// Calculate paste size in bytes
const pasteSize = Buffer.byteLength(content, 'utf8');
const sizeInKB = (pasteSize / 1024).toFixed(2);
const id = generateUniqueId();
pastes.set(id, content);
ipLastPaste.set(clientIp, now);
console.log(`[${new Date().toISOString()}] Paste created with ID: ${id} | Size: ${sizeInKB} KB (${pasteSize} bytes)`);
setTimeout(() => {
if (pastes.has(id)) {
pastes.delete(id);
console.log(`[${new Date().toISOString()}] Paste expired and deleted: ${id}`);
}
}, PASTE_TTL_MS);
res.status(201).json({ id: id });
});
app.get('/api/pastes/:id', (req, res) => {
const id = req.params.id;
if (pastes.has(id)) {
const content = pastes.get(id);
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.status(200).send(content);
} else {
res.status(404).json({ error: 'Paste not found or has expired.' });
}
});
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Periodically clean up the IP tracking map to prevent memory leak
setInterval(() => {
const now = Date.now();
let cleanupCount = 0;
// Remove IPs that haven't posted in the last hour (much longer than rate limit)
ipLastPaste.forEach((timestamp, ip) => {
if (now - timestamp > RATE_LIMIT_MS * 360) { // 1 hour = 360 * 10 seconds
ipLastPaste.delete(ip);
cleanupCount++;
}
});
if (cleanupCount > 0) {
console.log(`[${new Date().toISOString()}] Cleaned up ${cleanupCount} IP records from rate limiter`);
}
}, IP_CLEANUP_INTERVAL);
app.listen(PORT, () => {
console.log(`Paste server listening on port ${PORT}`);
console.log(`Pastes will be stored for ${PASTE_TTL_HOURS} hours.`);
console.log(`Open http://localhost:${PORT} in your browser to use the application`);
});
process.on('SIGINT', () => {
console.log('\nShutting down server...');
process.exit(0);
});