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.md
source 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:

RoleAPI AccessCapabilities
OwnerFullAll operations, billing, workspace settings
AdminFullManage contacts, conversations, content
AgentLimitedReply to conversations, view contacts
Custom rolesConfigurableEnterprise 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

ScopeGrants
Read admins
GET /admins
Read contacts
GET /contacts
,
POST /contacts/search
Write contacts
POST /contacts
,
PUT /contacts/{id}
,
DELETE /contacts/{id}
Read conversations
GET /conversations
,
GET /conversations/{id}
Write conversations
POST /conversations
, reply, close, assign
Read messagesRead sent messages
Write messages
POST /messages
Read articles
GET /articles
,
GET /help_center/collections
Write articlesCreate, update, delete articles and collections
Read tags
GET /tags
Write tagsCreate, apply, remove tags
Read events
GET /events
Write events
POST /events

Error Handling

IssueCauseSolution
OAuth callback failsWrong redirect URIMatch exactly in Developer Hub
forbidden
(403)
Missing OAuth scopeAdd scope, user must re-authorize
Token revokedUser uninstalled appHandle gracefully, notify admin
Admin not foundAdmin left workspaceRemove from your system
Team assignment failsTeam ID invalidList teams first with
admins.list()

Resources

Next Steps

For major migrations, see

intercom-migration-deep-dive
.