|
|
@@ -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
|