Атрибуция от бота в MCP/REST + REST API по ключу (этап 2)
- MCP: getActor (владелец ключа + бот), аудит changedByName=бот/label ключа, botId, metadata.source='mcp'; аудит для update_task/update_task_status - sendTaskMessage: botId (messageType 'bot'), починен путь isBotToken без req.user - authenticateTokenOrApiKey: JWT или X-Api-Key с белым списком endpoint'ов (/api/upload, messages, field-values, create task), проверки скоупов, аудит source='api' - Лимиты файлов через env: UPLOAD_IMAGE_MAX_MB=25, UPLOAD_DOC_MAX_MB=100, UPLOAD_MAX_MB=100 - MCP upload-инструменты: параметр fileUrl для привязки уже загруженного файла; новый инструмент get_api_guide
This commit is contained in:
@@ -16,6 +16,7 @@ import { validateRequiredFields } from "../utils/validate-required-fields";
|
||||
import { shiftRelatedTasks, calculateDateShift } from "../services/gantt-shift.service";
|
||||
import { runAutomationsByTrigger, type AutomationRunResult } from "./automation.routes";
|
||||
import { normalizeFieldValueForStorage } from "../utils/normalize-field-value";
|
||||
import { resolveRestActor, checkApiKeyWriteAccess } from "../utils/api-key-actor";
|
||||
import { db, withTenant } from "../db";
|
||||
import { eq, and, sql } from "drizzle-orm";
|
||||
|
||||
@@ -51,9 +52,16 @@ export function registerTaskCrudWriteRoutes(router: ReturnType<typeof import("ex
|
||||
return res.status(404).json({ success: false, error: 'Форма не найдена' });
|
||||
}
|
||||
|
||||
const canAccessForm = await storage.canUserAccessForm(req.user!.id, formId, req.organizationId!, 'participate');
|
||||
if (!canAccessForm) {
|
||||
return res.status(403).json({ success: false, error: 'Нет доступа к этой форме' });
|
||||
if (req.user) {
|
||||
const canAccessForm = await storage.canUserAccessForm(req.user.id, formId, req.organizationId!, 'participate');
|
||||
if (!canAccessForm) {
|
||||
return res.status(403).json({ success: false, error: 'Нет доступа к этой форме' });
|
||||
}
|
||||
}
|
||||
// Проверки для авторизации по API-ключу (JWT-путь не меняется)
|
||||
const apiKeyErrCreate = checkApiKeyWriteAccess(req, formId);
|
||||
if (apiKeyErrCreate) {
|
||||
return res.status(403).json({ success: false, error: apiKeyErrCreate });
|
||||
}
|
||||
|
||||
const { customFields, dueDate, ...baseTaskData } = req.body;
|
||||
@@ -137,11 +145,17 @@ export function registerTaskCrudWriteRoutes(router: ReturnType<typeof import("ex
|
||||
}
|
||||
}
|
||||
|
||||
// Автор: пользователь (JWT) или владелец API-ключа
|
||||
const createdByUserId = req.user?.id ?? req.apiKey?.createdBy;
|
||||
if (!createdByUserId) {
|
||||
return res.status(401).json({ success: false, error: 'Не удалось определить автора задачи' });
|
||||
}
|
||||
|
||||
const taskData = {
|
||||
...baseTaskData,
|
||||
formId,
|
||||
organizationId: req.organizationId!,
|
||||
createdBy: req.user!.id,
|
||||
createdBy: createdByUserId,
|
||||
dueDate: parsedDueDate,
|
||||
parentTaskId,
|
||||
};
|
||||
@@ -197,14 +211,15 @@ export function registerTaskCrudWriteRoutes(router: ReturnType<typeof import("ex
|
||||
}
|
||||
}
|
||||
|
||||
const creatorName = req.user ? (`${req.user.firstName || ''} ${req.user.middleName || ''} ${req.user.lastName || ''}`.trim() || req.user.email) : 'API';
|
||||
const creatorActor = await resolveRestActor(req, req.organizationId!);
|
||||
storage.addTaskAuditLog({
|
||||
taskId: task.id,
|
||||
organizationId: req.organizationId!,
|
||||
action: 'task.created',
|
||||
changedBy: req.user?.id ?? null,
|
||||
changedByName: creatorName,
|
||||
metadata: { title: task.title },
|
||||
changedBy: creatorActor.changedBy,
|
||||
changedByName: creatorActor.changedByName,
|
||||
botId: creatorActor.botId,
|
||||
metadata: { title: task.title, ...(creatorActor.source ? { source: creatorActor.source } : {}) },
|
||||
}).catch((auditErr: unknown) => { console.error('Audit log error:', auditErr); });
|
||||
|
||||
if (customFields && typeof customFields === 'object') {
|
||||
@@ -307,8 +322,8 @@ export function registerTaskCrudWriteRoutes(router: ReturnType<typeof import("ex
|
||||
finalTask = await storage.updateTask(task.id, req.organizationId!, { title: computedTitle });
|
||||
}
|
||||
|
||||
if (finalTask.assignedTo && finalTask.assignedTo !== req.user!.id) {
|
||||
notifyTaskAssigned(finalTask, finalTask.assignedTo, req.user!.id, req.organizationId!)
|
||||
if (finalTask.assignedTo && finalTask.assignedTo !== req.user?.id) {
|
||||
notifyTaskAssigned(finalTask, finalTask.assignedTo, req.user?.id ?? null, req.organizationId!)
|
||||
.catch((err) => console.error('[TaskCreate] notifyTaskAssigned error:', err));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user