package auth import ( "context" "errors" "time" "github.com/google/uuid" ) // Ошибки хранилища. var ( ErrNotFound = errors.New("auth: not found") ErrUsernameTaken = errors.New("auth: username taken") ) // ProfileUpdate — изменяемые пользователем поля профиля. nil — не менять. type ProfileUpdate struct { DisplayName **string // *nil — сбросить имя Locale *string UIPrefs *UIPrefs } // UserFilter — параметры списка пользователей. type UserFilter struct { Query string // подстрока логина или имени Role *Role After string // логин последнего элемента предыдущей страницы Limit int } // UserUpdate — изменения из админки. nil — не менять. type UserUpdate struct { DisplayName **string Role *Role IsBlocked *bool RevokeTokens bool } // Store — доступ к users, refresh_tokens, audit_log и app_settings. type Store interface { // WithTx выполняет fn в транзакции; Store внутри fn работает в ней же. WithTx(ctx context.Context, fn func(Store) error) error CountAdmins(ctx context.Context) (int, error) CreateUser(ctx context.Context, u User, createdBy *uuid.UUID) error GetUserByID(ctx context.Context, id uuid.UUID) (User, error) GetUserByUsername(ctx context.Context, username string) (User, error) RecordLoginFailure(ctx context.Context, id uuid.UUID, failedCount int, lockedUntil *time.Time) error // RecordLoginSuccess сбрасывает счётчик неудач; newHash != nil — пересчитанный хэш пароля. RecordLoginSuccess(ctx context.Context, id uuid.UUID, at time.Time, newHash *string) error // SetPassword меняет хэш, снимает must_change_password и увеличивает token_version. SetPassword(ctx context.Context, id uuid.UUID, hash string) error // SetCredentials меняет логин и хэш, снимает must_change_credentials и увеличивает token_version. SetCredentials(ctx context.Context, id uuid.UUID, username, hash string) error UpdateProfile(ctx context.Context, id uuid.UUID, upd ProfileUpdate) (User, error) InsertRefreshToken(ctx context.Context, t RefreshToken) error // GetRefreshTokenForUpdate находит токен по хэшу и блокирует строку до конца транзакции. GetRefreshTokenForUpdate(ctx context.Context, hash []byte) (RefreshToken, error) MarkRefreshTokenUsed(ctx context.Context, id uuid.UUID, at time.Time) error RevokeFamily(ctx context.Context, familyID uuid.UUID, at time.Time) error // RevokeUserSessions отзывает все семьи пользователя. RevokeUserSessions(ctx context.Context, userID uuid.UUID, at time.Time) error // ListSessions — активные семьи пользователя: есть неиспользованный, неотозванный, не истёкший токен. ListSessions(ctx context.Context, userID uuid.UUID, now time.Time) ([]Session, error) // ListUsers — страница пользователей по возрастанию логина, после логина f.After. ListUsers(ctx context.Context, f UserFilter) ([]User, error) // LockActiveAdmins блокирует строки активных админов до конца транзакции и возвращает их число: // два параллельных понижения не оставят систему без админа. LockActiveAdmins(ctx context.Context) (int, error) // UpdateUser применяет изменения админки; RevokeTokens увеличивает token_version. UpdateUser(ctx context.Context, id uuid.UUID, upd UserUpdate) (User, error) // ResetPassword ставит временный пароль: must_change_password, сброс блокировки входа, token_version + 1. ResetPassword(ctx context.Context, id uuid.UUID, hash string) error DeleteUser(ctx context.Context, id uuid.UUID) error // ResetRecommendations стирает то, из чего рекомендатор строит вкус: профиль (онбординг, CF, бандит), // агрегаты прослушиваний и показы волны. Лайки, дизлайки и история остаются. ResetRecommendations(ctx context.Context, id uuid.UUID) error InsertAudit(ctx context.Context, id uuid.UUID, e AuditEntry) error // ListAudit — записи журнала новее-первыми, с id меньше before (uuid.Nil — с начала). ListAudit(ctx context.Context, before uuid.UUID, limit int) ([]AuditRecord, error) GetSettingBool(ctx context.Context, key string) (bool, error) SetSetting(ctx context.Context, key string, value any) error } // Limiter ограничивает частоту попыток входа. type Limiter interface { // Allow учитывает попытку по ключу. retryAfter — секунды до сброса окна, если попытка отклонена. Allow(ctx context.Context, key string, limit int, window time.Duration) (ok bool, retryAfter int, err error) } // Clock — источник времени (подменяется в тестах). type Clock interface{ Now() time.Time } // IDGen — генератор UUIDv7. type IDGen interface{ New() uuid.UUID } // SystemClock — реальное время в UTC. type SystemClock struct{} // Now возвращает текущее время. func (SystemClock) Now() time.Time { return time.Now().UTC() } // UUIDv7 — генератор сортируемых по времени UUID. type UUIDv7 struct{} // New возвращает новый UUIDv7. func (UUIDv7) New() uuid.UUID { return uuid.Must(uuid.NewV7()) }