fix(ui): verify and create must use the same token, not just the freshest each

The ref fix made router creation use whichever token is current when the create call fires, but the preceding availability check still went through the query's own refetch, which stays bound to whatever token was current when that render's useQuery was set up. If the token rotated in between, that split verification from a stale caller's model list against creation under a different one, meaning the caller actually creating the router never had its own access checked. Fetch directly against accessTokenRef in the verification step too, so both calls agree on the same live identity instead of each independently chasing "freshest."
This commit is contained in:
Tin Chi Lo 2026-08-03 18:48:23 -07:00
parent 6ae0be8874
commit 8a30af69d0

View file

@ -7,7 +7,7 @@ import { all_admin_roles } from "@/utils/roles";
import { type ModelWriteScope } from "@/utils/modelPermissions";
import TeamDropdown from "../common_components/team_dropdown";
import { handleAddAutoRouterSubmit } from "./handle_add_auto_router_submit";
import { fetchAvailableModels } from "@/components/llm_calls/fetch_models";
import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_models";
import ComplexityRouterConfig, {
ComplexityRouterConfigValue,
DEFAULT_ADAPTIVE_WEIGHTS,
@ -204,13 +204,20 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
// background refetch failure keeps trusting the stale cache by design - see modelsUnverifiable
// above). The backend does not re-check a router's referenced model names against the caller's
// access either, so this is the only place that can catch it: force a fresh fetch right before
// creating the router, rather than trusting whatever's cached.
// creating the router, rather than trusting whatever's cached. Fetch directly against
// accessTokenRef instead of the query's own refetch, which stays bound to whichever token was
// current when this render's useQuery was set up - using it here could verify one caller's
// models and create the router under another if the token rotates mid-check.
const verifyPresetStillAvailable = async (presetKey: string): Promise<boolean> => {
const preset = getPresetByKey(presetKey);
if (!preset) return false;
const { data: freshModels, isError: freshError } = await refetchModels();
if (freshError) return false;
const freshSet = new Set((freshModels ?? []).map((m) => m.model_group));
let freshModels: ModelGroup[];
try {
freshModels = await fetchAvailableModels(accessTokenRef.current);
} catch {
return false;
}
const freshSet = new Set(freshModels.map((m) => m.model_group));
return getMissingModelsInPreset(preset, freshSet).length === 0;
};