feat: add engine workflow access requests
This commit is contained in:
@@ -16,6 +16,7 @@ const collectionKeys = [
|
||||
"accessRequests",
|
||||
"revokedAccounts",
|
||||
"taskerInviteRequests",
|
||||
"engineWorkflowAccessRequests",
|
||||
"syncStatuses",
|
||||
"auditEvents",
|
||||
"taskManagerMemberships",
|
||||
@@ -36,6 +37,8 @@ const serviceModuleIds = new Set(["codex_agents"]);
|
||||
const accessRequestStatuses = new Set(["new", "approved", "rejected"]);
|
||||
const taskerInviteRequestStatuses = new Set(["new", "approved", "rejected", "cancelled"]);
|
||||
const taskManagerInviteRoles = new Set(["guest", "member", "admin"]);
|
||||
const engineWorkflowAccessRequestStatuses = new Set(["new", "approved", "rejected", "cancelled"]);
|
||||
const engineWorkflowRoles = new Set(["viewer", "editor", "admin"]);
|
||||
const publicPoolClientId = "client_public_pool";
|
||||
const engineAuthentikGroups = [
|
||||
"nodedc:engine:admin",
|
||||
@@ -1130,6 +1133,141 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||
return { taskerInviteRequest: request, data };
|
||||
}
|
||||
|
||||
async function createEngineWorkflowAccessRequest(payload, identity = { name: "NODE.DC Engine", source: "engine" }) {
|
||||
const data = readData();
|
||||
const now = isoNow();
|
||||
const actor = resolveActor(data, identity);
|
||||
const workflowId = requireString(payload?.workflowId, "workflowId");
|
||||
const workflowName = optionalString(payload?.workflowName, workflowId);
|
||||
const targetEmail = normalizeEmail(requireString(payload?.targetEmail ?? payload?.email, "targetEmail"));
|
||||
const role = normalizeEngineWorkflowRole(payload?.role);
|
||||
|
||||
if (!isValidEmail(targetEmail)) {
|
||||
throw new Error("Введите корректную электронную почту");
|
||||
}
|
||||
|
||||
const targetUser = data.users.find((user) => normalizeEmail(user.email) === targetEmail && user.globalStatus === "active");
|
||||
if (!targetUser) {
|
||||
throw new Error("engine_target_user_not_found");
|
||||
}
|
||||
|
||||
const requesterEmail = normalizeEmail(payload?.requesterEmail ?? actor.email ?? "");
|
||||
const requesterUser =
|
||||
(payload?.requesterUserId ? data.users.find((user) => user.id === payload.requesterUserId) : null) ??
|
||||
(requesterEmail ? data.users.find((user) => normalizeEmail(user.email) === requesterEmail) : null) ??
|
||||
null;
|
||||
const requesterName = optionalString(payload?.requesterName, requesterUser?.name ?? actor.name ?? "NODE.DC Engine");
|
||||
const existingRequest = data.engineWorkflowAccessRequests.find(
|
||||
(request) =>
|
||||
request.status === "new" &&
|
||||
request.workflowId === workflowId &&
|
||||
request.targetUserId === targetUser.id
|
||||
);
|
||||
const request =
|
||||
existingRequest ??
|
||||
{
|
||||
id: uniqueId(data.engineWorkflowAccessRequests, "engine_workflow_access_request", `${workflowId}-${targetEmail}`),
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
Object.assign(request, {
|
||||
workflowId,
|
||||
workflowName,
|
||||
targetUserId: targetUser.id,
|
||||
targetEmail,
|
||||
targetName: targetUser.name ?? null,
|
||||
role,
|
||||
requesterUserId: requesterUser?.id ?? nullableStringWithFallback(payload?.requesterUserId, null),
|
||||
requesterEmail: requesterEmail || normalizeEmail(payload?.requesterEmail) || actor.email || "engine@nodedc.ru",
|
||||
requesterName,
|
||||
status: "new",
|
||||
reviewedByUserId: null,
|
||||
reviewedAt: null,
|
||||
engineAppliedAt: null,
|
||||
comment: nullableStringWithFallback(payload?.comment, existingRequest?.comment ?? null),
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
if (!existingRequest) {
|
||||
data.engineWorkflowAccessRequests.push(request);
|
||||
}
|
||||
|
||||
addAuditEvent(data, actor, {
|
||||
action: existingRequest ? "Обновлена заявка доступа Engine workflow" : "Создана заявка доступа Engine workflow",
|
||||
objectType: "engine_workflow_access_request",
|
||||
objectName: `${workflowName}:${targetEmail}`,
|
||||
result: "success",
|
||||
details: `Role: ${role}; requester: ${request.requesterEmail}`,
|
||||
});
|
||||
|
||||
await writeData(data);
|
||||
return {
|
||||
engineWorkflowAccessRequest: request,
|
||||
affectedUserIds: [request.requesterUserId, targetUser.id, "user_root"].filter((userId) => typeof userId === "string" && userId),
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
async function approveEngineWorkflowAccessRequest(engineWorkflowAccessRequestId, payload, identity) {
|
||||
const data = readData();
|
||||
const actor = resolveActor(data, identity);
|
||||
const request = findEngineWorkflowAccessRequestById(data, engineWorkflowAccessRequestId);
|
||||
const now = isoNow();
|
||||
|
||||
if (request.status === "rejected") {
|
||||
throw new Error("Отклонённую заявку Engine нельзя подтвердить");
|
||||
}
|
||||
|
||||
const targetUser = findById(data.users, request.targetUserId, "user");
|
||||
const grant = ensureEngineWorkflowServiceAccess(data, targetUser, request.role, now);
|
||||
|
||||
request.status = "approved";
|
||||
request.reviewedByUserId = actor.id;
|
||||
request.reviewedAt = now;
|
||||
request.engineAppliedAt = nullableStringWithFallback(payload?.engineAppliedAt, now);
|
||||
request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
|
||||
request.updatedAt = now;
|
||||
|
||||
addAuditEvent(data, actor, {
|
||||
action: "Подтверждена заявка доступа Engine workflow",
|
||||
objectType: "engine_workflow_access_request",
|
||||
objectName: `${request.workflowName}:${request.targetEmail}`,
|
||||
result: "success",
|
||||
details: `Workflow: ${request.workflowId}; role: ${request.role}`,
|
||||
});
|
||||
|
||||
await writeData(data);
|
||||
return { engineWorkflowAccessRequest: request, grant, targetUser, data };
|
||||
}
|
||||
|
||||
async function rejectEngineWorkflowAccessRequest(engineWorkflowAccessRequestId, payload, identity) {
|
||||
const data = readData();
|
||||
const actor = resolveActor(data, identity);
|
||||
const request = findEngineWorkflowAccessRequestById(data, engineWorkflowAccessRequestId);
|
||||
const now = isoNow();
|
||||
|
||||
if (request.status === "approved") {
|
||||
throw new Error("Подтверждённую заявку Engine нельзя отклонить");
|
||||
}
|
||||
|
||||
request.status = "rejected";
|
||||
request.reviewedByUserId = actor.id;
|
||||
request.reviewedAt = now;
|
||||
request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
|
||||
request.updatedAt = now;
|
||||
|
||||
addAuditEvent(data, actor, {
|
||||
action: "Отклонена заявка доступа Engine workflow",
|
||||
objectType: "engine_workflow_access_request",
|
||||
objectName: `${request.workflowName}:${request.targetEmail}`,
|
||||
result: "warning",
|
||||
details: request.comment ?? null,
|
||||
});
|
||||
|
||||
await writeData(data);
|
||||
return { engineWorkflowAccessRequest: request, data };
|
||||
}
|
||||
|
||||
async function cancelTaskerInviteRequest(payload, identity = { name: "Operational Core", source: "tasker" }) {
|
||||
const data = readData();
|
||||
const actor = resolveActor(data, identity);
|
||||
@@ -1866,10 +2004,12 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||
|
||||
return {
|
||||
approveAccessRequest,
|
||||
approveEngineWorkflowAccessRequest,
|
||||
approveTaskerInviteRequest,
|
||||
buildAuthentikSyncPlan,
|
||||
cancelTaskerInviteRequest,
|
||||
createAccessRequest,
|
||||
createEngineWorkflowAccessRequest,
|
||||
createTaskerInviteRequest,
|
||||
createClient,
|
||||
createGroup,
|
||||
@@ -1883,6 +2023,7 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||
deleteService,
|
||||
deleteUser,
|
||||
rejectAccessRequest,
|
||||
rejectEngineWorkflowAccessRequest,
|
||||
rejectTaskerInviteRequest,
|
||||
acceptInvite,
|
||||
commitInviteRegistration,
|
||||
@@ -1945,6 +2086,7 @@ function normalizeData(payload) {
|
||||
data.revokedAccounts = data.revokedAccounts.map(normalizeRevokedAccount).filter(Boolean);
|
||||
data.serviceModuleEntitlements = data.serviceModuleEntitlements.map(normalizeServiceModuleEntitlement).filter(Boolean);
|
||||
data.taskerInviteRequests = data.taskerInviteRequests.map(normalizeTaskerInviteRequest).filter(Boolean);
|
||||
data.engineWorkflowAccessRequests = data.engineWorkflowAccessRequests.map(normalizeEngineWorkflowAccessRequest).filter(Boolean);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -2079,6 +2221,37 @@ function normalizeTaskerInviteRequest(payload) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeEngineWorkflowAccessRequest(payload) {
|
||||
if (typeof payload !== "object" || payload === null) return null;
|
||||
const now = isoNow();
|
||||
const workflowId = typeof payload.workflowId === "string" ? payload.workflowId.trim() : "";
|
||||
const targetUserId = typeof payload.targetUserId === "string" ? payload.targetUserId.trim() : "";
|
||||
const targetEmail = normalizeEmail(payload.targetEmail ?? payload.email);
|
||||
const requesterEmail = normalizeEmail(payload.requesterEmail);
|
||||
|
||||
if (!workflowId || !targetUserId || !targetEmail || !requesterEmail) return null;
|
||||
|
||||
return {
|
||||
id: optionalString(payload.id, `engine_workflow_access_request_${slugify(`${workflowId}-${targetEmail}`)}`),
|
||||
workflowId,
|
||||
workflowName: optionalString(payload.workflowName, workflowId),
|
||||
targetUserId,
|
||||
targetEmail,
|
||||
targetName: nullableStringWithFallback(payload.targetName, null),
|
||||
role: normalizeEngineWorkflowRole(payload.role),
|
||||
requesterUserId: nullableStringWithFallback(payload.requesterUserId, null),
|
||||
requesterEmail,
|
||||
requesterName: optionalString(payload.requesterName, requesterEmail),
|
||||
status: pickEnum(payload.status, engineWorkflowAccessRequestStatuses, "new"),
|
||||
reviewedByUserId: nullableStringWithFallback(payload.reviewedByUserId, null),
|
||||
reviewedAt: nullableStringWithFallback(payload.reviewedAt, null),
|
||||
engineAppliedAt: nullableStringWithFallback(payload.engineAppliedAt, null),
|
||||
comment: nullableStringWithFallback(payload.comment, null),
|
||||
createdAt: optionalString(payload.createdAt, now),
|
||||
updatedAt: optionalString(payload.updatedAt, now),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSettings(payload) {
|
||||
const settings = typeof payload === "object" && payload !== null ? payload : {};
|
||||
const brand = typeof settings.brand === "object" && settings.brand !== null ? settings.brand : {};
|
||||
@@ -2457,6 +2630,52 @@ function ensureTaskerInviteServiceAccess(data, invite, user, now) {
|
||||
return grant;
|
||||
}
|
||||
|
||||
function ensureEngineWorkflowServiceAccess(data, user, workflowRole, now) {
|
||||
const service = findEngineService(data);
|
||||
if (!service) {
|
||||
throw new Error("engine_service_not_found");
|
||||
}
|
||||
|
||||
const requestedAppRole = workflowRole === "viewer" ? "viewer" : "member";
|
||||
data.exceptions = data.exceptions.filter((exception) => !(exception.serviceId === service.id && exception.userId === user.id));
|
||||
const existingGrant = data.grants.find(
|
||||
(grant) => grant.serviceId === service.id && grant.targetType === "user" && grant.targetId === user.id
|
||||
);
|
||||
|
||||
if (existingGrant) {
|
||||
existingGrant.status = "active";
|
||||
existingGrant.appRole =
|
||||
existingGrant.appRole === "admin" || existingGrant.appRole === "owner" ? existingGrant.appRole : requestedAppRole;
|
||||
existingGrant.updatedAt = now;
|
||||
markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
|
||||
return existingGrant;
|
||||
}
|
||||
|
||||
const grant = {
|
||||
id: uniqueId(data.grants, "grant", `${service.slug}-user-${user.email}`),
|
||||
serviceId: service.id,
|
||||
targetType: "user",
|
||||
targetId: user.id,
|
||||
appRole: requestedAppRole,
|
||||
status: "active",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
data.grants.push(grant);
|
||||
markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
|
||||
return grant;
|
||||
}
|
||||
|
||||
function findEngineService(data) {
|
||||
return data.services.find(
|
||||
(candidate) =>
|
||||
candidate.id === "service_nodedc" ||
|
||||
candidate.slug === "nodedc" ||
|
||||
candidate.slug === "engine" ||
|
||||
candidate.authentikApplicationSlug === "nodedc-engine"
|
||||
);
|
||||
}
|
||||
|
||||
function hasTaskManagerDenyException(data, userId) {
|
||||
const service = data.services.find((candidate) => candidate.slug === "task-manager");
|
||||
if (!service) {
|
||||
@@ -2696,6 +2915,10 @@ function findTaskerInviteRequestById(data, taskerInviteRequestId) {
|
||||
return findById(data.taskerInviteRequests, taskerInviteRequestId, "tasker_invite_request");
|
||||
}
|
||||
|
||||
function findEngineWorkflowAccessRequestById(data, engineWorkflowAccessRequestId) {
|
||||
return findById(data.engineWorkflowAccessRequests, engineWorkflowAccessRequestId, "engine_workflow_access_request");
|
||||
}
|
||||
|
||||
function resolveAccessRequestTargetClientId(data, value, fallback = publicPoolClientId) {
|
||||
const clientId = optionalString(value, fallback || publicPoolClientId);
|
||||
findClientById(data, clientId);
|
||||
@@ -2801,6 +3024,11 @@ function normalizeTaskManagerInviteRole(value) {
|
||||
return taskManagerInviteRoles.has(normalized) ? normalized : "member";
|
||||
}
|
||||
|
||||
function normalizeEngineWorkflowRole(value) {
|
||||
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
return engineWorkflowRoles.has(normalized) ? normalized : "viewer";
|
||||
}
|
||||
|
||||
function isValidEmail(email) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
}
|
||||
|
||||
@@ -547,6 +547,69 @@ app.post("/api/internal/tasker/invite-requests/cancel", asyncRoute(async (req, r
|
||||
res.json({ ok: true, taskerInviteRequest: result.taskerInviteRequest });
|
||||
}));
|
||||
|
||||
app.post("/api/internal/engine/workflow-access-requests", asyncRoute(async (req, res) => {
|
||||
if (!isInternalRequestAuthorized(req)) {
|
||||
res.status(config.internalAccessToken ? 401 : 503).json({
|
||||
ok: false,
|
||||
error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC Engine workflow access request" });
|
||||
const requesterPayload = typeof req.body?.requester === "object" && req.body.requester !== null ? req.body.requester : {};
|
||||
const requester = findInternalAccessUser(snapshot.data, {
|
||||
subject: requesterPayload.subject,
|
||||
email: requesterPayload.email,
|
||||
userId: requesterPayload.userId,
|
||||
});
|
||||
const targetEmail = typeof req.body?.targetEmail === "string" ? req.body.targetEmail.trim().toLowerCase() : "";
|
||||
const targetUser = findInternalAccessUser(snapshot.data, { email: targetEmail });
|
||||
|
||||
if (!requester) {
|
||||
res.status(404).json({ ok: false, error: "requester_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetUser || targetUser.globalStatus !== "active") {
|
||||
res.status(404).json({ ok: false, error: "user_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const groups = resolveRequiredGroups(snapshot.data, targetUser);
|
||||
const app = getAppsForUser(groups).find((candidate) => candidate.slug === "nodedc");
|
||||
|
||||
if (app?.hasAccess) {
|
||||
res.json({
|
||||
ok: true,
|
||||
alreadyAllowed: true,
|
||||
targetUser: {
|
||||
id: targetUser.id,
|
||||
email: targetUser.email,
|
||||
name: targetUser.name,
|
||||
avatarUrl: targetUser.avatarUrl ?? null,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await controlPlaneStore.createEngineWorkflowAccessRequest({
|
||||
workflowId: req.body?.workflow?.id ?? req.body?.workflowId,
|
||||
workflowName: req.body?.workflow?.name ?? req.body?.workflowName,
|
||||
targetEmail,
|
||||
role: req.body?.role,
|
||||
requesterUserId: requester.id,
|
||||
requesterEmail: requester.email,
|
||||
requesterName: requester.name,
|
||||
}, requester);
|
||||
|
||||
publishControlPlaneEvent(
|
||||
"engine.workflow-access-request.created",
|
||||
result.affectedUserIds?.length ? result.affectedUserIds : [requester.id, targetUser.id]
|
||||
);
|
||||
res.json({ ok: true, engineWorkflowAccessRequest: result.engineWorkflowAccessRequest });
|
||||
}));
|
||||
|
||||
app.post("/api/internal/tasker/profile-sync", asyncRoute(async (req, res) => {
|
||||
if (!isInternalRequestAuthorized(req)) {
|
||||
res.status(config.internalAccessToken ? 401 : 503).json({
|
||||
@@ -1372,6 +1435,56 @@ app.post("/api/admin/tasker-invite-requests/:taskerInviteRequestId/reject", requ
|
||||
res.json(scopeAdminMutationResult(req, { ...result, tasker: taskerResult }));
|
||||
}));
|
||||
|
||||
app.post("/api/admin/engine-workflow-access-requests/:engineWorkflowAccessRequestId/approve", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
|
||||
const request = snapshot.data.engineWorkflowAccessRequests.find(
|
||||
(candidate) => candidate.id === req.params.engineWorkflowAccessRequestId
|
||||
);
|
||||
|
||||
if (!request) {
|
||||
res.status(404).json({ error: "engine_workflow_access_request_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const engineResult = await requestEngineInternalJson(`/api/internal/workflows/${encodeURIComponent(request.workflowId)}/share/users`, {
|
||||
body: {
|
||||
email: request.targetEmail,
|
||||
role: request.role,
|
||||
requestId: request.id,
|
||||
approvedBy: {
|
||||
userId: req.nodedcSession.user?.id,
|
||||
email: req.nodedcSession.user?.email,
|
||||
name: req.nodedcSession.user?.name,
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = await controlPlaneStore.approveEngineWorkflowAccessRequest(
|
||||
req.params.engineWorkflowAccessRequestId,
|
||||
{ engineAppliedAt: engineResult.updatedAt ?? new Date().toISOString(), comment: req.body?.comment },
|
||||
req.nodedcSession.user
|
||||
);
|
||||
const syncResult = await syncUsersToAuthentik(result.data, [result.targetUser.id], req.nodedcSession.user);
|
||||
|
||||
publishControlPlaneEvent("admin.engine-workflow-access-request.approved", [
|
||||
result.engineWorkflowAccessRequest.requesterUserId,
|
||||
result.targetUser.id,
|
||||
]);
|
||||
res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data, engine: engineResult }));
|
||||
}));
|
||||
|
||||
app.post("/api/admin/engine-workflow-access-requests/:engineWorkflowAccessRequestId/reject", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
const result = await controlPlaneStore.rejectEngineWorkflowAccessRequest(
|
||||
req.params.engineWorkflowAccessRequestId,
|
||||
req.body,
|
||||
req.nodedcSession.user
|
||||
);
|
||||
|
||||
publishControlPlaneEvent("admin.engine-workflow-access-request.rejected", [
|
||||
result.engineWorkflowAccessRequest.requesterUserId,
|
||||
]);
|
||||
res.json(scopeAdminMutationResult(req, result));
|
||||
}));
|
||||
|
||||
app.post("/api/admin/groups", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
if (!assertAdminCanManageClient(req, res, req.body?.clientId)) {
|
||||
return;
|
||||
@@ -1670,6 +1783,11 @@ function readConfig() {
|
||||
taskInternalLogoutUrl:
|
||||
process.env.TASK_INTERNAL_LOGOUT_URL ??
|
||||
`${(process.env.TASK_BASE_URL ?? `http://${process.env.TASK_DOMAIN ?? "task.local.nodedc"}`).replace(/\/$/, "")}/api/internal/nodedc/logout/`,
|
||||
engineBaseUrl:
|
||||
process.env.NODEDC_ENGINE_INTERNAL_URL ??
|
||||
process.env.NODEDC_ENGINE_BASE_URL ??
|
||||
process.env.ENGINE_BASE_URL ??
|
||||
"https://engine.nodedc.ru",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2232,6 +2350,10 @@ function getTaskBaseUrl() {
|
||||
return taskBaseUrl.replace(/\/$/, "");
|
||||
}
|
||||
|
||||
function getEngineBaseUrl() {
|
||||
return String(config.engineBaseUrl || "https://engine.nodedc.ru").replace(/\/$/, "");
|
||||
}
|
||||
|
||||
async function requestTaskManagerInternalJson(pathname, init = {}) {
|
||||
if (!config.internalAccessToken) {
|
||||
throw new Error("NODE.DC internal access token is not configured");
|
||||
@@ -2260,6 +2382,37 @@ async function requestTaskManagerInternalJson(pathname, init = {}) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function requestEngineInternalJson(pathname, init = {}) {
|
||||
if (!config.internalAccessToken) {
|
||||
throw new Error("NODE.DC internal access token is not configured");
|
||||
}
|
||||
|
||||
const targetUrl = new URL(pathname, `${getEngineBaseUrl()}/`);
|
||||
const hasBody = typeof init.body === "object" && init.body !== null;
|
||||
const response = await fetch(targetUrl, {
|
||||
method: init.method ?? (hasBody ? "POST" : "GET"),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${config.internalAccessToken}`,
|
||||
"X-Authentik-Groups": "nodedc_admin nodedc:engine:admin",
|
||||
"X-Authentik-Email": "launcher-internal@nodedc.ru",
|
||||
"X-Authentik-Username": "launcher-internal@nodedc.ru",
|
||||
...(hasBody ? { "Content-Type": "application/json" } : {}),
|
||||
...(init.headers ?? {}),
|
||||
},
|
||||
body: hasBody ? JSON.stringify(init.body) : undefined,
|
||||
});
|
||||
const text = await response.text();
|
||||
const payload = text ? parseJsonResponse(text, targetUrl.toString()) : {};
|
||||
|
||||
if (!response.ok) {
|
||||
const error = typeof payload?.error === "string" ? payload.error : `Engine internal API failed: ${response.status}`;
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function syncTaskManagerUserProfile(user) {
|
||||
if (!user?.email || !config.internalAccessToken) {
|
||||
return null;
|
||||
@@ -3363,6 +3516,7 @@ function scopeControlPlaneData(data, scope) {
|
||||
invites: data.invites.filter((invite) => clientIds.has(invite.clientId)),
|
||||
accessRequests: [],
|
||||
taskerInviteRequests: [],
|
||||
engineWorkflowAccessRequests: [],
|
||||
grants: data.grants.filter((grant) => {
|
||||
if (grant.targetType === "client") return clientIds.has(grant.targetId);
|
||||
if (grant.targetType === "group") return groupIds.has(grant.targetId);
|
||||
@@ -3398,6 +3552,7 @@ function scopeRuntimeControlPlaneData(data, userId) {
|
||||
accessRequests: [],
|
||||
revokedAccounts: [],
|
||||
taskerInviteRequests: [],
|
||||
engineWorkflowAccessRequests: [],
|
||||
grants: [],
|
||||
exceptions: [],
|
||||
serviceModuleEntitlements: [],
|
||||
@@ -3425,6 +3580,7 @@ function scopeRuntimeControlPlaneData(data, userId) {
|
||||
accessRequests: [],
|
||||
revokedAccounts: [],
|
||||
taskerInviteRequests: [],
|
||||
engineWorkflowAccessRequests: [],
|
||||
grants: data.grants.filter((grant) => {
|
||||
if (grant.targetType === "client") return clientIds.has(grant.targetId);
|
||||
if (grant.targetType === "group") return groupIds.has(grant.targetId);
|
||||
|
||||
Reference in New Issue
Block a user