๐Ÿ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

twinmind-enterprise-rbac

Implement enterprise role-based access control for TwinMind. Use when setting up team permissions, configuring SSO/SAML, or implementing organization-level access policies. Trigger with phrases like "twinmind RBAC", "twinmind permissions", "twinmind enterprise access", "twinmind SSO", "twinmind team management". allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

twinmind-pack

Claude Code skill pack for TwinMind (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the twinmind-pack plugin:

/plugin install twinmind-pack@claude-code-plugins-plus

Click to copy

Instructions

# TwinMind Enterprise RBAC ## Overview Enterprise-grade role-based access control for TwinMind with SSO integration, team management, and audit capabilities. ## Prerequisites - TwinMind Enterprise account - Admin access to TwinMind organization - Identity provider (Okta, Azure AD, Google Workspace) - Understanding of RBAC concepts ## Instructions ### Step 1: Define Roles and Permissions ```typescript // src/twinmind/rbac/roles.ts export enum Permission { // Transcript permissions TRANSCRIPT_CREATE = 'transcript:create', TRANSCRIPT_READ = 'transcript:read', TRANSCRIPT_READ_ALL = 'transcript:read:all', // Read any transcript TRANSCRIPT_DELETE = 'transcript:delete', TRANSCRIPT_DELETE_ALL = 'transcript:delete:all', // Summary permissions SUMMARY_GENERATE = 'summary:generate', SUMMARY_READ = 'summary:read', // Action item permissions ACTION_ITEM_CREATE = 'action_item:create', ACTION_ITEM_ASSIGN = 'action_item:assign', ACTION_ITEM_DELETE = 'action_item:delete', // Team permissions TEAM_VIEW = 'team:view', TEAM_MANAGE = 'team:manage', TEAM_INVITE = 'team:invite', TEAM_REMOVE = 'team:remove', // Settings permissions SETTINGS_VIEW = 'settings:view', SETTINGS_MANAGE = 'settings:manage', INTEGRATIONS_MANAGE = 'integrations:manage', // Billing permissions BILLING_VIEW = 'billing:view', BILLING_MANAGE = 'billing:manage', // Admin permissions ADMIN_FULL = 'admin:*', AUDIT_VIEW = 'audit:view', COMPLIANCE_MANAGE = 'compliance:manage', } export interface Role { name: string; description: string; permissions: Permission[]; inherits?: string[]; // Inherit from other roles } export const predefinedRoles: Record = { viewer: { name: 'Viewer', description: 'Can view own transcripts and summaries', permissions: [ Permission.TRANSCRIPT_READ, Permission.SUMMARY_READ, ], }, member: { name: 'Member', description: 'Standard team member with create access', permissions: [ Permission.TRANSCRIPT_CREATE, Permission.TRANSCRIPT_READ, Permission.TRANSCRIPT_DELETE, Permission.SUMMARY_GENERATE, Permission.SUMMARY_READ, Permission.ACTION_ITEM_CREATE, ], inherits: ['viewer'], }, manager: { name: 'Manager', description: 'Can manage team transcripts and members', permissions: [ Permission.TRANSCRIPT_READ_ALL, Permission.TRANSCRIPT_DELETE_ALL, Permission.ACTION_ITEM_ASSIGN, Permission.ACTION_ITEM_DELETE, Permission.TEAM_VIEW, Permission.TEAM_INVITE, ], inherits: ['member'], }, admin: { name: 'Admin', description: 'Full organizational admin access', permissions: [ Permission.TEAM_MANAGE, Permission.TEAM_REMOVE, Permission.SETTINGS_VIEW, Permission.SETTINGS_MANAGE, Permission.INTEGRATIONS_MANAGE, Permission.BILLING_VIEW, Permission.AUDIT_VIEW, ], inherits: ['manager'], }, owner: { name: 'Owner', description: 'Organization owner with full access', permissions: [ Permission.ADMIN_FULL, Permission.BILLING_MANAGE, Permission.COMPLIANCE_MANAGE, ], inherits: ['admin'], }, }; // Resolve all permissions for a role (including inherited) export function resolvePermissions(roleName: string): Permission[] { const role = predefinedRoles[roleName]; if (!role) return []; const permissions = new Set(role.permissions); for (const inheritedRole of role.inherits || []) { const inheritedPermissions = resolvePermissions(inheritedRole); inheritedPermissions.forEach(p => permissions.add(p)); } return Array.from(permissions); } ``` ### Step 2: Implement Permission Checking ```typescript // src/twinmind/rbac/authorization.ts import { Permission, resolvePermissions } from './roles'; export interface User { id: string; email: string; organizationId: string; role: string; customPermissions?: Permission[]; // Additional granted permissions deniedPermissions?: Permission[]; // Explicitly denied } export class AuthorizationService { hasPermission(user: User, permission: Permission): boolean { // Check for admin wildcard const userPermissions = this.getUserPermissions(user); if (userPermissions.includes(Permission.ADMIN_FULL)) { return true; } // Check denied list first if (user.deniedPermissions?.includes(permission)) { return false; } // Check for exact permission or wildcard return userPermissions.includes(permission) || this.matchesWildcard(permission, userPermissions); } hasAnyPermission(user: User, permissions: Permission[]): boolean { return permissions.some(p => this.hasPermission(user, p)); } hasAllPermissions(user: User, permissions: Permission[]): boolean { return permissions.every(p => this.hasPermission(user, p)); } private getUserPermissions(user: User): Permission[] { const rolePermissions = resolvePermissions(user.role); const customPermissions = user.customPermissions || []; return [...new Set([...rolePermissions, ...customPermissions])]; } private matchesWildcard(permission: Permission, userPermissions: Permission[]): boolean { const [resource] = permission.split(':'); const wildcardPermission = `${resource}:*` as Permission; return userPermissions.includes(wildcardPermission); } } // Express middleware export function requirePermission(...permissions: Permission[]) { const authService = new AuthorizationService(); return (req: Request, res: Response, next: NextFunction) => { const user = req.user as User; if (!user) { return res.status(401).json({ error: 'Unauthorized' }); } if (!authService.hasAnyPermission(user, permissions)) { return res.status(403).json({ error: 'Insufficient permissions', required: permissions, user_role: user.role, }); } next(); }; } // Resource-level authorization export function canAccessTranscript(user: User, transcript: Transcript): boolean { const authService = new AuthorizationService(); // Admins can access all if (authService.hasPermission(user, Permission.TRANSCRIPT_READ_ALL)) { return true; } // Check if user owns the transcript if (transcript.created_by === user.id) { return authService.hasPermission(user, Permission.TRANSCRIPT_READ); } // Check if user was a participant if (transcript.participants?.some(p => p.email === user.email)) { return authService.hasPermission(user, Permission.TRANSCRIPT_READ); } return false; } ``` ### Step 3: Configure SSO/SAML Integration ```typescript // src/twinmind/rbac/sso.ts import { Strategy as SamlStrategy } from 'passport-saml'; import passport from 'passport'; export interface SSOConfig { provider: 'okta' | 'azure' | 'google' | 'onelogin'; entryPoint: string; // IdP SSO URL issuer: string; // SP Entity ID cert: string; // IdP Certificate callbackUrl: string; // ACS URL signatureAlgorithm?: string; identifierFormat?: string; attributeMapping: { email: string; firstName: string; lastName: string; groups?: string; }; groupRoleMapping?: Record; // IdP group -> TwinMind role } export function configureSAML(config: SSOConfig): void { const samlStrategy = new SamlStrategy( { entryPoint: config.entryPoint, issuer: config.issuer, cert: config.cert, callbackUrl: config.callbackUrl, signatureAlgorithm: config.signatureAlgorithm || 'sha256', identifierFormat: config.identifierFormat || 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', }, async (profile, done) => { try { // Extract user attributes const email = profile[config.attributeMapping.email]; const firstName = profile[config.attributeMapping.firstName]; const lastName = profile[config.attributeMapping.lastName]; const groups = profile[config.attributeMapping.groups || 'groups'] || []; // Map IdP groups to TwinMind roles let role = 'member'; // Default role if (config.groupRoleMapping) { for (const [group, mappedRole] of Object.entries(config.groupRoleMapping)) { if (groups.includes(group)) { role = mappedRole; break; } } } // Find or create user const user = await findOrCreateUser({ email, firstName, lastName, role, authProvider: 'saml', authProviderId: profile.nameID, }); done(null, user); } catch (error) { done(error as Error); } } ); passport.use('saml', samlStrategy); } // Example Okta configuration export const oktaConfig: SSOConfig = { provider: 'okta', entryPoint: process.env.OKTA_SSO_URL!, issuer: process.env.OKTA_ISSUER!, cert: process.env.OKTA_CERT!, callbackUrl: `${process.env.APP_URL}/auth/saml/callback`, attributeMapping: { email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', firstName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname', lastName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname', groups: 'http://schemas.xmlsoap.org/claims/Group', }, groupRoleMapping: { 'TwinMind-Admins': 'admin', 'TwinMind-Managers': 'manager', 'TwinMind-Members': 'member', 'TwinMind-Viewers': 'viewer', }, }; ``` ### Step 4: Team Management ```typescript // src/twinmind/rbac/teams.ts export interface Team { id: string; name: string; organizationId: string; members: TeamMember[]; settings: TeamSettings; } export interface TeamMember { userId: string; email: string; role: string; joinedAt: Date; invitedBy: string; } export interface TeamSettings { defaultTranscriptVisibility: 'private' | 'team' | 'organization'; allowExternalSharing: boolean; requireApprovalForSharing: boolean; autoTranscribe: boolean; } export class TeamManager { private client = getTwinMindClient(); private authService = new AuthorizationService(); async createTeam(name: string, creatorId: string): Promise { const response = await this.client.post('/teams', { name, settings: { defaultTranscriptVisibility: 'team', allowExternalSharing: false, requireApprovalForSharing: true, autoTranscribe: true, }, }); // Add creator as owner await this.addMember(response.data.id, creatorId, 'owner'); return response.data; } async addMember( teamId: string, userId: string, role: string, invitedBy?: string ): Promise { await this.client.post(`/teams/${teamId}/members`, { user_id: userId, role, invited_by: invitedBy, }); // Sync to TwinMind organization await this.syncToTwinMind(teamId); } async removeMember(teamId: string, userId: string): Promise { await this.client.delete(`/teams/${teamId}/members/${userId}`); await this.syncToTwinMind(teamId); } async updateMemberRole(teamId: string, userId: string, newRole: string): Promise { await this.client.patch(`/teams/${teamId}/members/${userId}`, { role: newRole, }); } async getTeamMembers(teamId: string): Promise { const response = await this.client.get(`/teams/${teamId}/members`); return response.data; } async inviteMember( teamId: string, email: string, role: string, invitedBy: User ): Promise { // Check permission if (!this.authService.hasPermission(invitedBy, Permission.TEAM_INVITE)) { throw new Error('Insufficient permissions to invite team members'); } // Send invitation await this.client.post(`/teams/${teamId}/invitations`, { email, role, invited_by: invitedBy.id, }); // Send invitation email await sendInvitationEmail(email, teamId, invitedBy); } private async syncToTwinMind(teamId: string): Promise { const members = await this.getTeamMembers(teamId); await this.client.put(`/organization/members`, { members: members.map(m => ({ email: m.email, role: m.role, })), }); } } ``` ### Step 5: Audit Logging for Access ```typescript // src/twinmind/rbac/audit.ts export interface AccessAuditEvent { timestamp: Date; userId: string; userEmail: string; action: string; resource: string; resourceId?: string; granted: boolean; reason?: string; ipAddress?: string; userAgent?: string; metadata?: Record; } export class AccessAuditLogger { async logAccess(event: Omit): Promise { const fullEvent: AccessAuditEvent = { ...event, timestamp: new Date(), }; // Store in database await db.accessAuditLogs.create(fullEvent); // Send to SIEM if configured if (process.env.SIEM_ENDPOINT) { await this.sendToSIEM(fullEvent); } // Log denied access at warning level if (!event.granted) { logger.warn('Access denied', { user: event.userEmail, action: event.action, resource: event.resource, reason: event.reason, }); } } async queryAuditLogs(filters: { userId?: string; action?: string; resource?: string; granted?: boolean; from?: Date; to?: Date; limit?: number; }): Promise { return db.accessAuditLogs.find({ ...filters, timestamp: { $gte: filters.from, $lte: filters.to, }, }).limit(filters.limit || 100); } async generateAccessReport( userId: string, dateRange: { from: Date; to: Date } ): Promise<{ totalAccesses: number; deniedAccesses: number; resourcesAccessed: string[]; uniqueResources: number; }> { const logs = await this.queryAuditLogs({ userId, from: dateRange.from, to: dateRange.to, }); const resourcesAccessed = logs .filter(l => l.granted) .map(l => l.resource); return { totalAccesses: logs.filter(l => l.granted).length, deniedAccesses: logs.filter(l => !l.granted).length, resourcesAccessed, uniqueResources: new Set(resourcesAccessed).size, }; } private async sendToSIEM(event: AccessAuditEvent): Promise { await fetch(process.env.SIEM_ENDPOINT!, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: 'twinmind-integration', event_type: 'access_audit', ...event, }), }); } } // Middleware to audit all accesses export function auditMiddleware(auditLogger: AccessAuditLogger) { return async (req: Request, res: Response, next: NextFunction) => { const originalSend = res.send; const user = req.user as User; res.send = function(body) { const granted = res.statusCode < 400; auditLogger.logAccess({ userId: user?.id || 'anonymous', userEmail: user?.email || 'anonymous', action: `${req.method} ${req.path}`, resource: req.path.split('/')[2] || 'unknown', resourceId: req.params.id, granted, reason: !granted ? body?.error : undefined, ipAddress: req.ip, userAgent: req.headers['user-agent'], }); return originalSend.call(this, body); }; next(); }; } ``` ## Output - Role and permission definitions - Permission checking service - SSO/SAML configuration - Team management functionality - Access audit logging ## Role Hierarchy ``` Owner โ””โ”€โ”€ Admin โ””โ”€โ”€ Manager โ””โ”€โ”€ Member โ””โ”€โ”€ Viewer ``` ## Enterprise Features | Feature | Description | Configuration | |---------|-------------|---------------| | SSO/SAML | Single sign-on integration | IdP configuration | | SCIM | User provisioning | Automatic sync | | Custom roles | Define organization roles | Role builder | | Audit logs | Access tracking | 90-day retention | | IP allowlisting | Restrict access by IP | Network settings | ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | SSO login failed | Certificate mismatch | Update IdP cert | | Role not syncing | SCIM misconfigured | Check provisioning | | Permission denied | Wrong role assigned | Review role mapping | | Audit gaps | Middleware not applied | Add to all routes | ## Resources - [TwinMind Enterprise](https://twinmind.com/enterprise) - [SAML 2.0 Specification](https://docs.oasis-open.org/security/saml/v2.0/) - [SCIM Protocol](https://scim.cloud/) ## Next Steps For migration from other tools, see `twinmind-migration-deep-dive`.

Skill file: plugins/saas-packs/twinmind-pack/skills/twinmind-enterprise-rbac/SKILL.md