fix(security): high — live authz, XFF, SSE buffering, atomicity, pagination
H1: auth extractor re-reads the live user row — trusts the DB role (not the
JWT claim) and rejects banned users mid-session. e2e proves a demoted
host loses powers and a banned host is locked out and can't self-unban.
H2: client_ip takes the right-most XFF hop (the one Caddy appends); spoofed
left-most entries are ignored, restoring IP-based throttles.
H3: Caddy excludes /api/v1/stream from `encode` so SSE isn't buffered.
H4: upload — quota increment + row insert + hashtag links now one txn.
H5: feed keyset pagination tiebroken on (created_at, id) + composite index
(migration 010); feed_delta bounded with LIMIT.
H7: LightboxModal keys its comment load off upload.id, ending the
SSE-driven refetch storm.
H8: CSP via SvelteKit kit.csp (svelte.config.js) hardens the localStorage
token model against injection.
Migration 010 also adds the latent comment/comment_hashtag indexes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
import { doubletap } from '$lib/actions/doubletap';
|
||||
import { focusTrap } from '$lib/actions/focus-trap';
|
||||
import { scrollLock } from '$lib/actions/scroll-lock';
|
||||
import { modalInert } from '$lib/actions/modal-inert';
|
||||
import { toastError } from '$lib/toast-store';
|
||||
import { vibrate } from '$lib/haptics';
|
||||
import HeartBurst from './HeartBurst.svelte';
|
||||
@@ -51,13 +52,19 @@
|
||||
if (burstTimer) clearTimeout(burstTimer);
|
||||
});
|
||||
|
||||
// Only refetch when a *different* upload is shown. The feed reassigns the
|
||||
// `upload` prop object on every SSE like/comment count update; keying the
|
||||
// effect off the memoized id avoids a refetch storm that would also clobber
|
||||
// a just-posted optimistic comment.
|
||||
const uploadId = $derived(upload.id);
|
||||
|
||||
$effect(() => {
|
||||
loadComments();
|
||||
loadComments(uploadId);
|
||||
});
|
||||
|
||||
async function loadComments() {
|
||||
async function loadComments(id: string) {
|
||||
try {
|
||||
comments = await api.get<CommentDto[]>(`/upload/${upload.id}/comments`);
|
||||
comments = await api.get<CommentDto[]>(`/upload/${id}/comments`);
|
||||
} catch {
|
||||
// Background fetch — failure leaves the panel empty; reopening the lightbox retries.
|
||||
}
|
||||
@@ -106,6 +113,7 @@
|
||||
aria-labelledby="lightbox-title"
|
||||
use:focusTrap={{ onclose }}
|
||||
use:scrollLock
|
||||
use:modalInert
|
||||
>
|
||||
<div class="flex max-h-[90vh] w-full max-w-2xl flex-col overflow-hidden rounded-xl bg-white dark:bg-gray-900">
|
||||
<!-- Media -->
|
||||
|
||||
Reference in New Issue
Block a user