feat(api,install): исключения fail2ban и смена своего никнейма
- установщик: `[DEFAULT] ignoreip` в jail.local — локальные сети и WireGuard исключены всегда, свой адрес добавляется флагом `--ignore-ip <ip|cidr>`; админ, часто заходящий в панель, больше не банит сам себя; - участник может менять свой никнейм на сервере без MANAGE_NICKNAMES и без проверки иерархии (тайм-аут на себя по-прежнему запрещён); - тест на смену своего ника и запрет self-тайм-аута.
This commit is contained in:
@@ -729,21 +729,29 @@ func (s *Server) registerMemberRoutes(api huma.API) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.checkMemberHierarchy(ctx, guildID, user, resolved, targetID); err != nil {
|
||||
return nil, err
|
||||
// Свой никнейм участник меняет сам: иерархия и MANAGE_NICKNAMES для
|
||||
// этого не нужны (AGENT.md 7.2, 7.17). Тайм-аут на себя — запрещён.
|
||||
selfEdit := targetID == user.ID
|
||||
if !selfEdit {
|
||||
if err := s.checkMemberHierarchy(ctx, guildID, user, resolved, targetID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var (
|
||||
nickname *string
|
||||
timeoutUntil *time.Time
|
||||
)
|
||||
if input.Body.Nickname != nil {
|
||||
if !resolved.Has(permissions.ManageNicknames) {
|
||||
if !selfEdit && !resolved.Has(permissions.ManageNicknames) {
|
||||
return nil, humaErrorStatus(http.StatusForbidden, "perm.denied", "MANAGE_NICKNAMES is required")
|
||||
}
|
||||
trimmed := strings.TrimSpace(*input.Body.Nickname)
|
||||
nickname = &trimmed
|
||||
}
|
||||
if input.Body.TimeoutUntil != nil {
|
||||
if selfEdit {
|
||||
return nil, humaErrorStatus(http.StatusForbidden, "perm.denied", "you cannot time yourself out")
|
||||
}
|
||||
if !resolved.Has(permissions.TimeoutMembers) {
|
||||
return nil, humaErrorStatus(http.StatusForbidden, "perm.denied", "TIMEOUT_MEMBERS is required")
|
||||
}
|
||||
|
||||
@@ -452,6 +452,43 @@ func TestInstanceAdminEndpointsAndLimits(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCanChangeOwnNickname(t *testing.T) {
|
||||
srv, _ := newTestServer(t)
|
||||
ownerCookie := registerAndLogin(t, srv, "nick_owner", "nick-owner@example.com")
|
||||
memberCookie := registerAndLogin(t, srv, "nick_member", "nick-member@example.com")
|
||||
|
||||
created := doJSON(t, srv, http.MethodPost, "/api/v1/guilds", `{"name":"Ники"}`, ownerCookie)
|
||||
guild := decodeResponse[struct {
|
||||
Guild struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"guild"`
|
||||
}](t, created)
|
||||
doJSON(t, srv, http.MethodPost, "/api/v1/guilds/"+guild.Guild.ID+"/join", "", memberCookie)
|
||||
|
||||
member, err := srv.auth.UserByEmail(t.Context(), "nick-member@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("UserByEmail: %v", err)
|
||||
}
|
||||
self := doJSON(t, srv, http.MethodPatch,
|
||||
"/api/v1/guilds/"+guild.Guild.ID+"/members/"+formatSnowflake(member.ID),
|
||||
`{"nickname":"себе ник"}`, memberCookie)
|
||||
if self.Code != http.StatusOK {
|
||||
t.Fatalf("self nickname = %d, body = %s", self.Code, self.Body.String())
|
||||
}
|
||||
|
||||
// Тайм-аут на себя запрещён даже владельцу.
|
||||
owner, err := srv.auth.UserByEmail(t.Context(), "nick-owner@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("UserByEmail: %v", err)
|
||||
}
|
||||
timeout := doJSON(t, srv, http.MethodPatch,
|
||||
"/api/v1/guilds/"+guild.Guild.ID+"/members/"+formatSnowflake(owner.ID),
|
||||
`{"timeout_until":"2030-01-01T00:00:00Z"}`, ownerCookie)
|
||||
if timeout.Code != http.StatusForbidden {
|
||||
t.Fatalf("self timeout = %d, want 403", timeout.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberTimeoutRequiresPermission(t *testing.T) {
|
||||
srv, _ := newTestServer(t)
|
||||
ownerCookie := registerAndLogin(t, srv, "timeout_owner", "timeout-owner@example.com")
|
||||
|
||||
Reference in New Issue
Block a user