Repository navigation
fix: enforce open restrictions and type-specific staff roles - #33
Conversation
|
Thanks for digging into this one, there was more broken here than I expected. Everything you describe is still the case on current main: On the two points you raised:
Tests pass on your branch (186/186). Since this PR touches the same parts of |
Matches upstream review of PR MSK-Scripts#33: the commands keep their default member permissions, and admins restrict or open them up under Server Settings > Integrations instead of a second check in code. Claude-Session: https://claude.ai/code/session_01HZPZxD3edzJNFFsiWsHYY1
864f7d4 to
e9852a4
Compare
|
Thanks for the clear answers. Done:
Verified live with a non-admin account: type-only staff can claim, |
|
#32, #34, #38 and #39 are all merged now, so this is the last one. Could you rebase onto main and mark it ready? It overlaps with #32 and #34 in |
- New getOpenRefusal() is the single eligibility check for the panel button,
both select menus, the questions modal and openTicket() itself.
- rolesWhoCanNotCreateTickets was validated but never checked; it is now.
- cantAccess was skipped on the single-type button path and inside
openTicket(); it is now checked on every path.
- openTicket() holds a per-user in-flight lock across the limit check,
channel creation and DB insert, so a burst of select-menu picks or modal
submits can no longer create several channels past maxTicketOpened.
- openTicket() returns { channel, refusal } so callers show the real reason.
- New locale keys messages.cannotCreateTickets and
messages.ticketOpenInProgress (all languages).
Claude-Session: https://claude.ai/code/session_01Kp1SLbxxAbXw3f42C1qThH
A type's staffRoles already decided who gets access to the channel (overriding the global roles), but every action except reopen checked isStaff() without the type and always fell back to the global roles. So type-only staff could not claim, close, lock, move or annotate their own tickets, and reminders pinged roles that cannot see the channel. - isStaff(member, type) uses the type's staffRoles when it has any, the global roles otherwise — the same rule as the channel permissions. - New isStaffIn(member, channelId) resolves the ticket's type; all ticket-scoped commands, buttons and menus use it. - New isAnyStaff(member) for /stats, /broadcast and rating comments; /broadcast only posts into tickets the sender is staff for. - Staff reminders and "staff replied" DMs use the ticket type's roles. - /blacklist and /setup enforce ManageGuild / Administrator in code instead of relying only on setDefaultMemberPermissions. - Document the override rule in config.example.jsonc. Claude-Session: https://claude.ai/code/session_01Kp1SLbxxAbXw3f42C1qThH
Closing and locking only changed the creator's overwrite, so users added with /add kept reading a closed ticket (including staff discussion after the close) and could still write in a locked one. Reopen also unlocked the creator even when the ticket was still locked. - New editParticipantAccess() edits the creator and every member overwrite except the bot and members who are staff for the ticket's type (a member deny would override their role access). - Close, /lock, /unlock, the dashboard lock action and reopen use it. - Reopen restores SendMessages only if the ticket is not locked. - /add gives read-only access while the ticket is locked. Claude-Session: https://claude.ai/code/session_01Kp1SLbxxAbXw3f42C1qThH
- Only the ticket creator can rate a ticket (button and modal); staff and users added with /add could rate it from an in-channel request. - The close-reason modal re-checks whoCanCloseTicket: STAFFONLY, since a modal submit is its own interaction. - The delete confirmation refuses to delete a channel that is not a ticket. - New locale key messages.onlyCreatorCanRate (all languages). Claude-Session: https://claude.ai/code/session_01Kp1SLbxxAbXw3f42C1qThH
- /lock and /unlock defer before editing each participant's permissions; several edits could outlast Discord's 3 s reply window. - config.example.jsonc: tickets of a type removed from the config fall back to the global staff roles. Claude-Session: https://claude.ai/code/session_01Kp1SLbxxAbXw3f42C1qThH
Review feedback: admins open these commands up via Integrations on purpose, so the code no longer re-checks ManageGuild / Administrator on top of setDefaultMemberPermissions. Claude-Session: https://claude.ai/code/session_01HZPZxD3edzJNFFsiWsHYY1
Review feedback: server owners need to see that a type with its own staffRoles no longer falls back to the global roles. Claude-Session: https://claude.ai/code/session_01HZPZxD3edzJNFFsiWsHYY1
openTicket now looks the member up for getOpenRefusal, so the MSK-Scripts#38 test's fake guild needs members.fetch; and since MSK-Scripts#38 the channel overwrites read client.user.id, so the race test's fake client needs a bot user. Test fixtures only. Claude-Session: https://claude.ai/code/session_01HZPZxD3edzJNFFsiWsHYY1
|
Rebased onto main and marked ready.
Tests pass (206/206). |
e9852a4 to
9f30707
Compare
Description
Permission fixes found while reviewing the bot before deploying it for our community (Uncharted Roleplay). Each commit is one change:
getOpenRefusal()is used by the panel button, both select menus, the questions modal andopenTicket()itself. Before:rolesWhoCanNotCreateTicketswas validated inconfig.jsbut never checked anywhere.cantAccesswas checked only in the two select menus, not on the single-typetb_openpath or inopenTicket().guild.channels.create, so a burst of select-menu picks or modal submits each passed the check.openTicket()now holds a per-user in-flight lock across check, creation and insert, and returns{ channel, refusal }so callers can show the real reason.staffRolesnow apply to staff actions. The channel permissions already gave a type'sstaffRolesaccess instead of the global roles (asconfig.example.jsoncdocuments: "overrides"), but every action except reopen calledisStaff(member)without the type. Members holding only a type's staff role could not claim, close, lock, move, add notes and so on; reminders pinged global roles that cannot see the channel.isStaff(member, type)now uses the type'sstaffRoleswhen it has any, the global roles otherwise.isStaffIn(member, channelId)is used by all ticket-scoped commands, buttons and menus; newisAnyStaff(member)covers/stats,/broadcastand rating comments./broadcastonly posts into tickets the sender is staff for./addkept reading a closed ticket (including staff discussion after the close) and could still write in a locked one; reopen unlocked the creator even if the ticket was locked. Staff members who were added are skipped, because a member deny would override their role access./addgives read-only access while the ticket is locked./locknow defers, since it may edit several overwrites.STAFFONLY, and the delete confirmation refuses non-ticket channels. (/blacklist//setuppermissions stay with the server admin via Integrations, as requested in review.)New locale keys
messages.cannotCreateTickets,messages.ticketOpenInProgressandmessages.onlyCreatorCanRate, translated in all languages.Related Issue
None opened; these came out of a code review. Happy to open issues if you prefer that workflow.
Type of Change
Breaking in one respect: global staff no longer act on tickets of a type that has its own
staffRoles(they already could not see those channels). Servers that relied on the old fallback should list the global role in that type'sstaffRoles;config.example.jsoncnow says so, and the changelog has a Breaking section for it.How Was This Tested?
npm test: all existing tests pass, plus newnode:testsuites (tests/open-eligibility.test.jsincluding the concurrency case,tests/staff-roles.test.js,tests/participant-access.test.js,tests/rating.test.js)./lock,/unlock) vs. global staff,/add+ lock + close for added users, rating by DM. All passed.Checklist
tb_custom-id prefix,client.logger, etc.)client.t(...)and all three locale files (main.json,en.json,de.json) are in sync (all eight locale files, in fact)initDatabase()(no schema change)MSK_API_KEY(premium features degrade gracefully)npm start) and I verified my change on a test server (see above)README.md/README_GER.md,config.example.jsonc,CLAUDE.md) where behavior, config keys, commands or the schema changed (config.example.jsonc)CHANGELOG.mdentry under[Unreleased]feat:,fix:,docs:, …)Note on the related PRs
This is one of three PRs from the same review (robustness, permissions, transcripts). They are independent and each applies to
mainon its own, but they touch some of the same lines (ticketActions.jsexports,ready.js,CHANGELOG.md). Following your order (#32, #34, then this one), I will rebase this after the other two are merged.https://claude.ai/code/session_01Kp1SLbxxAbXw3f42C1qThH