Skip to content

Member timeout (communication_disabled_until) silently dropped — not persisted or enforced #33

Description

@krazyjakee

Summary

Member timeout (a.k.a. moderation timeout / communication_disabled_until) is not functional end to end. The Flutter client and the MCP both send the request correctly, but the server silently ignores it: the field is never deserialized, never written to the DB, and never enforced. The PATCH returns 200 OK with the member unchanged, so the client believes the timeout succeeded (a false success — worse than an error).

The DB already has a timed_out_until column; it is read back in SELECTs and serialized in member responses, but nothing ever sets it.

How the client uses it

The client (and MCP timeout_member) call:

PATCH /spaces/{space_id}/members/{user_id}
{ "communication_disabled_until": "2026-06-14T12:00:00Z" }   // or null to clear

It gates the UI on the moderate_members permission and renders a "timed out" badge from timed_out_until in the member object.

Server gaps

  1. Field is dropped on deserialize. UpdateMember (src/models/member.rs:34-40) only declares nickname, avatar, roles, mute, deaf. There is no communication_disabled_until / timed_out_until, so serde discards the unknown field and update_member proceeds as a no-op for timeouts (src/routes/members.rs:102).
  2. DB never writes it. db::members::update_member (src/db/members.rs:121) only emits UPDATEs for nickname/avatar/roles/mute/deaf. No code path assigns timed_out_until.
  3. No permission gate for timeout. update_member checks manage_nicknames / manage_roles / mute_members / deafen_members, but never moderate_members for a timeout. (The permission exists and is used by src/routes/reports.rs.)
  4. No enforcement. src/routes/messages.rs has zero timeout checks — a timed-out user can still send messages (and should also be blocked from reactions, typing, voice connect, etc.).

Proposed fix

  • Add communication_disabled_until: Option<Option<String>> (or equivalent nullable) to UpdateMember, accepting both communication_disabled_until and timed_out_until as input keys; an explicit null clears the timeout.
  • Gate setting/clearing it behind the moderate_members permission (plus the existing require_hierarchy check so you can't time out someone above you).
  • Persist it in db::members::update_member (UPDATE members SET timed_out_until = ? ...), or add a dedicated route if preferred.
  • Enforce it: reject message create (and ideally reactions, typing, voice connect) with a 403 while timed_out_until is in the future. Treat past/expired timestamps as not-timed-out.
  • The member.update gateway broadcast already fires after update_member (src/routes/members.rs:170-185), so clients will reflect the change once it's persisted — no gateway work needed.

Acceptance criteria

  • PATCH /spaces/{id}/members/{user} with communication_disabled_until persists timed_out_until and returns the updated value.
  • Setting/clearing requires moderate_members (+ hierarchy); unauthorized callers get 403.
  • A timed-out member is blocked from sending messages (403) until the timeout expires.
  • Passing null clears the timeout.

Related

  • Client UI: daccord lib/features/member/views/accord_member_popout.dart
  • MCP tool: daccord lib/features/developer/services/mcp_tools.dart (timeout_member)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions