Update server.js
- Add rate limiting - Add paste size log
This commit is contained in:
parent
31a5922cf6
commit
c92c85b3f0
93
server.js
93
server.js
@ -1,58 +1,59 @@
|
|||||||
// Import necessary modules
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
// --- Configuration ---
|
const PORT = process.env.PORT || 3000;
|
||||||
const PORT = process.env.PORT || 3000; // Use environment variable or default to 3000
|
const PASTE_TTL_HOURS = 3;
|
||||||
const PASTE_TTL_HOURS = 2;
|
const PASTE_TTL_MS = PASTE_TTL_HOURS * 60 * 60 * 1000;
|
||||||
const PASTE_TTL_MS = PASTE_TTL_HOURS * 60 * 60 * 1000; // Time-to-live in milliseconds
|
|
||||||
|
|
||||||
// --- In-Memory Storage ---
|
const pastes = new Map();
|
||||||
// Using a Map for efficient key-based storage and deletion
|
const ipLastPaste = new Map(); // Tracks the last paste time for each IP
|
||||||
const pastes = new Map(); // Stores { id: content }
|
const RATE_LIMIT_MS = 10 * 1000; // 10 seconds rate limit
|
||||||
|
const IP_CLEANUP_INTERVAL = 30 * 60 * 1000; // Clean up IP records every 30 minutes
|
||||||
|
|
||||||
// --- Express App Setup ---
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Middleware to parse plain text request bodies (like Hastebin)
|
app.use(express.text({ limit: '10mb', type: 'text/plain' }));
|
||||||
// Limit set to 1MB, adjust as needed
|
app.use(express.json());
|
||||||
app.use(express.text({ limit: '1mb', type: 'text/plain' }));
|
app.use(express.static('static'));
|
||||||
app.use(express.json()); // For JSON API responses
|
|
||||||
|
|
||||||
// Serve static files from the 'public' directory
|
|
||||||
app.use(express.static('public'));
|
|
||||||
|
|
||||||
// Function to generate a unique ID without crypto
|
|
||||||
function generateUniqueId() {
|
function generateUniqueId() {
|
||||||
const timestamp = Date.now().toString(36);
|
const timestamp = Date.now().toString(36);
|
||||||
const randomPart = Math.random().toString(36).substring(2, 10);
|
const randomPart = Math.random().toString(36).substring(2, 10);
|
||||||
return `${timestamp}-${randomPart}`;
|
return `${timestamp}-${randomPart}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API Routes ---
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/pastes
|
|
||||||
* Creates a new paste.
|
|
||||||
* Expects the raw text content in the request body.
|
|
||||||
* Responds with the generated ID for the paste.
|
|
||||||
*/
|
|
||||||
app.post('/api/pastes', (req, res) => {
|
app.post('/api/pastes', (req, res) => {
|
||||||
const content = req.body;
|
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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Basic validation: Ensure content is present and is a string
|
|
||||||
if (!content || typeof content !== 'string' || content.trim() === '') {
|
if (!content || typeof content !== 'string' || content.trim() === '') {
|
||||||
return res.status(400).json({ error: 'Paste content cannot be empty.' });
|
return res.status(400).json({ error: 'Paste content cannot be empty.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a unique ID without using crypto
|
// Calculate paste size in bytes
|
||||||
|
const pasteSize = Buffer.byteLength(content, 'utf8');
|
||||||
|
const sizeInKB = (pasteSize / 1024).toFixed(2);
|
||||||
|
|
||||||
const id = generateUniqueId();
|
const id = generateUniqueId();
|
||||||
|
|
||||||
// Store the paste content in the map
|
|
||||||
pastes.set(id, content);
|
pastes.set(id, content);
|
||||||
console.log(`[${new Date().toISOString()}] Paste created with ID: ${id}`);
|
ipLastPaste.set(clientIp, now);
|
||||||
|
|
||||||
|
console.log(`[${new Date().toISOString()}] Paste created with ID: ${id} | Size: ${sizeInKB} KB (${pasteSize} bytes)`);
|
||||||
|
|
||||||
// Schedule the paste for deletion after the TTL
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (pastes.has(id)) {
|
if (pastes.has(id)) {
|
||||||
pastes.delete(id);
|
pastes.delete(id);
|
||||||
@ -60,21 +61,14 @@ app.post('/api/pastes', (req, res) => {
|
|||||||
}
|
}
|
||||||
}, PASTE_TTL_MS);
|
}, PASTE_TTL_MS);
|
||||||
|
|
||||||
// Respond with the ID
|
|
||||||
res.status(201).json({ id: id });
|
res.status(201).json({ id: id });
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/pastes/:id
|
|
||||||
* Retrieves the content of a specific paste by its ID.
|
|
||||||
* Responds with the raw text content or 404 if not found.
|
|
||||||
*/
|
|
||||||
app.get('/api/pastes/:id', (req, res) => {
|
app.get('/api/pastes/:id', (req, res) => {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
if (pastes.has(id)) {
|
if (pastes.has(id)) {
|
||||||
const content = pastes.get(id);
|
const content = pastes.get(id);
|
||||||
// Send raw text content
|
|
||||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
res.status(200).send(content);
|
res.status(200).send(content);
|
||||||
} else {
|
} else {
|
||||||
@ -82,22 +76,35 @@ app.get('/api/pastes/:id', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve the main HTML page for all other routes
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Start Server ---
|
// 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, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Paste server listening on port ${PORT}`);
|
console.log(`Paste server listening on port ${PORT}`);
|
||||||
console.log(`Pastes will be stored for ${PASTE_TTL_HOURS} hours.`);
|
console.log(`Pastes will be stored for ${PASTE_TTL_HOURS} hours.`);
|
||||||
console.log(`Open http://localhost:${PORT} in your browser to use the application`);
|
console.log(`Open http://localhost:${PORT} in your browser to use the application`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Graceful Shutdown (Optional but Recommended) ---
|
|
||||||
// Handles CTRL+C in the terminal
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
console.log('\nShutting down server...');
|
console.log('\nShutting down server...');
|
||||||
// Perform any cleanup here if needed (though in-memory storage is lost anyway)
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
Loading…
x
Reference in New Issue
Block a user