feat(28): drag and drop users between voice channels#750
Conversation
|
I would argue this function should be more moderator-override than permission-validation. In the current model, if a moderator wants to bring a guest into a private room to fill up a team in a game, they must go to role settings, find the guests role or create a temporary one, modify permissions to allow VIEW_CHANNEL and JOIN_VOICE for that specific channel. And remove those permissions when no longer needed. This creates administrative overhead/friction, and could potentially lead to permission creep. Our community structure relies on a small, permanent core and a larger rotating group of guests. The problem: We want our private channels to remain strictly invisible/unjoinable to guests to maintain privacy and reduce clutter. Safety: The concern about security is mitigated by the fact that the action is gated by MOVE_MEMBERS. We are not giving users the ability to bypass permissions, we are giving trusted moderators the ability to easily facilitate communication for that event/game/etc. Proposed safety check: Instead of checking moved users permissions, we should validate that the moderator has the authority to see/join the destination channel. |
|
@pixelsdontmove This approach seems perfectly reasonable to me, and it's something I had already considered. That's why I added the "Decision to make before merge" section in the description, so that when Diogo reviews it, he can decide which approach he finds more convincing. If needed, I can change it without any problem. |
| const [canView, canJoin, canUseVoice] = await Promise.all([ | ||
| channelUserCan( | ||
| input.channelId, | ||
| input.userId, | ||
| ChannelPermission.VIEW_CHANNEL | ||
| ), | ||
| channelUserCan(input.channelId, input.userId, ChannelPermission.JOIN), | ||
| userCan(input.userId, Permission.JOIN_VOICE_CHANNELS) | ||
| ]); | ||
|
|
||
| invariant(canView && canJoin && canUseVoice, { | ||
| code: 'FORBIDDEN', | ||
| message: 'Target user cannot join the destination channel' | ||
| }); |
There was a problem hiding this comment.
In my opinion.
A moderator who moves should be able to move the user regardless of the user can see the voice channel or not.
My reason is sometimes you will properly have closed spaces and have to invite a person in to it. and if you cant move him up you have to change channel permissions, even if its only a one time thing. which makes it extra steps.
But you laying the groundwork for the permission rework when that happens. So if the user is higher Role (hierarchy) can not be moved. unless has same role. - Depending on what the team wants. - could also be done in server settings. But right now its me rambling :P
| invariant(currentRuntime.id !== input.channelId, { | ||
| code: 'BAD_REQUEST', | ||
| message: 'User is already in that channel' | ||
| }); |
There was a problem hiding this comment.
Fine to log as an attempt to move. But otherwise should not do a toast for simple as an attempt and show an error for nothing.
| try { | ||
| await client.voice.moveUser.mutate({ userId, channelId }); | ||
| } catch (error) { | ||
| toast.error(getTrpcError(error, 'Failed to move user')); |
There was a problem hiding this comment.
More checks on what type of error. as not all should be applied to a toast. like an simple attempt.
Closes #28
Closes #745
Summary
Adds the ability for a moderator to drag a user from one voice channel to another in the sidebar. Gated behind a new dedicated
MOVE_MEMBERSpermission.How it works
The server stays client-driven:
voice.moveUseronly validates and emits aUSER_VOICE_MOVEDevent targeted at the moved user; that user's client reuses the existingjoinVoice+initflow to switch channels. No mediasoup runtime is manipulated by hand, and every sidebar updates through the existingUSER_JOIN_VOICE/USER_LEAVE_VOICEevents.Changes
MOVE_MEMBERS(packages/shared) + translations in the 7 locales. The roles UI picks it up automatically from the enum.voice.moveUserroute +voice.onMovedsubscription,USER_VOICE_MOVEDevent, ownmoveMembersrate-limiter.moveUserToVoiceChannelaction,useReceiveVoiceMovehook, and native HTML5 drag-and-drop on voice users / channels.voice.moveUserinvoice.test.ts.Design decisions
MOVE_MEMBERSpermission instead of reusingMANAGE_USERS, so "move" can be granted without ban/kick power.@dnd-kitchannel/category reordering untouched (minimal change; works across categories). Unifying both under a singleDndContextis a possible follow-up.fromChannelId, and only the session actually in the origin channel acts on it.Decision to make before merge
Discord, when moving a user between voice channels, checks the mover's permissions, a moderator can drop someone into any channel regardless of whether that user could normally join it.
This PR instead respects the moved user's permissions: the server validates that the target can actually enter the destination,
VIEW_CHANNEL+JOIN(channel) andJOIN_VOICE_CHANNELS(server), mirroringvoice.join. If the moved user has no access to that voice channel, they can't be moved there (the moderator gets an error and the user stays put).Worth confirming this is the semantics we want before merging.
Grabacion.2026-05-30.224852.mp4
Testing