Claude-skill-registry apollo-enterprise-rbac
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/apollo-enterprise-rbac" ~/.claude/skills/majiayu000-claude-skill-registry-apollo-enterprise-rbac && rm -rf "$T"
manifest:
skills/data/apollo-enterprise-rbac/SKILL.mdsafety · automated scan (low risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
- references API keys
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content
Apollo Enterprise RBAC
Overview
Implement role-based access control for Apollo.io integrations with granular permissions, team isolation, and audit trails.
Role Hierarchy
Super Admin | +-- Admin | | | +-- Sales Manager | | | | | +-- Sales Rep | | | +-- Marketing Manager | | | +-- Marketing User | +-- Read Only
Permission Definitions
// src/lib/rbac/permissions.ts export const PERMISSIONS = { // Contact permissions 'contacts:read': 'View contact information', 'contacts:create': 'Add new contacts', 'contacts:update': 'Edit contact information', 'contacts:delete': 'Delete contacts', 'contacts:export': 'Export contact data', 'contacts:reveal_email': 'Reveal email addresses (uses credits)', // Search permissions 'search:basic': 'Basic people search', 'search:advanced': 'Advanced search filters', 'search:bulk': 'Bulk search operations', // Enrichment permissions 'enrich:person': 'Enrich individual contacts', 'enrich:company': 'Enrich company data', 'enrich:bulk': 'Bulk enrichment operations', // Sequence permissions 'sequences:read': 'View sequences', 'sequences:create': 'Create new sequences', 'sequences:edit': 'Edit sequences', 'sequences:delete': 'Delete sequences', 'sequences:enroll': 'Add contacts to sequences', 'sequences:send': 'Send emails through sequences', // Admin permissions 'admin:users': 'Manage users', 'admin:roles': 'Manage roles', 'admin:settings': 'Configure settings', 'admin:billing': 'View/manage billing', 'admin:audit': 'View audit logs', 'admin:api_keys': 'Manage API keys', } as const; export type Permission = keyof typeof PERMISSIONS;
Role Definitions
// src/lib/rbac/roles.ts import { Permission } from './permissions'; interface Role { name: string; description: string; permissions: Permission[]; inherits?: string[]; } export const ROLES: Record<string, Role> = { super_admin: { name: 'Super Admin', description: 'Full system access', permissions: Object.keys(PERMISSIONS) as Permission[], }, admin: { name: 'Admin', description: 'Administrative access without billing', permissions: [ 'contacts:read', 'contacts:create', 'contacts:update', 'contacts:delete', 'contacts:export', 'search:basic', 'search:advanced', 'search:bulk', 'enrich:person', 'enrich:company', 'enrich:bulk', 'sequences:read', 'sequences:create', 'sequences:edit', 'sequences:delete', 'sequences:enroll', 'sequences:send', 'admin:users', 'admin:roles', 'admin:settings', 'admin:audit', ], }, sales_manager: { name: 'Sales Manager', description: 'Manage sales team and sequences', permissions: [ 'contacts:read', 'contacts:create', 'contacts:update', 'contacts:export', 'contacts:reveal_email', 'search:basic', 'search:advanced', 'enrich:person', 'enrich:company', 'sequences:read', 'sequences:create', 'sequences:edit', 'sequences:enroll', 'sequences:send', ], }, sales_rep: { name: 'Sales Representative', description: 'Basic sales access', permissions: [ 'contacts:read', 'contacts:create', 'contacts:update', 'contacts:reveal_email', 'search:basic', 'enrich:person', 'sequences:read', 'sequences:enroll', ], }, marketing_manager: { name: 'Marketing Manager', description: 'Manage marketing campaigns', permissions: [ 'contacts:read', 'contacts:export', 'search:basic', 'search:advanced', 'search:bulk', 'enrich:person', 'enrich:company', 'enrich:bulk', 'sequences:read', 'sequences:create', 'sequences:edit', ], }, marketing_user: { name: 'Marketing User', description: 'Basic marketing access', permissions: [ 'contacts:read', 'search:basic', 'sequences:read', ], }, read_only: { name: 'Read Only', description: 'View-only access', permissions: [ 'contacts:read', 'search:basic', 'sequences:read', ], }, };
RBAC Service
// src/services/rbac/rbac.service.ts import { User } from '../../models/user.model'; import { ROLES } from '../../lib/rbac/roles'; import { Permission } from '../../lib/rbac/permissions'; export class RBACService { async getUserPermissions(userId: string): Promise<Permission[]> { const user = await prisma.user.findUnique({ where: { id: userId }, include: { roles: true, customPermissions: true, }, }); if (!user) { return []; } const permissions = new Set<Permission>(); // Add role permissions for (const role of user.roles) { const roleDef = ROLES[role.name]; if (roleDef) { roleDef.permissions.forEach(p => permissions.add(p)); } } // Add custom permissions user.customPermissions.forEach(p => { if (p.granted) { permissions.add(p.permission as Permission); } else { permissions.delete(p.permission as Permission); } }); return Array.from(permissions); } async hasPermission(userId: string, permission: Permission): Promise<boolean> { const permissions = await this.getUserPermissions(userId); return permissions.includes(permission); } async hasAnyPermission(userId: string, permissions: Permission[]): Promise<boolean> { const userPermissions = await this.getUserPermissions(userId); return permissions.some(p => userPermissions.includes(p)); } async hasAllPermissions(userId: string, permissions: Permission[]): Promise<boolean> { const userPermissions = await this.getUserPermissions(userId); return permissions.every(p => userPermissions.includes(p)); } } export const rbac = new RBACService();
Permission Middleware
// src/middleware/rbac.middleware.ts import { Request, Response, NextFunction } from 'express'; import { rbac } from '../services/rbac/rbac.service'; import { Permission } from '../lib/rbac/permissions'; export function requirePermission(...permissions: Permission[]) { return async (req: Request, res: Response, next: NextFunction) => { const userId = req.user?.id; if (!userId) { return res.status(401).json({ error: 'Authentication required' }); } const hasPermission = await rbac.hasAnyPermission(userId, permissions); if (!hasPermission) { // Audit failed access attempt await auditLog.create({ action: 'PERMISSION_DENIED', actor: userId, resource: req.path, metadata: { requiredPermissions: permissions }, }); return res.status(403).json({ error: 'Permission denied', required: permissions, }); } next(); }; } // Usage in routes router.get('/contacts', requirePermission('contacts:read'), contactController.list ); router.post('/contacts', requirePermission('contacts:create'), contactController.create ); router.delete('/contacts/:id', requirePermission('contacts:delete'), contactController.delete ); router.post('/search/bulk', requirePermission('search:bulk', 'search:advanced'), searchController.bulkSearch );
Team-Based Access Control
// src/services/rbac/team-access.ts export class TeamAccessService { async getAccessibleContacts( userId: string, teamId: string ): Promise<string[]> { // Check user's team membership const membership = await prisma.teamMember.findFirst({ where: { userId, teamId }, include: { team: true }, }); if (!membership) { return []; } // Build access scope const accessScope = await this.buildAccessScope(userId, membership); // Return contact IDs user can access const contacts = await prisma.contact.findMany({ where: accessScope, select: { id: true }, }); return contacts.map(c => c.id); } private async buildAccessScope( userId: string, membership: TeamMembership ): Promise<any> { // Team admins can see all team contacts if (membership.role === 'admin' || membership.role === 'manager') { return { teamId: membership.teamId }; } // Regular members see own contacts + shared return { OR: [ { ownerId: userId }, { sharedWith: { some: { userId } } }, { teamId: membership.teamId, isPublic: true }, ], }; } } // Middleware for team-scoped access export function requireTeamAccess(resourceType: string) { return async (req: Request, res: Response, next: NextFunction) => { const userId = req.user?.id; const resourceId = req.params.id; const hasAccess = await teamAccess.canAccessResource( userId, resourceType, resourceId ); if (!hasAccess) { return res.status(403).json({ error: 'You do not have access to this resource', }); } next(); }; }
API Key Scoping
// src/lib/rbac/api-key-scope.ts interface ApiKeyScope { permissions: Permission[]; rateLimit: number; ipAllowlist?: string[]; expiresAt?: Date; } export async function createScopedApiKey( userId: string, scope: ApiKeyScope ): Promise<string> { // Validate user has the permissions they're granting const userPermissions = await rbac.getUserPermissions(userId); const invalidPermissions = scope.permissions.filter( p => !userPermissions.includes(p) ); if (invalidPermissions.length > 0) { throw new Error(`Cannot grant permissions you don't have: ${invalidPermissions.join(', ')}`); } // Generate key const apiKey = generateSecureKey(); // Store with scope await prisma.apiKey.create({ data: { key: hashApiKey(apiKey), userId, permissions: scope.permissions, rateLimit: scope.rateLimit, ipAllowlist: scope.ipAllowlist, expiresAt: scope.expiresAt, }, }); return apiKey; } // Middleware to enforce API key scope export function enforceApiKeyScope(requiredPermissions: Permission[]) { return async (req: Request, res: Response, next: NextFunction) => { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).json({ error: 'API key required' }); } const keyRecord = await prisma.apiKey.findFirst({ where: { key: hashApiKey(apiKey as string) }, }); if (!keyRecord) { return res.status(401).json({ error: 'Invalid API key' }); } // Check expiration if (keyRecord.expiresAt && new Date() > keyRecord.expiresAt) { return res.status(401).json({ error: 'API key expired' }); } // Check IP allowlist if (keyRecord.ipAllowlist?.length > 0) { const clientIp = req.ip; if (!keyRecord.ipAllowlist.includes(clientIp)) { return res.status(403).json({ error: 'IP not allowed' }); } } // Check permissions const hasPermission = requiredPermissions.every( p => keyRecord.permissions.includes(p) ); if (!hasPermission) { return res.status(403).json({ error: 'API key lacks required permissions', required: requiredPermissions, }); } next(); }; }
Admin Dashboard
// src/routes/admin/rbac.ts router.get('/users/:id/permissions', requirePermission('admin:users'), async (req, res) => { const permissions = await rbac.getUserPermissions(req.params.id); res.json({ permissions }); } ); router.post('/users/:id/roles', requirePermission('admin:roles'), async (req, res) => { const { roles } = req.body; await rbac.assignRoles(req.params.id, roles); res.json({ success: true }); } ); router.get('/audit/access-denied', requirePermission('admin:audit'), async (req, res) => { const logs = await prisma.auditLog.findMany({ where: { action: 'PERMISSION_DENIED' }, orderBy: { createdAt: 'desc' }, take: 100, }); res.json({ logs }); } );
Output
- Role-based permission system
- Team-based access control
- API key scoping
- Permission middleware
- Admin dashboard endpoints
Error Handling
| Issue | Resolution |
|---|---|
| Missing permissions | Request role upgrade |
| Team access denied | Check team membership |
| API key scope error | Regenerate with correct scope |
| Role conflict | Higher role takes precedence |
Resources
Next Steps
Proceed to
apollo-migration-deep-dive for migration strategies.