Fix the clear button and improve rate limiting

This commit is contained in:
ComputerTech 2026-03-27 16:04:26 +00:00
parent 0bb85da6bb
commit 53b908e651
4 changed files with 120 additions and 15 deletions

62
app.py
View File

@ -5,6 +5,7 @@ import secrets
import sqlite3
import sys
import threading
import time
import uuid
import datetime
from flask import Flask, render_template, request, jsonify, abort, Response, g
@ -128,10 +129,50 @@ def init_db():
views INTEGER DEFAULT 0
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS rate_limits (
ip_address TEXT,
timestamp REAL,
PRIMARY KEY (ip_address, timestamp)
)
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_rate_limit_ts ON rate_limits(timestamp)')
conn.commit()
finally:
conn.close()
_db_initialized = True
_start_cleanup_task()
_cleanup_running = False
def _start_cleanup_task():
"""Start a background thread to purge expired pastes every hour."""
global _cleanup_running
if _cleanup_running:
return
_cleanup_running = True
def run():
while True:
try:
# Sleep first to avoid running immediately on startup
time.sleep(3600)
now = datetime.datetime.now(_UTC).isoformat()
conn = sqlite3.connect(DATABASE)
try:
with conn:
res = conn.execute('DELETE FROM pastes WHERE expires_at < ?', (now,))
purged_pastes = res.rowcount
res = conn.execute('DELETE FROM rate_limits WHERE timestamp < ?', (time.time() - 3600,))
purged_ips = res.rowcount
if purged_pastes > 0 or purged_ips > 0:
print(f"[Cleanup] Purged {purged_pastes} expired pastes and {purged_ips} rate limit entries.", flush=True)
finally:
conn.close()
except Exception as e:
print(f"Error in background cleanup: {e}", file=sys.stderr)
t = threading.Thread(target=run, daemon=True)
t.start()
# ── Helpers ───────────────────────────────────────────────────────────────────
@ -171,6 +212,27 @@ def index():
@app.route('/create', methods=['POST'])
def create_paste():
# ── Rate limiting ────────────────────────────────────────────────────────
# 10 pastes per 10 minutes per IP address (SQLite backed for worker safety)
remote_ip = request.remote_addr
now_ts = time.time()
window = 600 # 10 minutes
conn = get_db_connection()
try:
# Purge old for this IP specifically or just check current
count = conn.execute(
'SELECT COUNT(*) FROM rate_limits WHERE ip_address = ? AND timestamp > ?',
(remote_ip, now_ts - window)
).fetchone()[0]
if count >= 10:
return jsonify({'error': 'Rate limit exceeded. Please wait a few minutes.'}), 429
conn.execute('INSERT INTO rate_limits (ip_address, timestamp) VALUES (?, ?)', (remote_ip, now_ts))
conn.commit()
finally:
conn.close()
data = request.get_json(silent=True)
if not data:
return jsonify({'error': 'JSON body required'}), 400

View File

@ -23,23 +23,38 @@ const _UI_VAR_MAP = {
};
// Fetch config and initialise application when ready
document.addEventListener('DOMContentLoaded', async () => {
try {
const resp = await fetch('/api/config');
if (resp.ok) window.PBCFG = await resp.json();
} catch (e) {
console.warn('Could not load /api/config, using CSS fallbacks.', e);
}
initialiseTheme();
applyUiVars();
document.addEventListener('DOMContentLoaded', () => {
// 1. Initialise DOM-dependent listeners IMMEDIATELY
initAutoSave();
// Attach theme toggle listener (fixing CSP blocking of inline onclick)
const toggleBtn = document.getElementById('themeToggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', toggleTheme);
}
// 2. Fetch config and apply theme/UI tweaks as a background enhancement
(async function loadConfig() {
try {
const resp = await fetch('/api/config');
if (resp.ok) {
window.PBCFG = await resp.json();
// Re-apply in case config changed defaults
initialiseTheme();
applyUiVars();
// refresh autosave settings if they come from config
if (window.PBCFG?.features?.auto_save_draft === false) {
// If now disabled, we could stop listeners, but for simplicity
// we just won't save next time.
}
}
} catch (e) {
console.warn('Could not load /api/config, using CSS fallbacks.', e);
}
})();
// Initial pass with CSS fallbacks
initialiseTheme();
applyUiVars();
});
// ── Theme Management ────────────────────────────────────────────────────────
@ -113,7 +128,7 @@ function swapPrismTheme(theme) {
}
}
function toggleTheme() {
window.toggleTheme = function () {
const current = document.documentElement.getAttribute('data-theme') || 'light';
const newTheme = current === 'light' ? 'dark' : 'light';
applyTheme(newTheme);
@ -226,7 +241,8 @@ function loadDraft() {
}
}
function clearDraft() {
window.clearDraft = function () {
console.log('[Draft] Clearing localStorage draft...');
localStorage.removeItem('paste_draft');
}

View File

@ -46,7 +46,31 @@ document.addEventListener('DOMContentLoaded', function () {
submitBtn.addEventListener('click', submitPaste);
// ── Clear Draft ──────────────────────────────────────────────────────────
const clearBtn = document.getElementById('clearBtn');
if (clearBtn) {
clearBtn.addEventListener('click', () => {
console.log('[Clear] Manual clear requested.');
// Clear the editor and title
textarea.value = '';
const title = document.getElementById('title');
if (title) title.value = '';
// Wipe the localStorage draft via the global helper in app.js
if (typeof window.clearDraft === 'function') {
window.clearDraft();
} else {
localStorage.removeItem('paste_draft');
}
textarea.focus();
console.log('[Clear] Draft wiped.');
});
}
// ── Submit ───────────────────────────────────────────────────────────────
async function submitPaste() {
const content = textarea.value;
const title = document.getElementById('title').value || 'Untitled';
@ -85,7 +109,9 @@ document.addEventListener('DOMContentLoaded', function () {
submitBtn.disabled = false;
submitBtn.textContent = 'Save';
} else {
clearDraft();
if (typeof window.clearDraft === 'function') {
window.clearDraft();
}
window.location.href = result.url + (keyBase64 ? '#' + keyBase64 : '');
}
} catch (err) {

View File

@ -13,6 +13,7 @@
<option value="1week">1 Week</option>
<option value="1month">1 Month</option>
</select>
<button id="clearBtn" class="nav-btn">Clear</button>
<button id="submitBtn" class="nav-btn nav-btn-save">Save</button>
{% if cfg.theme.allow_user_toggle %}
<button id="themeToggle" class="theme-toggle">🌙</button>