Add customizable site title feature via DUMBDROP_TITLE environment variable

This commit is contained in:
Greirson Lee-Thorp
2025-01-31 01:26:55 -08:00
parent cb58631664
commit 851fc95f1e
5 changed files with 33 additions and 41 deletions

View File

@@ -1,11 +1,12 @@
# Server Configuration # Server Configuration
PORT=3000 # The port the server will listen on PORT=3000 # The port the server will listen on
DUMBDROP_TITLE=DumbDrop # Site title displayed in header (default: DumbDrop)
# Upload Limits # Upload Limits
MAX_FILE_SIZE=1024 # Maximum file size in MB (default: 1024 MB / 1 GB) MAX_FILE_SIZE=1024 # Maximum file size in MB (default: 1024 MB / 1 GB)
# Security # Security
DUMBDROP_PIN= # Optional 4-digit PIN protection (leave empty to disable) DUMBDROP_PIN= # Optional PIN protection (4-10 digits, leave empty to disable)
# Notifications # Notifications
APPRISE_URL= # Apprise URL for notifications (leave empty to disable) APPRISE_URL= # Apprise URL for notifications (leave empty to disable)

View File

@@ -28,8 +28,9 @@ No auth (unless you want it now!), no storage, no nothing. Just a simple file up
| PORT | Server port | 3000 | No | | PORT | Server port | 3000 | No |
| MAX_FILE_SIZE| Maximum file size in MB | 1024 | No | | MAX_FILE_SIZE| Maximum file size in MB | 1024 | No |
| DUMBDROP_PIN | PIN protection (4-10 digits) | None | No | | DUMBDROP_PIN | PIN protection (4-10 digits) | None | No |
| DUMBDROP_TITLE| Site title displayed in header | DumbDrop| No |
| APPRISE_URL | Apprise URL for notifications | None | No | | APPRISE_URL | Apprise URL for notifications | None | No |
| APPRISE_MESSAGE| Notification message template | "File uploaded: {filename}" | No | | APPRISE_MESSAGE| Notification message template | "File uploaded: {filename}" | No |
## Security Features ## Security Features

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DumbDrop - Simple File Upload</title> <title>{{SITE_TITLE}} - Simple File Upload</title>
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script> <script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
@@ -26,7 +26,7 @@
<line class="sun" x1="18.36" y1="5.64" x2="19.78" y2="4.22" style="display:none"/> <line class="sun" x1="18.36" y1="5.64" x2="19.78" y2="4.22" style="display:none"/>
</svg> </svg>
</button> </button>
<h1>DumbDrop</h1> <h1>{{SITE_TITLE}}</h1>
<div class="upload-container" id="dropZone"> <div class="upload-container" id="dropZone">
<div class="upload-content"> <div class="upload-content">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DumbDrop - Login</title> <title>{{SITE_TITLE}} - Login</title>
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<style> <style>
.login-container { .login-container {
@@ -57,7 +57,7 @@
<body> <body>
<div class="login-container"> <div class="login-container">
<div class="pin-header"> <div class="pin-header">
<h1>DumbDrop</h1> <h1>{{SITE_TITLE}}</h1>
<h2>Enter PIN</h2> <h2>Enter PIN</h2>
</div> </div>
<form id="pin-form"> <form id="pin-form">

View File

@@ -16,6 +16,7 @@ const uploadDir = './uploads'; // Local development
const maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '1024') * 1024 * 1024; // Convert MB to bytes const maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '1024') * 1024 * 1024; // Convert MB to bytes
const APPRISE_URL = process.env.APPRISE_URL; const APPRISE_URL = process.env.APPRISE_URL;
const APPRISE_MESSAGE = process.env.APPRISE_MESSAGE || 'File uploaded: {filename}'; const APPRISE_MESSAGE = process.env.APPRISE_MESSAGE || 'File uploaded: {filename}';
const siteTitle = process.env.DUMBDROP_TITLE || 'DumbDrop';
// Brute force protection setup // Brute force protection setup
const loginAttempts = new Map(); // Stores IP addresses and their attempt counts const loginAttempts = new Map(); // Stores IP addresses and their attempt counts
@@ -191,43 +192,29 @@ const requirePin = (req, res, next) => {
next(); next();
}; };
// Apply pin protection to all /upload routes // Move the root and login routes before static file serving
app.use('/upload', requirePin);
// Serve login page and its assets without PIN check
app.use((req, res, next) => {
if (req.path === '/login.html' || req.path === '/styles.css' || req.path.startsWith('/api/')) {
return next();
}
// Check PIN requirement
if (!PIN) {
return next();
}
// Check cookie
const providedPin = req.cookies.DUMBDROP_PIN;
if (!safeCompare(providedPin, PIN)) {
// If requesting HTML or root, redirect to login
if (req.path === '/' || req.path.endsWith('.html')) {
return res.redirect('/login.html');
}
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
// Serve static files
app.use(express.static('public'));
// Handle root route
app.get('/', (req, res) => { app.get('/', (req, res) => {
if (PIN && !safeCompare(req.cookies.DUMBDROP_PIN, PIN)) { if (PIN && !safeCompare(req.cookies.DUMBDROP_PIN, PIN)) {
return res.redirect('/login.html'); return res.redirect('/login.html');
} }
res.sendFile(path.join(__dirname, 'public', 'index.html')); // Read the file and replace the title
let html = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
html = html.replace(/{{SITE_TITLE}}/g, siteTitle); // Use global replace
res.send(html);
}); });
app.get('/login.html', (req, res) => {
let html = fs.readFileSync(path.join(__dirname, 'public', 'login.html'), 'utf8');
html = html.replace(/{{SITE_TITLE}}/g, siteTitle); // Use global replace
res.send(html);
});
// Move static file serving after our dynamic routes
app.use(express.static('public'));
// PIN protection middleware should be before the routes that need protection
app.use('/upload', requirePin);
// Store ongoing uploads // Store ongoing uploads
const uploads = new Map(); const uploads = new Map();
@@ -335,11 +322,14 @@ app.listen(port, () => {
log.info(`Server running at http://localhost:${port}`); log.info(`Server running at http://localhost:${port}`);
log.info(`Upload directory: ${uploadDir}`); log.info(`Upload directory: ${uploadDir}`);
// Log custom title if set
if (process.env.DUMBDROP_TITLE) {
log.info(`Custom title set to: ${siteTitle}`);
}
// Add Apprise configuration logging // Add Apprise configuration logging
if (APPRISE_URL) { if (APPRISE_URL) {
log.info(`Apprise notifications enabled`); log.info('Apprise notifications enabled');
log.info(`Apprise URL: ${APPRISE_URL}`);
log.info(`Apprise message template: ${APPRISE_MESSAGE}`);
} else { } else {
log.info('Apprise notifications disabled - no URL configured'); log.info('Apprise notifications disabled - no URL configured');
} }