security(docs): prevent stale projection rendering

This commit is contained in:
2026-07-21 19:15:03 +02:00
parent 7cdd1e1c78
commit 901baf8352

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { ChevronDown, ChevronRight, RefreshCw } from "lucide-react"; import { ChevronDown, ChevronRight, RefreshCw } from "lucide-react";
import { import {
@@ -67,20 +67,29 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
const [context, setContext] = useState<DocsContext | null>(null); const [context, setContext] = useState<DocsContext | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
const loadSequence = useRef(0);
const [documentationType, setDocumentationType] = useState<DocumentationType>(() => documentationTypeFromSearch(location.search)); const [documentationType, setDocumentationType] = useState<DocumentationType>(() => documentationTypeFromSearch(location.search));
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(() => new Set()); const [expandedNodes, setExpandedNodes] = useState<Set<string>>(() => new Set());
const locale = localeFromSearch(location.search) ?? language; const locale = localeFromSearch(location.search) ?? language;
const adminDocs = documentationType === "admin"; const adminDocs = documentationType === "admin";
async function load() { async function load() {
const sequence = ++loadSequence.current;
setLoading(true); setLoading(true);
setError(""); setError("");
setContext(null);
try { try {
setContext(await fetchDocsContext(settings, { documentationType, locale })); const nextContext = await fetchDocsContext(settings, { documentationType, locale });
if (sequence !== loadSequence.current) return;
if (nextContext.actor.documentation_type !== documentationType) {
throw new Error("Documentation response type did not match the requested projection.");
}
setContext(nextContext);
} catch (err) { } catch (err) {
if (sequence !== loadSequence.current) return;
setError(adminErrorMessage(err)); setError(adminErrorMessage(err));
} finally { } finally {
setLoading(false); if (sequence === loadSequence.current) setLoading(false);
} }
} }