0.7.0

Added

  • Миграции 018_oauth_sessions и 019_refresh_token_revocation (SQLite + PG)
  • Таблица oauth_sessions(id, user_id, expires_at, created_at) для серверного хранения коротких OAuth-сессий
  • Колонка refresh_tokens.revoked_at (soft-revoke), индекс idx_refresh_tokens_family
  • Тип store.OAuthSession и методы CreateOAuthSession/GetOAuthSession/DeleteOAuthSession/DeleteExpiredOAuthSessions
  • Тесты криптографии аутентификации: internal/auth/tokens_test.go (HS256-only, alg=none/HS512 rejection, tampered payload), internal/auth/oauth_test.go (refresh happy-path, reuse-detection с family revoke, family isolation), internal/server/handler/auth_test.go (forged cookie, random session ID, legitimate session, cookie flags)

Changed

  • JWT-подпись переведена на crypto/hmac.New(sha256.New, secret) вместо sha256(data || secret). ValidateAccessToken парсит header и принимает только alg=HS256. Сравнение подписи через hmac.Equal (constant-time)
  • Cookie tc_session теперь хранит только opaque session ID (32 байта random); содержимое (user_id, expires_at) — в БД. Cookie выставляется с HttpOnly, Secure (в prod), SameSite=Strict, MaxAge=5min
  • Refresh family-rotation: generateFamilyID() создаёт случайный 32-байтный family ID при первичном login; при refresh новый токен наследует family; повторное использование revoked токена → revoke всей семьи (reuse-detection)
  • RevokeRefreshToken/RevokeRefreshTokenFamily теперь делают UPDATE revoked_at, а не DELETE — запись сохраняется для обнаружения reuse-attack
  • Singleton refresh promise в web/src/lib/api/client.ts: одновременные 401 делят один refresh-вызов, чтобы избежать ложного reuse-detect и потери семьи
  • request/rawRequest в client.ts отрефакторены в общие helpers buildHeaders/buildInit/parseError; парсинг ошибок проверяет Content-Type и не падает на не-JSON ответах

Fixed

  • C-1: JWT length-extension и отсутствие проверки alg (любой токен с alg=none или alg=HS512 мог проходить как валидный)
  • C-2: подделка cookie tc_session = {"user_id":N} для входа за любого пользователя
  • H-4: Family = hashToken(refreshToken) ломал reuse-detection (отзывался только сам токен)
  • H-5: гонка параллельных tryRefresh на фронте, ложно вызывавшая reuse-detect и revoke всей семьи