Claude-code-plugins-plus-skills intercom-enterprise-rbac
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/intercom-pack/skills/intercom-enterprise-rbac" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-intercom-enterprise-rbac && rm -rf "$T"
manifest:
plugins/saas-packs/intercom-pack/skills/intercom-enterprise-rbac/SKILL.mdsource content
Intercom Enterprise RBAC
Overview
Configure enterprise-grade access control for Intercom integrations using OAuth scopes, admin role management, and app-level permission enforcement.
Prerequisites
- Intercom workspace with admin access
- Understanding of OAuth 2.0 flows
- For public apps: OAuth configured in Developer Hub
Intercom Admin Roles
Intercom has built-in admin roles that control workspace access:
| Role | API Access | Capabilities |
|---|---|---|
| Owner | Full | All operations, billing, workspace settings |
| Admin | Full | Manage contacts, conversations, content |
| Agent | Limited | Reply to conversations, view contacts |
| Custom roles | Configurable | Enterprise plan feature |
Step 1: List Admins and Roles
import { IntercomClient } from "intercom-client"; const client = new IntercomClient({ token: process.env.INTERCOM_ACCESS_TOKEN!, }); // List all admins in the workspace const adminList = await client.admins.list(); for (const admin of adminList.admins) { console.log(`${admin.name} (${admin.email})`); console.log(` ID: ${admin.id}`); console.log(` Type: ${admin.type}`); // "admin" or "team" console.log(` Active: ${admin.awayModeEnabled ? "Away" : "Available"}`); } // Find a specific admin by ID const admin = await client.admins.find({ adminId: "12345" }); console.log(`Admin: ${admin.name} - ${admin.email}`);
Instructions
Step 2: OAuth Scope-Based Access Control
For public apps (OAuth), scopes control what your app can access in a customer's workspace.
// OAuth configuration const OAUTH_CONFIG = { clientId: process.env.INTERCOM_CLIENT_ID!, clientSecret: process.env.INTERCOM_CLIENT_SECRET!, redirectUri: "https://your-app.com/auth/intercom/callback", }; // Step 1: Build authorization URL with minimal scopes function getAuthUrl(state: string): string { return `https://app.intercom.com/oauth?` + `client_id=${OAUTH_CONFIG.clientId}&` + `state=${state}&` + `redirect_uri=${encodeURIComponent(OAUTH_CONFIG.redirectUri)}`; } // Step 2: Exchange authorization code for token async function exchangeCode(code: string): Promise<{ token: string; tokenType: string; }> { const response = await fetch("https://api.intercom.io/auth/eagle/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_id: OAUTH_CONFIG.clientId, client_secret: OAUTH_CONFIG.clientSecret, code, }), }); if (!response.ok) { const error = await response.json(); throw new Error(`OAuth token exchange failed: ${error.message}`); } const data = await response.json(); return { token: data.token, tokenType: data.token_type }; } // Step 3: Store token per workspace for multi-tenant interface WorkspaceAuth { workspaceId: string; token: string; installedAt: Date; installedBy: string; // admin email } // Usage const authUrl = getAuthUrl(crypto.randomUUID()); // Redirect user to authUrl // On callback, exchange code for token
Step 3: App-Level Permission Enforcement
Enforce permissions at the application layer based on the current admin.
// Define permission levels for your app's operations type IntercomPermission = | "contacts:read" | "contacts:write" | "contacts:delete" | "conversations:read" | "conversations:reply" | "conversations:assign" | "conversations:close" | "articles:read" | "articles:write" | "settings:manage"; // Map admin types to permissions const ROLE_PERMISSIONS: Record<string, Set<IntercomPermission>> = { owner: new Set([ "contacts:read", "contacts:write", "contacts:delete", "conversations:read", "conversations:reply", "conversations:assign", "conversations:close", "articles:read", "articles:write", "settings:manage", ]), admin: new Set([ "contacts:read", "contacts:write", "conversations:read", "conversations:reply", "conversations:assign", "conversations:close", "articles:read", "articles:write", ]), agent: new Set([ "contacts:read", "conversations:read", "conversations:reply", ]), }; function checkPermission(adminRole: string, permission: IntercomPermission): boolean { return ROLE_PERMISSIONS[adminRole]?.has(permission) ?? false; } // Express middleware function requirePermission(permission: IntercomPermission) { return (req: any, res: any, next: any) => { const adminRole = req.user?.intercomRole || "agent"; if (!checkPermission(adminRole, permission)) { return res.status(403).json({ error: "Forbidden", message: `Missing permission: ${permission}`, required: permission, currentRole: adminRole, }); } next(); }; } // Usage app.delete( "/api/contacts/:id", requirePermission("contacts:delete"), deleteContactHandler ); app.post( "/api/conversations/:id/assign", requirePermission("conversations:assign"), assignConversationHandler );
Step 4: Team-Based Conversation Assignment
// List teams in workspace const admins = await client.admins.list(); const teams = admins.admins.filter(a => a.type === "team"); console.log("Teams:", teams.map(t => `${t.name} (${t.id})`)); // Assign conversation to team based on topic async function routeConversation( conversationId: string, adminId: string, topic: string ): Promise<void> { const teamRouting: Record<string, string> = { billing: "team-billing-123", technical: "team-engineering-456", sales: "team-sales-789", }; const teamId = teamRouting[topic]; if (teamId) { await client.conversations.assign({ conversationId, type: "team", adminId, assigneeId: teamId, body: `Routed to ${topic} team`, }); } }
Step 5: Audit Logging for Admin Actions
interface AdminAuditEntry { timestamp: string; adminId: string; adminEmail: string; action: string; resource: string; resourceId: string; success: boolean; ipAddress?: string; } async function auditAdminAction(entry: AdminAuditEntry): Promise<void> { // Log to your audit database await db.auditLog.insert(entry); // Track as Intercom data event for visibility await client.dataEvents.create({ eventName: "admin-action-logged", createdAt: Math.floor(Date.now() / 1000), userId: entry.adminId, metadata: { action: entry.action, resource: entry.resource, resource_id: entry.resourceId, success: entry.success, }, }); // Alert on sensitive operations if (entry.action.includes("delete") || entry.action.includes("settings")) { console.warn(`[AUDIT] Sensitive action: ${entry.action} by ${entry.adminEmail}`); } }
OAuth Scope Reference
| Scope | Grants |
|---|---|
| Read admins | |
| Read contacts | , |
| Write contacts | , , |
| Read conversations | , |
| Write conversations | , reply, close, assign |
| Read messages | Read sent messages |
| Write messages | |
| Read articles | , |
| Write articles | Create, update, delete articles and collections |
| Read tags | |
| Write tags | Create, apply, remove tags |
| Read events | |
| Write events | |
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| OAuth callback fails | Wrong redirect URI | Match exactly in Developer Hub |
(403) | Missing OAuth scope | Add scope, user must re-authorize |
| Token revoked | User uninstalled app | Handle gracefully, notify admin |
| Admin not found | Admin left workspace | Remove from your system |
| Team assignment fails | Team ID invalid | List teams first with |
Resources
Next Steps
For major migrations, see
intercom-migration-deep-dive.