1b1a679827
REST-слой Фазы 1 на huma (OpenAPI 3.1 генерируется из кода):
- профиль: GET/PATCH /users/@me, смена пароля со step-up, публичный профиль,
завершение онбординга (новая миграция 00003 с onboarding_completed_at);
- серверы: создание/изменение/удаление, join/leave, список серверов
пользователя, журнал действий;
- комнаты: список с учётом прав, создание/изменение/удаление;
- участники: список с профилями и ролями, никнейм, тайм-аут, исключение;
- роли: CRUD, выдача/снятие с проверкой иерархии и запретом выдачи прав выше
собственных;
- админ инстанса: публичная информация, настройки, серверы, пользователи,
аудит, выдача прав администратора со step-up; обход лимитов фиксируется в
аудите отдельной записью limits.bypass;
- движок прав: участие в сервере стало обязательным условием (IsMember),
не участник не получает прав роли @user; калькулятор прав общий для API и
Gateway, инвалидация кэша после изменений;
- Gateway: браузерный клиент аутентифицируется cookie на рукопожатии, IDENTIFY
без токена использует её; события GUILD/CHANNEL/MEMBER/ROLE рассылаются из
ручек, USER_UPDATE — адресно;
- ошибки huma отдаются в едином конверте {"error":{"code","message"}}.
Тесты: 8 сценариев API (профиль, жизненный цикл сервера и права, лимиты и
обход админом, иерархия ролей, тайм-аут, скрытие комнаты оверрайдом,
членство в движке прав, cookie-идентификация Gateway).
84 lines
2.8 KiB
Go
84 lines
2.8 KiB
Go
package permissions
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// Channel вычисляет права пользователя в конкретной комнате: базовые права
|
|
// сервера, затем оверрайды ролей, затем оверрайд пользователя (AGENT.md 6.2).
|
|
func (c *Calculator) Channel(ctx context.Context, guildID, channelID, userID uint64, instanceAdmin bool) (Resolved, error) {
|
|
if cached, ok := c.cache.GetChannel(channelID, userID); ok {
|
|
cached.IsInstanceAdmin = instanceAdmin
|
|
return cached, nil
|
|
}
|
|
|
|
resolved, err := c.Guild(ctx, guildID, userID, instanceAdmin)
|
|
if err != nil {
|
|
return Resolved{}, err
|
|
}
|
|
resolved.Channel = resolved.Guild
|
|
if !resolved.IsMember && !resolved.IsInstanceAdmin {
|
|
// Не участник не видит ничего, даже если есть персональный оверрайд.
|
|
c.cache.PutChannel(channelID, userID, resolved)
|
|
return resolved, nil
|
|
}
|
|
if resolved.IsInstanceAdmin || resolved.Guild.Has(Administrator) {
|
|
c.cache.PutChannel(channelID, userID, resolved)
|
|
return resolved, nil
|
|
}
|
|
|
|
overrides, err := c.source.ChannelOverrides(ctx, channelID)
|
|
if err != nil {
|
|
return Resolved{}, fmt.Errorf("load channel overrides: %w", err)
|
|
}
|
|
roles, err := c.source.GuildRoles(ctx, guildID)
|
|
if err != nil {
|
|
return Resolved{}, fmt.Errorf("load guild roles: %w", err)
|
|
}
|
|
memberRoleIDs, err := c.source.MemberRoleIDs(ctx, guildID, userID)
|
|
if err != nil {
|
|
return Resolved{}, fmt.Errorf("load member roles: %w", err)
|
|
}
|
|
|
|
assigned := make(map[uint64]bool, len(memberRoleIDs)+1)
|
|
for _, roleID := range memberRoleIDs {
|
|
assigned[roleID] = true
|
|
}
|
|
defaultRoleID := uint64(0)
|
|
for _, role := range roles {
|
|
if role.IsDefault {
|
|
defaultRoleID = role.ID
|
|
assigned[role.ID] = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Порядок как в AGENT.md 6.2: базовые права роли @user → оверрайды ролей →
|
|
// персональный оверрайд пользователя.
|
|
denied, allowed := Permission(0), Permission(0)
|
|
for _, override := range overrides {
|
|
if override.TargetType != "role" {
|
|
continue
|
|
}
|
|
if override.TargetID == defaultRoleID || assigned[override.TargetID] {
|
|
denied |= override.Deny
|
|
allowed |= override.Allow
|
|
}
|
|
}
|
|
computed := (resolved.Guild &^ denied) | allowed
|
|
|
|
// Персональный deny обязан перебить ролевой allow, поэтому применяется
|
|
// к уже вычисленному результату.
|
|
var userDeny, userAllow Permission
|
|
for _, override := range overrides {
|
|
if override.TargetType == "user" && override.TargetID == userID {
|
|
userDeny |= override.Deny
|
|
userAllow |= override.Allow
|
|
}
|
|
}
|
|
resolved.Channel = (computed &^ userDeny) | userAllow
|
|
c.cache.PutChannel(channelID, userID, resolved)
|
|
return resolved, nil
|
|
}
|