Przeglądaj źródła

docs: Obsidian note on 429 deauth loop fix

neyrogovnarik 1 miesiąc temu
rodzic
commit
fe453a8058

+ 74 - 0
obsidian_data/Photoplaces_data/atomic-rate-limit-429-deauth-loop.md

@@ -0,0 +1,74 @@
+# 429 Too Many Requests как причина деавторизации
+
+**Контекст:** При быстрых F5 (3-4 раза) на `/places/my` пользователя деавторизует — `/auth/me` начинает стабильно падать с 401.
+
+## Суть
+
+Коренная причина: **write-лимитер (burst=10) исчерпывается быстрее, чем refresh-механизм успевает восстановить сессию.**
+
+### Схема race при старом коде
+
+1. Page load → `refreshSession()` вызывает `/auth/me` **без Bearer** → 401
+2. Page load → `fetchPlaces()` вызывает `/places/my` **без Bearer** → 401
+3. Оба 401 попадают в `request()` → `catch` → `refreshAccessToken()` + ретрай с Bearer
+4. Ретрай `/auth/me` с Bearer → 200 (write limiter: +1)
+5. Ретрай `/places/my` с Bearer → 200 (write limiter: +1)
+6. **Итого на 1 page load: 2 запроса к write-лимитеру.** Burst=10 → исчерпан за 5 page loads
+
+### Дополнительный эффект: каскад 401 → 429
+
+Когда burst исчерпан:
+- `/auth/me` → 429 (Too Many Requests)
+- `request()` в `catch` → деавторизация
+- Пользователь видит "Войти"
+
+## Исправления
+
+### 1. `refreshSession` — сначала refresh, потом `/auth/me`
+
+`frontend/src/hooks/useAuth.tsx`:
+```ts
+// было:
+const res = await api.get<User>('/auth/me')
+// стало:
+const newToken = await refreshAccessToken()
+setAccessToken(newToken)
+const res = await api.get<User>('/auth/me')
+```
+
+Теперь `/auth/me` приходит с Bearer с первого раза — ни 401, ни ретрая. Сокращает write-лимитер с 2 до 1 хита на page load.
+
+### 2. Экспорт `refreshAccessToken` из `api.ts`
+
+Нужен для прямого вызова из `refreshSession()`.
+
+### 3. 429 retry в `request()`
+
+`frontend/src/lib/api.ts`:
+```ts
+if (res.status === 429 && !isRetry) {
+    await new Promise(r => setTimeout(r, 1000))
+    return request<T>(path, options, true)
+}
+```
+
+### 4. Увеличение write-лимитера 10 → 60 req/min
+
+- `backend/cmd/api/main.go` (in-memory fallback): `10/60.0, 10` → `60/60.0, 60`
+- `backend/internal/middleware/ratelimit_redis.go` (Redis): `Limit: 10` → `Limit: 60`
+
+Файлы:
+- `frontend/src/hooks/useAuth.tsx:38-41`
+- `frontend/src/lib/api.ts:19` (экспорт)
+- `frontend/src/lib/api.ts:72-75` (429 retry)
+- `backend/cmd/api/main.go:149`
+- `backend/internal/middleware/ratelimit_redis.go:146`
+
+## Связанные заметки
+
+- [[atomic-refresh-token-race-condition]]
+- [[atomic-cookie-secure-via-protocol]]
+- [[atomic-fetch-retry-infinite-loop]]
+- [[atomic-redis-rate-limiter-failopen-failclosed]]
+
+#auth #rate-limiting #frontend #backend #bugfix