Files
pipu-music/services/api/internal/auth/store.go
T
mrleo1nidandClaude Opus 5.5 74da457362 feat(auth): make upload review a separate grant instead of an admin privilege
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>
2026-10-21 10:07:05 +03:00

113 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()) }