fix(security): add csrfFetch wrapper and apply to all client-side mutating requests
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m52s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m48s
CI / fail2ban Regex (pull_request) Successful in 44s
CI / Semgrep Security Scan (pull_request) Successful in 20s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m4s
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m52s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m48s
CI / fail2ban Regex (pull_request) Successful in 44s
CI / Semgrep Security Scan (pull_request) Successful in 20s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m4s
Introduces `csrfFetch` (= `makeCsrfFetch(fetch)`) in cookies.ts as a drop-in fetch replacement that auto-injects X-XSRF-TOKEN on POST/PUT/PATCH/DELETE. Previously 8 call sites sent mutating requests without the CSRF header — annotation resize, comment POST/PATCH/DELETE, Geschichte CRUD, Stammbaum relationship creation, bulk-edit PATCH, and file upload — all would fail with CSRF_TOKEN_MISSING if the backend's cookie-based protection triggered. All 14 client-side mutating fetches now use csrfFetch; withCsrf/makeCsrfFetch remain in the API for injectable-fetch use cases (e.g. useTranscriptionBlocks). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import type { Comment, FlatMessage, MentionDTO } from '$lib/shared/types';
|
||||
import MentionEditor from '$lib/shared/discussion/MentionEditor.svelte';
|
||||
import CommentMessage from '$lib/shared/discussion/CommentMessage.svelte';
|
||||
import { extractContent } from '$lib/shared/discussion/mention';
|
||||
import { csrfFetch } from '$lib/shared/cookies';
|
||||
type Props = {
|
||||
documentId: string;
|
||||
annotationId?: string | null;
|
||||
@@ -79,7 +80,7 @@ async function postComment() {
|
||||
posting = true;
|
||||
try {
|
||||
const { content, mentionedUserIds } = extractContent(text, newMentionCandidates);
|
||||
const res = await fetch(commentsBase, {
|
||||
const res = await csrfFetch(commentsBase, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content, mentionedUserIds })
|
||||
@@ -104,7 +105,7 @@ async function saveEdit(commentId: string) {
|
||||
if (!text || posting) return;
|
||||
posting = true;
|
||||
try {
|
||||
const res = await fetch(`/api/documents/${documentId}/comments/${commentId}`, {
|
||||
const res = await csrfFetch(`/api/documents/${documentId}/comments/${commentId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content: text })
|
||||
@@ -138,7 +139,7 @@ async function deleteComment(commentId: string) {
|
||||
if (posting) return;
|
||||
posting = true;
|
||||
try {
|
||||
const res = await fetch(`/api/documents/${documentId}/comments/${commentId}`, {
|
||||
const res = await csrfFetch(`/api/documents/${documentId}/comments/${commentId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (res.ok) {
|
||||
|
||||
Reference in New Issue
Block a user