簡介
多租戶平台必須保證資料隔離-租戶A無法存取租戶B的資料。 RBAC(基於角色的存取控制)控制誰可以做什麼。本文實現了一個完整的多租戶RBAC系統。
1. 租戶隔離
1.1 資料分割區策略
// 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 租戶設置
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系統
2.1 權限定義
// 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 系統角色
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 權限檢查器
// 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. 審計追踪
// 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. 總結
- 租用戶隔離 — 與以下人員共用資料庫
tenantId濾 - 60 權限 — 細化,依功能分組
- 4 個系統角色 — 擁有者、管理者、成員、檢視者
- 自訂角色 — 租用戶可以創建自己的角色
- 審計追蹤 — 記錄的每項重要操作
下一篇文章: 聊天頻道 — 多平台訊息連線。