Kaynağa Gözat

fix: grace period reuse не должен кидать ErrTokenReused

При повторном использовании старого токена (после потери
Set-Cookie из-за F5) ReplaceIfExists возвращает false,
т.к. replaced_at уже установлен. Вместо ErrTokenReused
(который сбрасывает все сессии) — revoke одного токена
и создание новой пары.
neyrogovnarik 1 ay önce
ebeveyn
işleme
cd10d4d914
1 değiştirilmiş dosya ile 12 ekleme ve 0 silme
  1. 12 0
      backend/internal/services/auth.go

+ 12 - 0
backend/internal/services/auth.go

@@ -239,6 +239,18 @@ func (s *AuthService) RefreshSession(ctx context.Context, plainRefreshToken stri
 		return nil, "", fmt.Errorf("replace old refresh token: %w", err)
 	}
 	if !replaced {
+		// Token was already replaced (grace period after lost cookie / fast F5).
+		// Not theft — browser didn't receive the new cookie. Revoke this specific
+		// token to prevent further reuse, then issue fresh tokens.
+		if storedToken.ReplacedAt != nil {
+			logger.InfoContext(ctx, "grace period token reuse, issuing new tokens",
+				slog.String("user_id", user.ID),
+				slog.Time("replaced_at", *storedToken.ReplacedAt))
+			if revokeErr := s.refreshTokenRepo.Revoke(ctx, storedToken.TokenHash); revokeErr != nil {
+				logger.ErrorContext(ctx, "failed to revoke grace-period token", log.WithError(revokeErr))
+			}
+			return s.generateTokens(ctx, user)
+		}
 		logger.WarnContext(ctx, "concurrent token rotation detected", slog.String("user_id", user.ID))
 		return nil, "", ErrTokenReused
 	}