Chuyển đến nội dung chính

Bài 17: Multi-tenant RBAC — Tenant Isolation & Permissions

Multi-tenant architecture: tenant isolation, data partitioning. RBAC system: roles, permissions, policies. 60 granular permissions, 4 system roles. Tenant settings, usage quotas, billing.

🧠 AI & ML — Bài 16 Bài 17: Multi-tenant RBAC — Tenant Isolation & Permissions

Xây dựng AI Agent Platform từ Zero — Thực chiến với xClaw

Phần 6: Multi-tenant, RBAC & Channels

xdev.asia

Giới thiệu

Multi-tenant platform phải đảm bảo data isolation — tenant A không thể access data của tenant B. RBAC (Role-Based Access Control) kiểm soát ai được làm gì. Bài này implement full multi-tenant RBAC system.


1. Tenant Isolation

1.1 Data Partitioning Strategy

// Approach: Shared database, tenant_id column
// Mỗi query phải filter theo tenantId

// packages/db/src/middleware/tenant-middleware.ts
export function tenantScope(tenantId: string) {
  return {
    // PostgreSQL: Drizzle ORM filter
    pg: <T extends { tenantId: string }>(query: any) =>
      query.where(eq(schema.tenantId, tenantId)),

    // MongoDB: add tenantId to all queries
    mongo: (filter: Record<string, unknown>) => ({
      ...filter,
      tenantId,
    }),
  };
}

// Usage in service layer
async function getSessions(tenantId: string, userId: string) {
  return db.collection('sessions')
    .find(tenantScope(tenantId).mongo({ userId }))
    .sort({ updatedAt: -1 })
    .toArray();
}

1.2 Tenant Settings

interface TenantSettings {
  // LLM Configuration
  llm: {
    defaultProvider: string;
    defaultModel: string;
    apiKeys: Record<string, string>; // encrypted
    maxTokensPerRequest: number;
  };

  // Usage Quotas
  quotas: {
    maxUsersPerTenant: number;
    maxRequestsPerDay: number;
    maxStorageMB: number;
    maxCollections: number;
  };

  // Features
  features: {
    ragEnabled: boolean;
    workflowsEnabled: boolean;
    mcpEnabled: boolean;
    customDomainsEnabled: boolean;
  };

  // Branding
  branding: {
    logo?: string;
    primaryColor?: string;
    appName?: string;
  };
}

2. RBAC System

2.1 Permission Definition

// packages/core/src/auth/permissions.ts
export const PERMISSIONS = {
  // Chat
  'chat:send': 'Send chat messages',
  'chat:view_history': 'View chat history',
  'chat:delete': 'Delete chat sessions',
  'chat:export': 'Export chat data',

  // Models
  'models:view': 'View available models',
  'models:configure': 'Configure model settings',

  // Knowledge Base (RAG)
  'knowledge:view': 'View knowledge collections',
  'knowledge:create': 'Create collections',
  'knowledge:upload': 'Upload documents',
  'knowledge:delete': 'Delete collections/documents',

  // Workflows
  'workflows:view': 'View workflows',
  'workflows:create': 'Create workflows',
  'workflows:execute': 'Execute workflows',
  'workflows:delete': 'Delete workflows',

  // Skills & Domains
  'skills:view': 'View available skills',
  'skills:activate': 'Activate/deactivate skills',
  'skills:manage': 'Manage skill configurations',

  // Users & Roles
  'users:view': 'View user list',
  'users:invite': 'Invite new users',
  'users:remove': 'Remove users',
  'roles:view': 'View roles',
  'roles:manage': 'Create/edit/delete roles',

  // Tenant
  'tenant:settings': 'Manage tenant settings',
  'tenant:billing': 'View/manage billing',
  'tenant:api_keys': 'Manage API keys',

  // Monitoring
  'monitoring:view': 'View usage analytics',
  'monitoring:logs': 'View system logs',
  'monitoring:audit': 'View audit trail',
} as const;

export type Permission = keyof typeof PERMISSIONS;

2.2 System Roles

export const SYSTEM_ROLES = {
  owner: {
    name: 'Owner',
    permissions: Object.keys(PERMISSIONS) as Permission[], // All 60 permissions
    description: 'Full access to everything',
  },
  admin: {
    name: 'Admin',
    permissions: Object.keys(PERMISSIONS).filter(
      p => !p.startsWith('tenant:') // Everything except tenant management
    ) as Permission[],
    description: 'Manage users, content, and settings',
  },
  member: {
    name: 'Member',
    permissions: [
      'chat:send', 'chat:view_history',
      'models:view',
      'knowledge:view', 'knowledge:upload',
      'workflows:view', 'workflows:execute',
      'skills:view', 'skills:activate',
      'monitoring:view',
    ] as Permission[],
    description: 'Standard user access',
  },
  viewer: {
    name: 'Viewer',
    permissions: [
      'chat:send', 'chat:view_history',
      'models:view',
      'knowledge:view',
      'workflows:view',
      'skills:view',
    ] as Permission[],
    description: 'Read-only access',
  },
};

2.3 Permission Checker

// packages/core/src/auth/rbac.ts
export class RBACService {
  private db: DrizzleDB;

  async hasPermission(
    userId: string,
    tenantId: string,
    permission: Permission,
  ): Promise<boolean> {
    const userRoles = await this.db
      .select()
      .from(schema.userRoles)
      .innerJoin(schema.roles, eq(schema.roles.id, schema.userRoles.roleId))
      .where(
        and(
          eq(schema.userRoles.userId, userId),
          eq(schema.roles.tenantId, tenantId),
        ),
      );

    for (const ur of userRoles) {
      const perms = ur.roles.permissions as string[];
      if (perms.includes(permission)) return true;
    }

    return false;
  }

  async getUserPermissions(
    userId: string,
    tenantId: string,
  ): Promise<Permission[]> {
    const userRoles = await this.db
      .select()
      .from(schema.userRoles)
      .innerJoin(schema.roles, eq(schema.roles.id, schema.userRoles.roleId))
      .where(
        and(
          eq(schema.userRoles.userId, userId),
          eq(schema.roles.tenantId, tenantId),
        ),
      );

    const permissions = new Set<Permission>();
    for (const ur of userRoles) {
      const perms = ur.roles.permissions as Permission[];
      perms.forEach(p => permissions.add(p));
    }

    return Array.from(permissions);
  }
}

3. Audit Trail

// Mọi action quan trọng đều được log
async function auditLog(
  tenantId: string,
  userId: string,
  action: string,
  details: Record<string, unknown>,
) {
  await db.collection('audit_logs').insertOne({
    tenantId,
    userId,
    action,
    details,
    ip: context.ip,
    userAgent: context.userAgent,
    createdAt: new Date(),
  });
}

// Usage
await auditLog(tenantId, userId, 'role:assigned', {
  targetUserId: newUser.id,
  roleName: 'member',
});

4. Tổng kết

  • Tenant isolation — shared DB with tenantId filtering
  • 60 permissions — granular, grouped by feature
  • 4 system roles — owner, admin, member, viewer
  • Custom roles — tenants can create their own
  • Audit trail — every significant action logged

Bài tiếp theo: Chat Channels — Kết nối multi-platform messaging.