diff --git a/AGENTS.md b/AGENTS.md index 528976f..226f2d2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,8 +36,9 @@ Prometheus is a Slack bot built with `@slack/bolt` in Socket Mode. It runs via ` - `isWorkspaceAdmin` — Slack API check (`users.info`) - `isChannelManager` — `appointed_managers` with `role = 'manager'` - `isChannelModerator` — any role in `appointed_managers` (manager or moderator) +- `isSlackChannelManager` — Slack's native Channel Manager role (Enterprise Grid), via `getChannelManagers` in `lib/moderation.js` (undocumented `admin.roles.entity.listAssignments`). Uses the same enterprise moderation creds (`SLACK_BROWSER_TOKEN`/`SLACK_COOKIE`); no-ops (`false`) when those aren't configured - `canManage` — globalAdmin OR channelManager (for delete/destroy/welcome) -- `canBan` — globalAdmin OR channelModerator (for ban/unban/@here) +- `canBan` — globalAdmin OR channelModerator OR isSlackChannelManager (for ban/unban/@here/@channel) - `canAnchor` — `canManage` OR workspaceAdmin - `SUPERADMINS` grants access to the `/pro admin` command; it does not automatically insert rows into `global_admins` diff --git a/README.md b/README.md index d0ce55b..a7e41aa 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Prometheus is a Slack bot that lets community members take responsibility for ke - **Workspace admin**: inherited from Slack - **Channel manager**: appointed per-channel; can delete, destroy, set welcome messages - **Channel moderator**: appointed per-channel; can timeout, @here, @channel +- **Slack Channel Manager**: Slack's own native per-channel role (Enterprise Grid); can @here, @channel. Read via the enterprise moderation creds — inert without `SLACK_BROWSER_TOKEN`/`SLACK_COOKIE` ## Web API diff --git a/lib/moderation.js b/lib/moderation.js index 7f7f628..d508a46 100644 --- a/lib/moderation.js +++ b/lib/moderation.js @@ -68,3 +68,15 @@ export async function deleteAttachment(channel, ts, attachment) { attachment, }); } + +export async function getChannelManagers(channelId) { + if (!areWeEnterprise) return []; + try { + const json = await moderationAPI("admin.roles.entity.listAssignments", { + entity_id: channelId, + }); + return json.role_assignments?.[0]?.users || []; + } catch { + return []; + } +} diff --git a/lib/perms.js b/lib/perms.js index 34217ff..812f395 100644 --- a/lib/perms.js +++ b/lib/perms.js @@ -3,9 +3,13 @@ import { hasChannelRole as dbHasChannelRole, isAppointedManager as dbIsAppointedManager, } from "./db.js"; +import { getChannelManagers } from "./moderation.js"; export { isGlobalAdmin }; +export const isSlackChannelManager = async (userId, channelId) => + (await getChannelManagers(channelId)).includes(userId); + export const isWorkspaceAdmin = async (client, userId) => { try { const r = await client.users.info({ user: userId }); @@ -25,7 +29,9 @@ export const isChannelManager = (_client, userId, channelId) => // timeout, untimeout, @here, @channel export const canBan = async (client, userId, channelId) => - (await isGlobalAdmin(userId)) || (await dbHasChannelRole(userId, channelId)); + (await isGlobalAdmin(userId)) || + (await dbHasChannelRole(userId, channelId)) || + (await isSlackChannelManager(userId, channelId)); // delete, destroy thread, welcome export const canManage = async (client, userId, channelId) =>