paste-bin/templates/index.html
Jarian Cottingham 3ea2ab4258 fix: security hardening — SECRET_KEY, CSRF, rate-limit, headers, Docker, SVG (#7,#8,#9,#10,#11,#12,#13,#14,#15,#18,#21,#25)
Use secrets.token_hex for SECRET_KEY (no hardcoded default).
Add CSRF tokens to forms and cookie.
Rate limit uploads: 10 per 60s per IP.
Add security headers: CSP, X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy.
Block SVG uploads (executable JS risk).
Validate image content via magic bytes.
Atomic file creation with O_EXCL (fixes TOCTOU race).
Increase paste ID from 8→16 hex chars.
Run cleanup_expired every 5min in background thread.
Delete .txt files on paste deletion.
Fix file upload tab (missing name attribute).
Docker: add non-root user, pin dependency versions.
2026-07-04 04:57:15 +00:00

136 lines
6.0 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PasteBin</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<header>
<h1><a href="/">PasteBin</a></h1>
<p>Share text, images, and files temporarily</p>
</header>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
<div class="tabs">
<button class="tab active" data-tab="text">Text</button>
<button class="tab" data-tab="image">Image</button>
<button class="tab" data-tab="file">File</button>
</div>
<form method="POST" action="/paste" enctype="multipart/form-data" id="uploadForm">
<input type="hidden" name="paste_type" id="pasteType" value="text">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-group">
<label for="title">Title (optional)</label>
<input type="text" id="title" name="title" placeholder="Give your paste a name">
</div>
<div id="text-panel" class="panel active">
<div class="form-group">
<label for="content">Content</label>
<textarea id="content" name="content" placeholder="Paste your text here..." rows="12"></textarea>
</div>
</div>
<div id="image-panel" class="panel">
<div class="form-group">
<label id="file-label">Image (max 20 MB)</label>
<div class="file-input">
<input type="file" id="upload-file" name="file" accept="image/png,image/jpeg,image/gif,image/bmp,image/webp,image/svg+xml,image/tiff" style="display:none">
<label for="upload-file" class="file-label">
<span class="file-icon" id="file-icon">&#128247;</span>
<span class="file-text" id="file-text">Choose image or drag here</span>
</label>
<span id="chosen-filename" class="chosen-file"></span>
</div>
</div>
</div>
<div id="file-panel" class="panel">
<div class="form-group">
<label id="file-label2">File (max 20 MB)</label>
<div class="file-input">
<span class="file-icon">&#128100;</span>
<span class="file-text" id="file-text2">Choose file or drag here</span>
<span id="chosen-filename2" class="chosen-file"></span>
</div>
</div>
</div>
<div class="form-group">
<label for="expiry">Expires</label>
<select id="expiry" name="expiry">
{% for key, label in expiry_options %}
<option value="{{ key }}" {% if key == '1d' %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="submit-btn">Create Paste</button>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tabs = document.querySelectorAll('.tab');
const panels = document.querySelectorAll('.panel');
const pasteTypeInput = document.getElementById('pasteType');
const uploadFile = document.getElementById('upload-file');
const uploadFile2 = document.getElementById('upload-file2');
const chosenFilename = document.getElementById('chosen-filename');
const chosenFilename2 = document.getElementById('chosen-filename2');
const fileIcon = document.getElementById('file-icon');
const fileText = document.getElementById('file-text');
const fileLabel = document.getElementById('file-label');
const contentTextarea = document.getElementById('content');
function switchTab(tabName) {
tabs.forEach(t => t.classList.remove('active'));
panels.forEach(p => p.classList.remove('active'));
document.querySelector('[data-tab="' + tabName + '"]').classList.add('active');
document.getElementById(tabName + '-panel').classList.add('active');
pasteTypeInput.value = tabName;
uploadFile.setAttribute('name', '__unused__');
contentTextarea.setAttribute('name', '__unused__');
if (tabName === 'text') {
contentTextarea.setAttribute('name', 'content');
} else if (tabName === 'image') {
uploadFile.setAttribute('name', 'file');
uploadFile.setAttribute('accept', 'image/png,image/jpeg,image/gif,image/bmp,image/webp,image/tiff');
fileIcon.innerHTML = '&#128247;';
fileText.textContent = 'Choose image or drag here';
fileLabel.textContent = 'Image (max 20 MB)';
} else if (tabName === 'file') {
uploadFile.setAttribute('name', 'file');
uploadFile.removeAttribute('accept');
}
}
tabs.forEach(tab => {
tab.addEventListener('click', function() {
switchTab(this.dataset.tab);
});
});
uploadFile.addEventListener('change', function() {
chosenFilename.textContent = this.files[0] ? this.files[0].name : '';
chosenFilename2.textContent = '';
});
uploadFile2.addEventListener('change', function() {
chosenFilename2.textContent = this.files[0] ? this.files[0].name : '';
chosenFilename.textContent = '';
});
});
</script>
</body>
</html>