Implement a way to change channel permissions for each user.#1280
Implement a way to change channel permissions for each user.#12802Pacalypse- wants to merge 3 commits intochannel-settings-dialog-2from
Conversation
|
Depends on #1279 |
e846737 to
9d094cb
Compare
a36e0a3 to
d8c66ef
Compare
9d094cb to
2f06a50
Compare
a8c697a to
de99ba0
Compare
37aa829 to
426c420
Compare
8b2a6d0 to
1354049
Compare
426c420 to
ef06878
Compare
| "permissions": { | ||
| "ban": "Can ban users", | ||
| "banShort": "Ban", | ||
| "changeTopic": "Can change topic", | ||
| "changeTopicShort": "Change topic", | ||
| "editPermissions": "Can edit permissions", | ||
| "editPermissionsShort": "Edit permissions", | ||
| "editTitle": "Edit permissions for {{name}}", | ||
| "joinedDate": "Joined {{date}}", | ||
| "kick": "Can kick users", | ||
| "kickShort": "Kick", | ||
| "loadError": "Failed to load users.", | ||
| "noSearchResults": "No users match your search", | ||
| "noUsers": "This channel has no other members", | ||
| "owner": "Owner", | ||
| "saveError": "Failed to save permissions", | ||
| "togglePrivate": "Can toggle private status", | ||
| "togglePrivateShort": "Toggle private" | ||
| }, | ||
| "tabs": { | ||
| "general": "General" | ||
| "general": "General", | ||
| "permissions": "Permissions" | ||
| }, | ||
| "users": { | ||
| "title": "Users" | ||
| } |
There was a problem hiding this comment.
CLAUDE.md violation: translation file manually edited
CLAUDE.md states:
Don't edit translation files (
global.json) manually - runpnpm run gen-translations
Translation keys should be extracted from source code by the gen-translations command rather than added directly to global.json. Manual edits may be overwritten or cause conflicts on the next generation run.
| async listUserChannelEntries({ | ||
| channelId, | ||
| userId, | ||
| isAdmin, | ||
| limit, | ||
| offset, | ||
| searchStr, | ||
| }: { | ||
| channelId: SbChannelId | ||
| userId: SbUserId | ||
| isAdmin: boolean | ||
| limit: number | ||
| offset: number | ||
| searchStr?: string | ||
| }): Promise<ListUserChannelEntriesResponse> { | ||
| const [channelInfo, userChannelEntry] = await Promise.all([ | ||
| getChannelInfo(channelId), | ||
| getUserChannelEntryForUser(userId, channelId), | ||
| ]) | ||
|
|
||
| if (!channelInfo) { | ||
| throw new ChatServiceError(ChatServiceErrorCode.ChannelNotFound, 'Channel not found') | ||
| } | ||
| if (!userChannelEntry) { | ||
| throw new ChatServiceError( | ||
| ChatServiceErrorCode.NotInChannel, | ||
| 'Must be in channel to view user channel entries', | ||
| ) | ||
| } | ||
|
|
||
| const isUserChannelOwner = channelInfo.ownerId === userId | ||
| if (!isAdmin && !isUserChannelOwner && !userChannelEntry.channelPermissions.editPermissions) { | ||
| throw new ChatServiceError( | ||
| ChatServiceErrorCode.NotEnoughPermissions, | ||
| "You don't have enough permissions to view user channel entries", | ||
| ) | ||
| } | ||
|
|
||
| const userChannelEntries = await getUserChannelEntriesForChannel({ | ||
| channelId, | ||
| limit, | ||
| offset, | ||
| searchStr, | ||
| }) | ||
| const userIds = userChannelEntries.map(u => u.userId) | ||
| const users = await findUsersById(userIds) | ||
|
|
||
| return { | ||
| channelId, | ||
| userChannelEntries: userChannelEntries.map(u => toUserChannelEntryJson(u)), | ||
| hasMoreUsers: userChannelEntries.length >= limit, | ||
| users, | ||
| } | ||
| } |
There was a problem hiding this comment.
CLAUDE.md violation: class method doesn't use this
CLAUDE.md states:
If a class method doesn't use
this, extract it as a module-level function instead.
listUserChannelEntries only calls module-level functions (getChannelInfo, getUserChannelEntryForUser, getUserChannelEntriesForChannel, findUsersById, toUserChannelEntryJson) and never references this. It should be extracted as a standalone exported function at module level, similar to the pattern used by other functions in this file.
| const isUserChannelOwner = channelInfo.ownerId === userId | ||
| if (!isUserChannelOwner && !userChannelEntry.channelPermissions.editPermissions) { | ||
| if (!isAdmin && !isUserChannelOwner && !userChannelEntry?.channelPermissions.editPermissions) { | ||
| throw new ChatServiceError( | ||
| ChatServiceErrorCode.NotEnoughPermissions, | ||
| "You don't have enough permissions to update other user's permissions", |
There was a problem hiding this comment.
Security: missing target restrictions allow privilege escalation
The authorization check here only validates that the caller has sufficient permissions, but does not restrict who can be targeted. This creates three distinct privilege escalation paths:
-
Self-escalation: A user with
editPermissionscan pass their ownuserIdastargetIdand grant themselvesban,kick,togglePrivate, orchangeTopic. The only guard is client-side (the UI hides this option), making the endpoint directly exploitable. -
Targeting the channel owner: A non-owner moderator with
editPermissionscan targetchannelInfo.ownerIdastargetIdand modify the owner's storedchannelPermissions. Again, only the client prevents this. -
editPermissionscascade: A user witheditPermissions(not the owner or server admin) can granteditPermissionsto any other channel member, since there is no restriction on which specific permissions a delegated moderator can set. This allows unbounded privilege promotion chains.
Suggested fixes:
- Add
if (!isAdmin && !isUserChannelOwner && targetId === userId) throw new ChatServiceError(...)to prevent self-targeting. - Add a guard preventing non-owners/non-admins from targeting the channel owner.
- Restrict which permissions a delegated moderator can grant — at minimum, prevent non-owners/non-admins from granting
editPermissionsto others.
Code reviewFound 3 issues (2 CLAUDE.md violations, 1 security concern):
|
No description provided.