Контекст: В frontend/src/lib/api.ts функция request<T> имела параметр-флаг isRetry, защищающий от повторного входа в ветку обновления токена. А параллельная функция requestFormData<T> (загрузка файлов через multipart) проверяла только res.status === 401 && accessToken, без флага.
Суть: Если после успешного refresh сервер снова отдаёт 401 (токен отозван/невалиден сразу), условие остаётся истинным → новый refresh → повторная загрузка → 401 → … Получается плотный цикл запросов к /auth/refresh и upload-эндпоинту (фактически self-DoS клиента и сервера).
Решение: Прокинуть isRetry в requestFormData, как в request:
async function requestFormData<T>(path, formData, isRetry = false): Promise<T> {
// ...
if (res.status === 401 && !isRetry && accessToken) {
const newToken = await refreshAccessToken()
accessToken = newToken
return requestFormData<T>(path, formData, true) // только один повтор
}
}
Правило: любой код «повтори запрос после обновления токена» обязан иметь однократный guard от рекурсии. Это должно быть единообразно во всех путях запроса (JSON и multipart).
Связанные заметки: [[frontend-api-client]], [[atomic-api-contract-refresh-cookie]]
Источник: Code review 2026-06-30 (neyrogovnarik), P1 fix в lib/api.ts
#frontend #typescript #auth #best-practice #bug