Files
pipu-music/services/api/internal/auth/policy.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

104 lines
4.4 KiB
Go

package auth
import "github.com/google/uuid"
// Матрица прав из docs/auth-and-roles.md. Проверяется в сервисном слое, а не только в middleware.
// Permission — право, не зависящее от целевого пользователя.
type Permission int
// Глобальные права.
const (
PermAdminPanel Permission = iota // открыть админку
PermListUsers // видеть список пользователей
PermManageLibrary // хранилища, сканирование, переиндексация
PermViewLibraryStatus // статус библиотеки (просмотр)
PermSuggestTracks // предлагать треки, видеть свои предложения
PermReviewUploads // очередь, одобрение, правка метаданных; даёт флаг, не роль
PermSystemStatus // статус рекомендательной системы, аудит
)
var permissions = map[Permission][]Role{
PermAdminPanel: {RoleModerator, RoleAdmin},
PermListUsers: {RoleModerator, RoleAdmin},
PermManageLibrary: {RoleAdmin},
PermViewLibraryStatus: {RoleModerator, RoleAdmin},
PermSuggestTracks: {RoleModerator, RoleAdmin},
PermSystemStatus: {RoleAdmin},
}
// Can сообщает, есть ли у роли глобальное право.
// Аудит загрузок сюда не входит: его проверяет Principal.Allowed.
func Can(r Role, p Permission) bool {
for _, allowed := range permissions[p] {
if allowed == r {
return true
}
}
return false
}
// Allowed — право конкретного пользователя. Аудит загрузок не вытекает из роли admin.
func (p Principal) Allowed(perm Permission) bool {
if perm == PermReviewUploads {
return p.ReviewsUploads
}
return Can(p.Role, perm)
}
// UserAction — действие над учётной записью другого пользователя.
type UserAction int
// Действия над пользователями.
const (
UserCreate UserAction = iota // создать пользователя с ролью target
UserEdit // изменить отображаемое имя
UserResetPassword // сбросить пароль
UserBlock // заблокировать / разблокировать
UserChangeRole // сменить роль (target — текущая роль)
UserDelete // удалить
UserResetRecommendations // сбросить профиль рекомендаций
)
// Actor — кто действует (или над кем). ID нужен, чтобы запретить действия над собой.
type Actor struct {
ID uuid.UUID
Role Role
}
// CanManageUser — может ли actor выполнить action над target.
// Модератор управляет только обычными пользователями; роли и удаление — только админ.
// Блокировать, удалять себя и менять себе роль нельзя никому: так не потерять доступ случайно.
// Правило «последний админ» зависит от данных и проверяется в сервисе.
func CanManageUser(actor, target Actor, action UserAction) bool {
self := actor.ID != uuid.Nil && actor.ID == target.ID
if self && (action == UserBlock || action == UserDelete || action == UserChangeRole) {
return false
}
switch actor.Role {
case RoleAdmin:
return true
case RoleModerator:
switch action {
case UserCreate, UserEdit, UserResetPassword, UserBlock, UserResetRecommendations:
return target.Role == RoleUser
default:
return false
}
default:
return false
}
}
// CanAssignRole — может ли actor выдать роль newRole (при создании или смене роли).
func CanAssignRole(actor Role, newRole Role) bool {
switch actor {
case RoleAdmin:
return newRole.Valid()
case RoleModerator:
return newRole == RoleUser
default:
return false
}
}