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 } // 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) InsertAudit(ctx context.Context, id uuid.UUID, e AuditEntry) 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()) }