Права доступа API-ключей: скоупы read/write/full и выборочный доступ к формам и справочникам

- organization_api_keys.scopes (jsonb, NULL = полный доступ для legacy-ключей)
- MCP: фильтрация инструментов по режиму и проверка formIds в handler'ах
- PATCH /api/mcp-keys/:id, валидация скоупов в server/utils/api-key.ts
- UI Settings: диалог создания/редактирования ключа с режимом и мультивыбором форм/справочников
This commit is contained in:
2026-07-21 16:12:55 +03:00
parent 08b0979ca2
commit a7733b4a03
8 changed files with 660 additions and 103 deletions

View File

@@ -2557,6 +2557,14 @@ export type WebPushSubscription = typeof webPushSubscriptions.$inferSelect;
// Organization API Keys (for MCP server access)
// =====================
// Скоупы прав доступа API-ключа.
// NULL в БД = полный доступ (legacy-ключи, созданные до введения скоупов).
export type ApiKeyScopes = {
mode: 'read' | 'write' | 'full'; // read = только чтение; write = чтение + создание; full = всё
formIds: number[] | null; // null = все формы
tableIds: number[] | null; // null = все справочники
};
export const organizationApiKeys = pgTable("organization_api_keys", {
id: serial("id").primaryKey(),
organizationId: integer("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
@@ -2568,6 +2576,7 @@ export const organizationApiKeys = pgTable("organization_api_keys", {
lastUsedAt: timestamp("last_used_at"),
isActive: boolean("is_active").notNull().default(true),
isLegacy: boolean("is_legacy").notNull().default(false),
scopes: jsonb("scopes").$type<ApiKeyScopes>(),
}, (table) => ({
orgIndex: index("api_keys_org_idx").on(table.organizationId),
hashIndex: index("api_keys_hash_idx").on(table.keyHash),