Harden CSRF/CSP and add hash version upgrades
This commit is contained in:
@@ -47,6 +47,16 @@
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
.card-visual.has-image {
|
||||
background: #f6b24f;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-visual-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.card-visual.hovering {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
@@ -269,3 +279,10 @@ input[type="range"].full-slider:disabled::-moz-range-thumb {
|
||||
background: #f1f1f1;
|
||||
border-color: #c1c1c1;
|
||||
}
|
||||
|
||||
.fx-canvas {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
@@ -205,6 +205,11 @@ Registriere dich erneut mit dem korrekten Schlüssel vom Host ‒ oder lasse das
|
||||
Auth- und Admin-sensitive Routen sind gegen Brute-Force-Angriffe rate-limitiert.
|
||||
Warte kurz und versuche es dann erneut.
|
||||
|
||||
### „CSRF-Validierung fehlgeschlagen."
|
||||
|
||||
Authentifizierte Schreibaktionen erfordern jetzt eine Same-Origin-Browseranfrage.
|
||||
Lade die Seite neu und versuche es erneut. Bei eigener API-Nutzung müssen `Origin`/`Referer` zum App-Host passen.
|
||||
|
||||
## Daten & Datenschutz
|
||||
|
||||
- Vorschläge, Stimmen und Phasenstatus werden in einer gemeinsamen Datenbank gespeichert.
|
||||
|
||||
@@ -209,6 +209,11 @@ Register again using the correct key from the host ‒ or leave it blank to crea
|
||||
Auth and admin-sensitive routes are rate-limited to reduce brute-force attempts.
|
||||
Wait briefly, then retry.
|
||||
|
||||
### "CSRF validation failed."
|
||||
|
||||
Authenticated write actions now require a same-origin browser request.
|
||||
Reload the page and retry. If you're calling the API from custom tooling, send matching `Origin`/`Referer` values for your app host.
|
||||
|
||||
## Data & Privacy
|
||||
|
||||
- Suggestions, votes, and phase states are stored in a shared database.
|
||||
|
||||
47
wwwroot/js/effects.js
vendored
47
wwwroot/js/effects.js
vendored
@@ -3,48 +3,15 @@
|
||||
// Screenshot hover ---------------------------------------------------
|
||||
export function setupCardVisualHover(el, url) {
|
||||
if (!el || !url) return;
|
||||
const img = new Image();
|
||||
let naturalW = 0;
|
||||
let naturalH = 0;
|
||||
let loaded = false;
|
||||
img.src = url;
|
||||
img.onload = () => {
|
||||
naturalW = img.naturalWidth;
|
||||
naturalH = img.naturalHeight;
|
||||
loaded = true;
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
el.classList.remove("hovering");
|
||||
el.style.backgroundSize = "";
|
||||
el.style.backgroundPosition = "";
|
||||
el.style.backgroundRepeat = "";
|
||||
};
|
||||
|
||||
el.addEventListener("mouseenter", () => {
|
||||
el.classList.add("hovering");
|
||||
el.style.backgroundSize = "auto";
|
||||
el.style.backgroundRepeat = "no-repeat";
|
||||
el.style.backgroundPosition = "center";
|
||||
});
|
||||
|
||||
el.addEventListener("mousemove", (e) => {
|
||||
if (!loaded) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const overW = naturalW - rect.width;
|
||||
const overH = naturalH - rect.height;
|
||||
if (overW <= 0 && overH <= 0) {
|
||||
el.style.backgroundPosition = "center";
|
||||
return;
|
||||
}
|
||||
const xRatio = (e.clientX - rect.left) / rect.width;
|
||||
const yRatio = (e.clientY - rect.top) / rect.height;
|
||||
const xPercent = overW > 0 ? xRatio * 100 : 50;
|
||||
const yPercent = overH > 0 ? yRatio * 100 : 50;
|
||||
el.style.backgroundPosition = `${xPercent}% ${yPercent}%`;
|
||||
});
|
||||
|
||||
["mouseleave", "blur"].forEach((evt) => el.addEventListener(evt, reset));
|
||||
["mouseleave", "blur"].forEach((evt) =>
|
||||
el.addEventListener(evt, () => {
|
||||
el.classList.remove("hovering");
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Celebration FX -----------------------------------------------------
|
||||
@@ -57,10 +24,6 @@ function ensureFxCanvas() {
|
||||
if (fxCanvas) return;
|
||||
fxCanvas = document.createElement("canvas");
|
||||
fxCanvas.className = "fx-canvas";
|
||||
fxCanvas.style.position = "fixed";
|
||||
fxCanvas.style.inset = "0";
|
||||
fxCanvas.style.pointerEvents = "none";
|
||||
fxCanvas.style.zIndex = "120";
|
||||
fxCanvas.width = window.innerWidth;
|
||||
fxCanvas.height = window.innerHeight;
|
||||
fxCtx = fxCanvas.getContext("2d");
|
||||
|
||||
@@ -6,7 +6,6 @@ import { setupCardVisualHover, triggerCelebration } from "./effects.js";
|
||||
import { renderAdminLinker } from "./admin-ui.js";
|
||||
import { getUiRuntime } from "./ui-runtime.js";
|
||||
import {
|
||||
cssEscapeUrl,
|
||||
escapeHtml,
|
||||
isLinked,
|
||||
linkedPeerTitles,
|
||||
@@ -95,7 +94,7 @@ export function buildCard(
|
||||
: "";
|
||||
const visual =
|
||||
hasImage && safeShot
|
||||
? `<button class="card-visual" data-img="${safeShot}" aria-label="${t("card.openScreenshot")}" style="background-image:url('${cssEscapeUrl(safeShot)}')"></button>`
|
||||
? `<button class="card-visual has-image" data-img="${escapeHtml(safeShot)}" aria-label="${t("card.openScreenshot")}"><img class="card-visual-image" src="${escapeHtml(safeShot)}" alt="" loading="lazy" decoding="async" /></button>`
|
||||
: `<div class="card-visual"></div>`;
|
||||
const hasPlayers = s.minPlayers || s.maxPlayers;
|
||||
const players = hasPlayers
|
||||
|
||||
Reference in New Issue
Block a user