2022-10-11 05:55:05 -07:00
|
|
|
import express from 'express';
|
2022-11-09 06:25:00 -08:00
|
|
|
import * as Db from '@/Db';
|
|
|
|
import { InternalHooksManager } from '@/InternalHooksManager';
|
|
|
|
import * as ResponseHelper from '@/ResponseHelper';
|
|
|
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
|
|
|
import config from '@/config';
|
|
|
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
|
|
|
import { validateEntity } from '@/GenericHelpers';
|
|
|
|
import type { WorkflowRequest } from '@/requests';
|
|
|
|
import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelper';
|
2022-10-11 05:55:05 -07:00
|
|
|
import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee';
|
2022-10-13 02:55:58 -07:00
|
|
|
import { externalHooks } from '../Server';
|
2022-11-09 06:25:00 -08:00
|
|
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
2022-10-13 02:55:58 -07:00
|
|
|
import { LoggerProxy } from 'n8n-workflow';
|
2022-11-09 06:25:00 -08:00
|
|
|
import * as TagHelpers from '@/TagHelpers';
|
2022-10-13 02:55:58 -07:00
|
|
|
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
|
2022-11-11 02:14:45 -08:00
|
|
|
import { IExecutionPushResponse } from '@/Interfaces';
|
|
|
|
import * as GenericHelpers from '@/GenericHelpers';
|
2022-10-11 05:55:05 -07:00
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
|
|
export const EEWorkflowController = express.Router();
|
|
|
|
|
|
|
|
EEWorkflowController.use((req, res, next) => {
|
|
|
|
if (!isSharingEnabled() || !config.getEnv('enterprise.workflowSharingEnabled')) {
|
|
|
|
// skip ee router and use free one
|
|
|
|
next('router');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// use ee router
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (EE) PUT /workflows/:id/share
|
|
|
|
*
|
|
|
|
* Grant or remove users' access to a workflow.
|
|
|
|
*/
|
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
EEWorkflowController.put(
|
|
|
|
'/:workflowId/share',
|
|
|
|
ResponseHelper.send(async (req: WorkflowRequest.Share) => {
|
|
|
|
const { workflowId } = req.params;
|
|
|
|
const { shareWithIds } = req.body;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!Array.isArray(shareWithIds) ||
|
|
|
|
!shareWithIds.every((userId) => typeof userId === 'string')
|
|
|
|
) {
|
2022-11-22 05:00:36 -08:00
|
|
|
throw new ResponseHelper.BadRequestError('Bad request');
|
2022-11-16 05:13:36 -08:00
|
|
|
}
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
const { ownsWorkflow, workflow } = await EEWorkflows.isOwned(req.user, workflowId);
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
if (!ownsWorkflow || !workflow) {
|
2022-11-22 05:00:36 -08:00
|
|
|
throw new ResponseHelper.UnauthorizedError('Forbidden');
|
2022-11-16 05:13:36 -08:00
|
|
|
}
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
let newShareeIds: string[] = [];
|
|
|
|
await Db.transaction(async (trx) => {
|
|
|
|
// remove all sharings that are not supposed to exist anymore
|
|
|
|
await EEWorkflows.pruneSharings(trx, workflowId, [req.user.id, ...shareWithIds]);
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
const sharings = await EEWorkflows.getSharings(trx, workflowId);
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
// extract the new sharings that need to be added
|
|
|
|
newShareeIds = rightDiff(
|
|
|
|
[sharings, (sharing) => sharing.userId],
|
|
|
|
[shareWithIds, (shareeId) => shareeId],
|
|
|
|
);
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2022-11-16 05:13:36 -08:00
|
|
|
if (newShareeIds.length) {
|
|
|
|
await EEWorkflows.share(trx, workflow, newShareeIds);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
);
|
2022-10-11 07:40:39 -07:00
|
|
|
|
|
|
|
EEWorkflowController.get(
|
2022-10-13 02:55:58 -07:00
|
|
|
'/:id(\\d+)',
|
2022-10-11 07:40:39 -07:00
|
|
|
ResponseHelper.send(async (req: WorkflowRequest.Get) => {
|
|
|
|
const { id: workflowId } = req.params;
|
|
|
|
|
|
|
|
const workflow = await EEWorkflows.get(
|
|
|
|
{ id: parseInt(workflowId, 10) },
|
|
|
|
{ relations: ['shared', 'shared.user', 'shared.role'] },
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!workflow) {
|
2022-11-22 05:00:36 -08:00
|
|
|
throw new ResponseHelper.NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
|
2022-10-11 07:40:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const userSharing = workflow.shared?.find((shared) => shared.user.id === req.user.id);
|
|
|
|
|
|
|
|
if (!userSharing && req.user.globalRole.name !== 'owner') {
|
2022-11-22 05:00:36 -08:00
|
|
|
throw new ResponseHelper.UnauthorizedError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'It looks like you cannot access this workflow. Ask the owner to share it with you.',
|
|
|
|
);
|
2022-10-11 07:40:39 -07:00
|
|
|
}
|
|
|
|
|
2022-10-31 07:08:25 -07:00
|
|
|
return EEWorkflows.addCredentialsToWorkflow(
|
|
|
|
EEWorkflows.addOwnerAndSharings(workflow),
|
|
|
|
req.user,
|
|
|
|
);
|
2022-10-11 07:40:39 -07:00
|
|
|
}),
|
|
|
|
);
|
2022-10-13 02:55:58 -07:00
|
|
|
|
|
|
|
EEWorkflowController.post(
|
|
|
|
'/',
|
|
|
|
ResponseHelper.send(async (req: WorkflowRequest.Create) => {
|
|
|
|
delete req.body.id; // delete if sent
|
|
|
|
|
|
|
|
const newWorkflow = new WorkflowEntity();
|
|
|
|
|
|
|
|
Object.assign(newWorkflow, req.body);
|
|
|
|
|
|
|
|
await validateEntity(newWorkflow);
|
|
|
|
|
|
|
|
await externalHooks.run('workflow.create', [newWorkflow]);
|
|
|
|
|
|
|
|
const { tags: tagIds } = req.body;
|
|
|
|
|
|
|
|
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
|
|
|
newWorkflow.tags = await Db.collections.Tag.findByIds(tagIds, {
|
|
|
|
select: ['id', 'name'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await WorkflowHelpers.replaceInvalidCredentials(newWorkflow);
|
|
|
|
|
|
|
|
WorkflowHelpers.addNodeIds(newWorkflow);
|
|
|
|
|
|
|
|
// This is a new workflow, so we simply check if the user has access to
|
|
|
|
// all used workflows
|
|
|
|
|
|
|
|
const allCredentials = await EECredentials.getAll(req.user);
|
|
|
|
|
|
|
|
try {
|
|
|
|
EEWorkflows.validateCredentialPermissionsToUser(newWorkflow, allCredentials);
|
|
|
|
} catch (error) {
|
2022-11-22 05:00:36 -08:00
|
|
|
throw new ResponseHelper.BadRequestError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'The workflow you are trying to save contains credentials that are not shared with you',
|
2022-10-13 02:55:58 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let savedWorkflow: undefined | WorkflowEntity;
|
|
|
|
|
|
|
|
await Db.transaction(async (transactionManager) => {
|
|
|
|
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
|
|
|
|
|
|
|
const role = await Db.collections.Role.findOneOrFail({
|
|
|
|
name: 'owner',
|
|
|
|
scope: 'workflow',
|
|
|
|
});
|
|
|
|
|
|
|
|
const newSharedWorkflow = new SharedWorkflow();
|
|
|
|
|
|
|
|
Object.assign(newSharedWorkflow, {
|
|
|
|
role,
|
|
|
|
user: req.user,
|
|
|
|
workflow: savedWorkflow,
|
|
|
|
});
|
|
|
|
|
|
|
|
await transactionManager.save<SharedWorkflow>(newSharedWorkflow);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!savedWorkflow) {
|
|
|
|
LoggerProxy.error('Failed to create workflow', { userId: req.user.id });
|
2022-11-22 05:00:36 -08:00
|
|
|
throw new ResponseHelper.InternalServerError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'An error occurred while saving your workflow. Please try again.',
|
|
|
|
);
|
2022-10-13 02:55:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
|
|
|
|
savedWorkflow.tags = TagHelpers.sortByRequestOrder(savedWorkflow.tags, {
|
|
|
|
requestOrder: tagIds,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await externalHooks.run('workflow.afterCreate', [savedWorkflow]);
|
|
|
|
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, newWorkflow, false);
|
|
|
|
|
|
|
|
const { id, ...rest } = savedWorkflow;
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: id.toString(),
|
|
|
|
...rest,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2022-11-08 08:52:42 -08:00
|
|
|
/**
|
|
|
|
* (EE) GET /workflows
|
|
|
|
*/
|
|
|
|
EEWorkflowController.get(
|
|
|
|
'/',
|
|
|
|
ResponseHelper.send(async (req: WorkflowRequest.GetAll) => {
|
2022-11-18 04:07:39 -08:00
|
|
|
const workflows = (await EEWorkflows.getMany(
|
2022-11-08 08:52:42 -08:00
|
|
|
req.user,
|
|
|
|
req.query.filter,
|
|
|
|
)) as unknown as WorkflowEntity[];
|
|
|
|
|
|
|
|
return Promise.all(
|
|
|
|
workflows.map(async (workflow) =>
|
|
|
|
EEWorkflows.addCredentialsToWorkflow(EEWorkflows.addOwnerAndSharings(workflow), req.user),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
EEWorkflowController.patch(
|
|
|
|
'/:id(\\d+)',
|
|
|
|
ResponseHelper.send(async (req: WorkflowRequest.Update) => {
|
|
|
|
const { id: workflowId } = req.params;
|
2022-10-31 02:35:24 -07:00
|
|
|
const forceSave = req.query.forceSave === 'true';
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
const updateData = new WorkflowEntity();
|
|
|
|
const { tags, ...rest } = req.body;
|
|
|
|
Object.assign(updateData, rest);
|
|
|
|
|
2022-11-11 02:14:45 -08:00
|
|
|
const safeWorkflow = await EEWorkflows.preventTampering(updateData, workflowId, req.user);
|
|
|
|
|
2022-11-18 04:07:39 -08:00
|
|
|
const updatedWorkflow = await EEWorkflows.update(
|
2022-10-26 06:49:43 -07:00
|
|
|
req.user,
|
2022-11-11 02:14:45 -08:00
|
|
|
safeWorkflow,
|
2022-10-26 06:49:43 -07:00
|
|
|
workflowId,
|
|
|
|
tags,
|
2022-10-31 02:35:24 -07:00
|
|
|
forceSave,
|
2022-10-26 06:49:43 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
const { id, ...remainder } = updatedWorkflow;
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: id.toString(),
|
|
|
|
...remainder,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
);
|
2022-11-11 02:14:45 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* (EE) POST /workflows/run
|
|
|
|
*/
|
|
|
|
EEWorkflowController.post(
|
|
|
|
'/run',
|
|
|
|
ResponseHelper.send(async (req: WorkflowRequest.ManualRun): Promise<IExecutionPushResponse> => {
|
|
|
|
const workflow = new WorkflowEntity();
|
|
|
|
Object.assign(workflow, req.body.workflowData);
|
|
|
|
|
2022-11-21 23:37:52 -08:00
|
|
|
if (workflow.id !== undefined) {
|
|
|
|
const safeWorkflow = await EEWorkflows.preventTampering(
|
|
|
|
workflow,
|
|
|
|
workflow.id.toString(),
|
|
|
|
req.user,
|
|
|
|
);
|
|
|
|
req.body.workflowData.nodes = safeWorkflow.nodes;
|
|
|
|
}
|
2022-11-11 02:14:45 -08:00
|
|
|
|
2022-11-18 04:07:39 -08:00
|
|
|
return EEWorkflows.runManually(req.body, req.user, GenericHelpers.getSessionId(req));
|
2022-11-11 02:14:45 -08:00
|
|
|
}),
|
|
|
|
);
|