Claude-code-plugins-plus-skills hubspot-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/hubspot-pack/skills/hubspot-enterprise-rbac" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-hubspot-enterprise-rbac && rm -rf "$T"
manifest:
plugins/saas-packs/hubspot-pack/skills/hubspot-enterprise-rbac/SKILL.mdsource content
HubSpot Enterprise RBAC
Overview
Implement role-based access control for HubSpot integrations using OAuth scopes, multiple private apps with different permissions, and application-level authorization.
Prerequisites
- HubSpot Enterprise subscription (for team-level permissions)
- Understanding of HubSpot OAuth scopes
- Multiple private apps or OAuth app configured
Instructions
Step 1: Scope-Based Access Model
HubSpot's permission model is scope-based. Create separate private apps for different access levels:
| Role | Private App | Scopes | Use Case |
|---|---|---|---|
| Reader | | , , | Dashboards, reports |
| Writer | | Above + variants | CRM operations |
| Admin | | All CRM scopes + | Schema management |
| Sync | | , , | Data sync jobs |
| Webhook | | | Event handling only |
Step 2: Multi-Token Client Factory
import * as hubspot from '@hubspot/api-client'; type AccessLevel = 'reader' | 'writer' | 'admin' | 'sync'; const TOKEN_MAP: Record<AccessLevel, string> = { reader: process.env.HUBSPOT_READER_TOKEN!, writer: process.env.HUBSPOT_WRITER_TOKEN!, admin: process.env.HUBSPOT_ADMIN_TOKEN!, sync: process.env.HUBSPOT_SYNC_TOKEN!, }; const clientCache = new Map<AccessLevel, hubspot.Client>(); export function getClientForRole(role: AccessLevel): hubspot.Client { if (!clientCache.has(role)) { const token = TOKEN_MAP[role]; if (!token) { throw new Error(`No token configured for role: ${role}`); } clientCache.set(role, new hubspot.Client({ accessToken: token, numberOfApiCallRetries: 3, })); } return clientCache.get(role)!; } // Usage const readClient = getClientForRole('reader'); // can only read const writeClient = getClientForRole('writer'); // can read and write
Step 3: Application-Level Permission Middleware
import { Request, Response, NextFunction } from 'express'; interface AppPermissions { contacts: { read: boolean; write: boolean; delete: boolean }; deals: { read: boolean; write: boolean; delete: boolean }; companies: { read: boolean; write: boolean; delete: boolean }; } const ROLE_PERMISSIONS: Record<string, AppPermissions> = { sales_rep: { contacts: { read: true, write: true, delete: false }, deals: { read: true, write: true, delete: false }, companies: { read: true, write: false, delete: false }, }, marketing: { contacts: { read: true, write: true, delete: false }, deals: { read: true, write: false, delete: false }, companies: { read: true, write: false, delete: false }, }, admin: { contacts: { read: true, write: true, delete: true }, deals: { read: true, write: true, delete: true }, companies: { read: true, write: true, delete: true }, }, readonly: { contacts: { read: true, write: false, delete: false }, deals: { read: true, write: false, delete: false }, companies: { read: true, write: false, delete: false }, }, }; function requirePermission( objectType: keyof AppPermissions, action: 'read' | 'write' | 'delete' ) { return (req: Request, res: Response, next: NextFunction) => { const userRole = req.user?.role || 'readonly'; const permissions = ROLE_PERMISSIONS[userRole]; if (!permissions || !permissions[objectType]?.[action]) { return res.status(403).json({ error: 'Forbidden', message: `Role "${userRole}" lacks ${action} permission for ${objectType}`, }); } next(); }; } // Usage app.get('/api/contacts', requirePermission('contacts', 'read'), listContacts); app.post('/api/contacts', requirePermission('contacts', 'write'), createContact); app.delete('/api/contacts/:id', requirePermission('contacts', 'delete'), deleteContact);
Step 4: OAuth 2.0 for Multi-Portal Access
// For public apps accessing multiple HubSpot portals interface PortalCredentials { portalId: string; accessToken: string; refreshToken: string; expiresAt: Date; } class MultiPortalManager { private credentials = new Map<string, PortalCredentials>(); async getClient(portalId: string): Promise<hubspot.Client> { let creds = this.credentials.get(portalId); if (!creds) { throw new Error(`No credentials for portal ${portalId}. User must authorize.`); } // Refresh token if expired if (new Date() >= creds.expiresAt) { creds = await this.refreshToken(creds); this.credentials.set(portalId, creds); } return new hubspot.Client({ accessToken: creds.accessToken }); } private async refreshToken(creds: PortalCredentials): Promise<PortalCredentials> { const tempClient = new hubspot.Client(); const response = await tempClient.oauth.tokensApi.create( 'refresh_token', undefined, undefined, process.env.HUBSPOT_CLIENT_ID!, process.env.HUBSPOT_CLIENT_SECRET!, creds.refreshToken ); return { ...creds, accessToken: response.accessToken, refreshToken: response.refreshToken, expiresAt: new Date(Date.now() + response.expiresIn * 1000), }; } }
Step 5: Audit Trail
interface HubSpotAuditEntry { timestamp: string; userId: string; role: string; action: string; objectType: string; objectId: string; success: boolean; hubspotCorrelationId?: string; } async function auditHubSpotAction( userId: string, role: string, action: string, objectType: string, objectId: string, success: boolean, correlationId?: string ): Promise<void> { const entry: HubSpotAuditEntry = { timestamp: new Date().toISOString(), userId, role, action, objectType, objectId, success, hubspotCorrelationId: correlationId, }; // Store in your audit database await db.auditLog.insert(entry); // Alert on suspicious activity if (!success && action === 'delete') { console.warn('Failed delete attempt:', { userId, role, objectType, objectId }); } }
Output
- Scope-based access model with separate private apps per role
- Multi-token client factory for role-based HubSpot access
- Application-level permission middleware
- Multi-portal OAuth management for public apps
- Audit trail for all HubSpot operations
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Token lacks required scope | Use the correct role's token |
| Permission denied in app | User role too restrictive | Check ROLE_PERMISSIONS mapping |
| Token refresh fails | Client secret changed | Update client secret in env |
| Audit gaps | Async logging failed | Add retry to audit log writes |
Resources
Next Steps
For major migrations, see
hubspot-migration-deep-dive.