Reviewing the upload queue is now a per-user flag (reviews_uploads), not part of the admin role: several people can hold it, a moderator can have it, and an admin may not. Only admins grant or revoke it; existing admins keep it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
5.9 KiB
Go
113 lines
5.9 KiB
Go
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
|
||
ReviewsUploads *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()) }
|