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
tenantIdfiltering - 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.