Agent Skills: Intercom Enterprise RBAC

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/intercom-enterprise-rbac

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/intercom-pack/skills/intercom-enterprise-rbac

Skill Files

Browse the full folder contents for intercom-enterprise-rbac.

Download Skill

Loading file tree…

plugins/saas-packs/intercom-pack/skills/intercom-enterprise-rbac/SKILL.md

Skill Metadata

Name
intercom-enterprise-rbac
Description
|

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 | 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 messages | Read sent messages | | Write messages | POST /messages | | Read articles | GET /articles, GET /help_center/collections | | Write articles | Create, update, delete articles and collections | | Read tags | GET /tags | | Write tags | Create, apply, remove tags | | Read events | GET /events | | Write events | POST /events |

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | OAuth callback fails | Wrong redirect URI | Match exactly in Developer Hub | | forbidden (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 admins.list() |

Resources

Next Steps

For major migrations, see intercom-migration-deep-dive.