feat(validation): implement validateAnnotationStrokes function for safe annotation data handling

feat(rate-limit): add TRUSTED_PROXY_MODE for configurable proxy header trust
feat(comments): validate annotation data structure in comment routes and components
This commit is contained in:
Yusuf İpek
2026-04-10 20:38:22 +03:00
parent 03bfd565e8
commit 439e74d037
8 changed files with 134 additions and 29 deletions
+5 -2
View File
@@ -13,6 +13,7 @@ import { VideoPageLoading } from '@/components/video-page/video-page-loading';
import { VideoPageError } from '@/components/video-page/video-page-error';
import { GuestNameGate } from '@/components/video-page/guest-name-gate';
import { useCommentMedia } from '@/components/video-page/hooks/use-comment-media';
import { validateAnnotationStrokes } from '@/lib/validation';
import { useVersionActions } from '@/components/video-page/hooks/use-version-actions';
import { useWatchProgress } from '@/components/video-page/hooks/use-watch-progress';
import { useVideoPlayer } from '@/components/video-page/hooks/use-video-player';
@@ -498,7 +499,8 @@ export function VideoPageContent({
const editAnnotationInitialStrokes = useMemo<AnnotationStroke[] | undefined>(() => {
if (editAnnotationData) {
try {
return JSON.parse(editAnnotationData) as AnnotationStroke[];
const parsed = JSON.parse(editAnnotationData);
return (validateAnnotationStrokes(parsed) as AnnotationStroke[] | null) ?? undefined;
} catch {
return undefined;
}
@@ -507,7 +509,8 @@ export function VideoPageContent({
const editingComment = comments.find((comment) => comment.id === editingCommentId);
if (!editingComment?.annotationData) return undefined;
try {
return JSON.parse(editingComment.annotationData) as AnnotationStroke[];
const parsed = JSON.parse(editingComment.annotationData);
return (validateAnnotationStrokes(parsed) as AnnotationStroke[] | null) ?? undefined;
} catch {
return undefined;
}
@@ -16,6 +16,7 @@ import { toast } from 'sonner';
import type { AnnotationCanvasHandle, AnnotationStroke } from '@/components/annotation-canvas';
import type { Comment, CommentActionsConfig, CommentTag, Version, VideoData } from '@/components/video-page/types';
import { extractPastedImageFile, validateImageFile } from '@/components/video-page/image-upload-utils';
import { validateAnnotationStrokes } from '@/lib/validation';
interface UseCommentActionsParams extends CommentActionsConfig {
setVideo: Dispatch<SetStateAction<VideoData | null>>;
@@ -867,7 +868,9 @@ export function useCommentActions({
setIsEditingAnnotation(false);
if (finalAnnotationData !== undefined && finalAnnotationData) {
try {
setViewingAnnotation(JSON.parse(finalAnnotationData));
const parsed = JSON.parse(finalAnnotationData);
const safe = validateAnnotationStrokes(parsed);
if (safe) setViewingAnnotation(safe as AnnotationStroke[]);
} catch {
// ignore parse errors
}
@@ -11,6 +11,7 @@ import type {
PlayerAdapter,
Version,
} from '@/components/video-page/types';
import { validateAnnotationStrokes } from '@/lib/validation';
interface UseVideoPlayerParams {
activeVersion: Version | undefined;
@@ -765,8 +766,9 @@ export function useVideoPlayer({
}
if (annotation) {
try {
const strokes = JSON.parse(annotation) as AnnotationStroke[];
setViewingAnnotation(strokes);
const parsed = JSON.parse(annotation);
const safe = validateAnnotationStrokes(parsed);
setViewingAnnotation(safe as AnnotationStroke[] | null);
} catch {
setViewingAnnotation(null);
}