| name | apollo-enterprise-rbac |
| description | Enterprise role-based access control for Apollo.io.
Use when implementing team permissions, restricting data access,
or setting up enterprise security controls.
Trigger with phrases like "apollo rbac", "apollo permissions",
"apollo roles", "apollo team access", "apollo enterprise security".
|
| allowed-tools | Read, Write, Edit, Bash(kubectl:*), Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
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
export const 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:basic': 'Basic people search',
'search:advanced': 'Advanced search filters',
'search:bulk': 'Bulk search operations',
'enrich:person': 'Enrich individual contacts',
'enrich:company': 'Enrich company data',
'enrich:bulk': 'Bulk enrichment operations',
'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: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
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', , , , ,
, , , ,
],
},
: {
: ,
: ,
: [
, , , , ,
, ,
, ,
, , , , ,
],
},
: {
: ,
: ,
: [
, , , ,
,
,
, ,
],
},
: {
: ,
: ,
: [
, ,
, , ,
, , ,
, , ,
],
},
: {
: ,
: ,
: [
,
,
,
],
},
: {
: ,
: ,
: [
,
,
,
],
},
};
RBAC Service
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>();
for (const role of user.roles) {
const roleDef = ROLES[role.name];
if (roleDef) {
roleDef.permissions.forEach( permissions.(p));
}
}
user..( {
(p.) {
permissions.(p. );
} {
permissions.(p. );
}
});
.(permissions);
}
(: , : ): <> {
permissions = .(userId);
permissions.(permission);
}
(: , : []): <> {
userPermissions = .(userId);
permissions.( userPermissions.(p));
}
(: , : []): <> {
userPermissions = .(userId);
permissions.( userPermissions.(p));
}
}
rbac = ();
Permission Middleware
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) {
await auditLog.create({
action: 'PERMISSION_DENIED',
actor: userId,
resource: req.path,
: { : permissions },
});
res.().({
: ,
: permissions,
});
}
();
};
}
router.(,
(),
contactController.
);
router.(,
(),
contactController.
);
router.(,
(),
contactController.
);
router.(,
(, ),
searchController.
);
Team-Based Access Control
export class TeamAccessService {
async getAccessibleContacts(
userId: string,
teamId: string
): Promise<string[]> {
const membership = await prisma.teamMember.findFirst({
where: { userId, teamId },
include: { team: true },
});
if (!membership) {
return [];
}
const accessScope = await this.buildAccessScope(userId, membership);
const contacts = await prisma.contact.findMany({
where: accessScope,
select: { id: true },
});
return contacts.map(c => c.id);
}
private async buildAccessScope(
userId: string,
membership: TeamMembership
): <> {
(membership. === || membership. === ) {
{ : membership. };
}
{
: [
{ : userId },
{ : { : { userId } } },
{ : membership., : },
],
};
}
}
() {
(: , : , : ) => {
userId = req.?.;
resourceId = req..;
hasAccess = teamAccess.(
userId,
resourceType,
resourceId
);
(!hasAccess) {
res.().({
: ,
});
}
();
};
}
API Key Scoping
interface ApiKeyScope {
permissions: Permission[];
rateLimit: number;
ipAllowlist?: string[];
expiresAt?: Date;
}
export async function createScopedApiKey(
userId: string,
scope: ApiKeyScope
): Promise<string> {
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(', ')}`);
}
const apiKey = generateSecureKey();
await prisma.apiKey.create({
data: {
key: (apiKey),
userId,
: scope.,
: scope.,
: scope.,
: scope.,
},
});
apiKey;
}
() {
(: , : , : ) => {
apiKey = req.[];
(!apiKey) {
res.().({ : });
}
keyRecord = prisma..({
: { : (apiKey ) },
});
(!keyRecord) {
res.().({ : });
}
(keyRecord. && () > keyRecord.) {
res.().({ : });
}
(keyRecord.?. > ) {
clientIp = req.;
(!keyRecord..(clientIp)) {
res.().({ : });
}
}
hasPermission = requiredPermissions.(
keyRecord..(p)
);
(!hasPermission) {
res.().({
: ,
: requiredPermissions,
});
}
();
};
}
Admin Dashboard
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.