security: auth redesign, SSRF protection, CORS lockdown, and 13 other fixes

- Auth (#2/#3): UUID PK, 256-bit keys, SHA-256 lookup + bcrypt hash
- SSRF (#1): validate URLs, block private IPs, cloud metadata endpoints
- CORS (#4): lock to pingql.com origins, not wildcard
- SSE limit (#6): 10 connections per monitor max
- ReDoS (#7): cap $regex patterns at 200 chars
- Monitor limit (#8): 100 per account default
- Cookie env config (#9): secure/domain from env vars
- Bearer parsing (#10): case-insensitive RFC 6750
- Pings retention (#11): 90-day pruner, hourly interval
- monitors.enabled index (#12): partial index for /internal/due
- Runner locking (#14): locked_until for horizontal scale safety
- COALESCE nullable bug (#17): dynamic PATCH with explicit undefined checks
- MONITOR_TOKEN null guard (#18): startup validation + middleware hardening
- reset-key cookie fix (#16): sets new cookie in response
This commit is contained in:
M1
2026-03-17 06:10:10 +04:00
parent 5071e340c7
commit 6bdd76b4f0
10 changed files with 250 additions and 72 deletions
+8 -14
View File
@@ -15,11 +15,11 @@
<h2 class="text-sm font-semibold text-gray-300 mb-4">Account</h2>
<div class="space-y-3">
<div>
<label class="block text-xs text-gray-500 mb-1">Primary Key</label>
<label class="block text-xs text-gray-500 mb-1">Account ID</label>
<div class="flex gap-2">
<code id="primary-key" class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-blue-400 text-sm tracking-widest"><%= it.account.id %></code>
<button onclick="copyKey()" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-400 hover:text-white transition-colors text-xs">Copy</button>
<code id="account-id" class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-gray-400 text-sm"><%= it.account.id %></code>
</div>
<p class="text-xs text-gray-600 mt-1.5">Your secret key was shown once at registration and cannot be displayed again. Use "Rotate Key" below to generate a new one.</p>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">Member since</label>
@@ -88,7 +88,7 @@
<div class="flex items-center justify-between p-3 bg-gray-800/50 rounded-lg border border-gray-700/50">
<div>
<p class="text-sm text-gray-200"><%= k.label %></p>
<p class="text-xs text-gray-600 mt-0.5 font-mono"><%= k.id %> · created <%= new Date(k.created_at).toLocaleDateString() %> <%~ k.last_used_at ? '· last used ' + it.timeAgoSSR(k.last_used_at) : '· never used' %></p>
<p class="text-xs text-gray-600 mt-0.5">created <%= new Date(k.created_at).toLocaleDateString() %> <%~ k.last_used_at ? '· last used ' + it.timeAgoSSR(k.last_used_at) : '· never used' %></p>
</div>
<button onclick="deleteKey('<%= k.id %>')" class="text-xs text-gray-600 hover:text-red-400 transition-colors px-2 py-1">Revoke</button>
</div>
@@ -111,14 +111,6 @@
}
})();
function copyKey() {
const key = document.getElementById('primary-key').textContent;
navigator.clipboard.writeText(key);
const btn = event.target;
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy', 1500);
}
async function saveEmail() {
const email = document.getElementById('email-input').value.trim();
if (!email) return;
@@ -139,8 +131,10 @@
async function confirmReset() {
if (!confirm('Rotate your primary key?\n\nYour current key will stop working immediately. Make sure to copy the new one.')) return;
const data = await api('/account/reset-key', { method: 'POST', body: {} });
await fetch('/account/login', { method: 'POST', credentials: 'same-origin', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ key: data.key }) });
location.reload();
// New key is shown once — display it in the new-key-reveal area
document.getElementById('new-key-value').textContent = data.key;
document.getElementById('new-key-reveal').classList.remove('hidden');
// Cookie is set server-side by reset-key endpoint
}
function showCreateKey() {