security(relay): harden internet-facing relay server
Closes the account-takeover chain and related issues found in audit: - WS auth brute-force protection: per-session lockout (authThrottle) + per- socket failure cap that closes the socket (1008); applied to both web-client and desktop auth. Failed auth no longer leaves the socket open for unlimited guesses. - WS upgrades now pass an IP-based rate limiter and Origin allowlist before handleUpgrade (previously bypassed all HTTP middleware); trust proxy set so limiting keys on the real client IP. - /health no longer leaks live session IDs (counts only). - .env.example rate-limit fixed (900000ms / 300) from the accidental ~11k rps. - Credentials moved out of URLs into the X-MacroPad-Password header; images fetched via header + blob URLs; login stores creds in sessionStorage. - Session creation bounded (max sessions, min password length) and TTL-pruned; session store writes are now atomic (temp+rename) and debounced. - Session IDs lengthened to 12 chars with rejection sampling (no modulo bias). - Enable helmet CSP; restrict CORS to configured origins. - Request IDs use crypto.randomUUID; drop postinstall build hook and uuid dep. - Uniform response for unknown session IDs (removes enumeration oracle). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,8 +169,9 @@
|
||||
|
||||
let desktopConnected = false;
|
||||
|
||||
// Check for saved password
|
||||
const savedPassword = sessionStorage.getItem(`macropad_${sessionId}`);
|
||||
// Check for saved password (session first, then "remembered" store)
|
||||
const savedPassword = sessionStorage.getItem(`macropad_${sessionId}`)
|
||||
|| localStorage.getItem(`macropad_${sessionId}`);
|
||||
if (savedPassword) {
|
||||
passwordInput.value = savedPassword;
|
||||
}
|
||||
@@ -225,13 +226,16 @@
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Save password if remember is checked
|
||||
// Hand the credential to the app via storage (never the URL).
|
||||
sessionStorage.setItem(`macropad_${sessionId}`, password);
|
||||
if (rememberCheckbox.checked) {
|
||||
sessionStorage.setItem(`macropad_${sessionId}`, password);
|
||||
localStorage.setItem(`macropad_${sessionId}`, password);
|
||||
} else {
|
||||
localStorage.removeItem(`macropad_${sessionId}`);
|
||||
}
|
||||
|
||||
// Redirect to the PWA with password
|
||||
window.location.href = `/${sessionId}/app?auth=${encodeURIComponent(password)}`;
|
||||
// Redirect to the PWA (credential is read from storage).
|
||||
window.location.href = `/${sessionId}/app`;
|
||||
} else {
|
||||
const data = await response.json();
|
||||
errorDiv.textContent = data.error || 'Invalid password';
|
||||
|
||||
Reference in New Issue
Block a user