# TOCTOU race condition при регистрации пользователя **Контекст**: В `backend/internal/services/auth.go:85-86`, метод `Register` сначала проверяет существование email, а потом создаёт пользователя. Между этими двумя операциями возможна гонка. ## Проблема ```go func (s *AuthService) Register(ctx context.Context, input RegisterInput) (*AuthResult, string, error) { // Шаг 1: Check (Time-of-Check) existing, _ := s.userRepo.GetByEmail(ctx, input.Email) // ❌ ошибка подавлена if existing != nil { return nil, "", ErrEmailExists } // ⚡ В этот момент другой запрос может создать пользователя с тем же email // Шаг 2: Act (Time-of-Use) if err := s.userRepo.Create(ctx, user); err != nil { return nil, "", fmt.Errorf("create user: %w", err) } } ``` **Две проблемы**: 1. **Подавление ошибки**: `_` игнорирует ошибку `GetByEmail`. Если БД недоступна — пользователь получит "email already exists" вместо ошибки сервера. 2. **TOCTOU race**: два одновременных запроса с одинаковым email могут пройти проверку и оба создать пользователя. ## Решение Использовать уникальный constraint на уровне БД + проверять ошибку `Create`: ```sql -- миграция (уже есть) ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email); ``` ```go func (s *AuthService) Register(ctx context.Context, input RegisterInput) (*AuthResult, string, error) { // ... хеширование пароля, создание User ... if err := s.userRepo.Create(ctx, user); err != nil { // Проверяем, что ошибка — нарушение уникального constraint if isDuplicateEmailError(err) { return nil, "", ErrEmailExists } return nil, "", fmt.Errorf("create user: %w", err) } return s.generateTokens(ctx, user) } ``` ```go // repository/users.go — проверка ошибки pgx func isDuplicateEmailError(err error) bool { var pgErr *pgconn.PgError return errors.As(err, &pgErr) && pgErr.Code == "23505" // unique_violation } ``` ## Альтернативы | Подход | Плюсы | Минусы | |---|---|---| | **Unique constraint + проверка ошибки** | Атомарно, стандартная практика | Зависит от кода ошибки pgx | | **Транзакция + FOR UPDATE** | Можно сделать доп. проверки | Блокировка строки, медленнее | | **Optimistic locking** | Нет блокировок | Больше кода | ## Связанные заметки - [[backend-auth-security]] — контекст auth сервиса - [[architecture-overview]] — таблица P0 ## Источник Code review PhotoPlaces 2026-06. `services/auth.go:85-86`. ## Теги #concurrency #database #security #golang #race-condition #best-practice