n8n
Version:
n8n Workflow Automation Tool
967 lines • 84.8 kB
JavaScript
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = void 0;
const express = require("express");
const fs_1 = require("fs");
const path_1 = require("path");
const typeorm_1 = require("typeorm");
const bodyParser = require("body-parser");
const history = require("connect-history-api-fallback");
const os = require("os");
const _ = require("lodash");
const clientOAuth2 = require("client-oauth2");
const clientOAuth1 = require("oauth-1.0a");
const csrf = require("csrf");
const requestPromise = require("request-promise-native");
const crypto_1 = require("crypto");
const bcryptjs_1 = require("bcryptjs");
const promClient = require("prom-client");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const n8n_nodes_base_1 = require("n8n-nodes-base");
const basicAuth = require("basic-auth");
const compression = require("compression");
const jwt = require("jsonwebtoken");
const jwks = require("jwks-rsa");
const timezones = require("google-timezones-json");
const parseUrl = require("parseurl");
const querystring = require("querystring");
const Queue = require("./Queue");
const _1 = require(".");
const config = require("../config");
const TagHelpers = require("./TagHelpers");
const PersonalizationSurvey = require("./PersonalizationSurvey");
const InternalHooksManager_1 = require("./InternalHooksManager");
const TagEntity_1 = require("./databases/entities/TagEntity");
const WorkflowEntity_1 = require("./databases/entities/WorkflowEntity");
require('body-parser-xml')(bodyParser);
class App {
constructor() {
this.app = express();
this.endpointWebhook = config.get('endpoints.webhook');
this.endpointWebhookWaiting = config.get('endpoints.webhookWaiting');
this.endpointWebhookTest = config.get('endpoints.webhookTest');
this.defaultWorkflowName = config.get('workflows.defaultName');
this.defaultCredentialsName = config.get('credentials.defaultName');
this.saveDataErrorExecution = config.get('executions.saveDataOnError');
this.saveDataSuccessExecution = config.get('executions.saveDataOnSuccess');
this.saveManualExecutions = config.get('executions.saveDataManualExecutions');
this.executionTimeout = config.get('executions.timeout');
this.maxExecutionTimeout = config.get('executions.maxTimeout');
this.payloadSizeMax = config.get('endpoints.payloadSizeMax');
this.timezone = config.get('generic.timezone');
this.restEndpoint = config.get('endpoints.rest');
this.activeWorkflowRunner = _1.ActiveWorkflowRunner.getInstance();
this.testWebhooks = _1.TestWebhooks.getInstance();
this.push = _1.Push.getInstance();
this.activeExecutionsInstance = _1.ActiveExecutions.getInstance();
this.waitTracker = _1.WaitTracker();
this.protocol = config.get('protocol');
this.sslKey = config.get('ssl_key');
this.sslCert = config.get('ssl_cert');
this.externalHooks = _1.ExternalHooks();
this.presetCredentialsLoaded = false;
this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint');
const urlBaseWebhook = _1.WebhookHelpers.getWebhookBaseUrl();
const telemetrySettings = {
enabled: config.get('diagnostics.enabled'),
};
if (telemetrySettings.enabled) {
const conf = config.get('diagnostics.config.frontend');
const [key, url] = conf.split(';');
if (!key || !url) {
n8n_workflow_1.LoggerProxy.warn('Diagnostics frontend config is invalid');
telemetrySettings.enabled = false;
}
telemetrySettings.config = { key, url };
}
this.frontendSettings = {
endpointWebhook: this.endpointWebhook,
endpointWebhookTest: this.endpointWebhookTest,
saveDataErrorExecution: this.saveDataErrorExecution,
saveDataSuccessExecution: this.saveDataSuccessExecution,
saveManualExecutions: this.saveManualExecutions,
executionTimeout: this.executionTimeout,
maxExecutionTimeout: this.maxExecutionTimeout,
timezone: this.timezone,
urlBaseWebhook,
versionCli: '',
oauthCallbackUrls: {
oauth1: `${urlBaseWebhook}${this.restEndpoint}/oauth1-credential/callback`,
oauth2: `${urlBaseWebhook}${this.restEndpoint}/oauth2-credential/callback`,
},
versionNotifications: {
enabled: config.get('versionNotifications.enabled'),
endpoint: config.get('versionNotifications.endpoint'),
infoUrl: config.get('versionNotifications.infoUrl'),
},
instanceId: '',
telemetry: telemetrySettings,
personalizationSurvey: {
shouldShow: false,
},
};
}
getCurrentDate() {
return new Date();
}
async config() {
const enableMetrics = config.get('endpoints.metrics.enable');
let register;
if (enableMetrics) {
const prefix = config.get('endpoints.metrics.prefix');
register = new promClient.Registry();
register.setDefaultLabels({ prefix });
promClient.collectDefaultMetrics({ register });
}
this.versions = await _1.GenericHelpers.getVersions();
this.frontendSettings.versionCli = this.versions.cli;
this.frontendSettings.instanceId = await n8n_core_1.UserSettings.getInstanceId();
this.frontendSettings.personalizationSurvey =
await PersonalizationSurvey.preparePersonalizationSurvey();
await this.externalHooks.run('frontend.settings', [this.frontendSettings]);
const excludeEndpoints = config.get('security.excludeEndpoints');
const ignoredEndpoints = [
'healthz',
'metrics',
this.endpointWebhook,
this.endpointWebhookTest,
this.endpointPresetCredentials,
];
ignoredEndpoints.push.apply(ignoredEndpoints, excludeEndpoints.split(':'));
const authIgnoreRegex = new RegExp(`^\/(${_(ignoredEndpoints).compact().join('|')})\/?.*$`);
const basicAuthActive = config.get('security.basicAuth.active');
if (basicAuthActive) {
const basicAuthUser = (await _1.GenericHelpers.getConfigValue('security.basicAuth.user'));
if (basicAuthUser === '') {
throw new Error('Basic auth is activated but no user got defined. Please set one!');
}
const basicAuthPassword = (await _1.GenericHelpers.getConfigValue('security.basicAuth.password'));
if (basicAuthPassword === '') {
throw new Error('Basic auth is activated but no password got defined. Please set one!');
}
const basicAuthHashEnabled = (await _1.GenericHelpers.getConfigValue('security.basicAuth.hash'));
let validPassword = null;
this.app.use(async (req, res, next) => {
if (authIgnoreRegex.exec(req.url)) {
return next();
}
const realm = 'n8n - Editor UI';
const basicAuthData = basicAuth(req);
if (basicAuthData === undefined) {
return _1.ResponseHelper.basicAuthAuthorizationError(res, realm, 'Authorization is required!');
}
if (basicAuthData.name === basicAuthUser) {
if (basicAuthHashEnabled) {
if (validPassword === null &&
(await bcryptjs_1.compare(basicAuthData.pass, basicAuthPassword))) {
validPassword = basicAuthData.pass;
}
if (validPassword === basicAuthData.pass && validPassword !== null) {
return next();
}
}
else if (basicAuthData.pass === basicAuthPassword) {
return next();
}
}
return _1.ResponseHelper.basicAuthAuthorizationError(res, realm, 'Authorization data is wrong!');
});
}
const jwtAuthActive = config.get('security.jwtAuth.active');
if (jwtAuthActive) {
const jwtAuthHeader = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwtHeader'));
if (jwtAuthHeader === '') {
throw new Error('JWT auth is activated but no request header was defined. Please set one!');
}
const jwksUri = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwksUri'));
if (jwksUri === '') {
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
}
const jwtHeaderValuePrefix = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwtHeaderValuePrefix'));
const jwtIssuer = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwtIssuer'));
const jwtNamespace = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwtNamespace'));
const jwtAllowedTenantKey = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwtAllowedTenantKey'));
const jwtAllowedTenant = (await _1.GenericHelpers.getConfigValue('security.jwtAuth.jwtAllowedTenant'));
function isTenantAllowed(decodedToken) {
if (jwtNamespace === '' || jwtAllowedTenantKey === '' || jwtAllowedTenant === '')
return true;
for (const [k, v] of Object.entries(decodedToken)) {
if (k === jwtNamespace) {
for (const [kn, kv] of Object.entries(v)) {
if (kn === jwtAllowedTenantKey && kv === jwtAllowedTenant) {
return true;
}
}
}
}
return false;
}
this.app.use((req, res, next) => {
if (authIgnoreRegex.exec(req.url)) {
return next();
}
let token = req.header(jwtAuthHeader);
if (token === undefined || token === '') {
return _1.ResponseHelper.jwtAuthAuthorizationError(res, 'Missing token');
}
if (jwtHeaderValuePrefix !== '' && token.startsWith(jwtHeaderValuePrefix)) {
token = token.replace(`${jwtHeaderValuePrefix} `, '').trimLeft();
}
const jwkClient = jwks({ cache: true, jwksUri });
function getKey(header, callback) {
jwkClient.getSigningKey(header.kid, (err, key) => {
if (err)
throw _1.ResponseHelper.jwtAuthAuthorizationError(res, err.message);
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
const jwtVerifyOptions = {
issuer: jwtIssuer !== '' ? jwtIssuer : undefined,
ignoreExpiration: false,
};
jwt.verify(token, getKey, jwtVerifyOptions, (err, decoded) => {
if (err) {
_1.ResponseHelper.jwtAuthAuthorizationError(res, 'Invalid token');
}
else if (!isTenantAllowed(decoded)) {
_1.ResponseHelper.jwtAuthAuthorizationError(res, 'Tenant not allowed');
}
else {
next();
}
});
});
}
this.app.use((req, res, next) => {
if (req.url.indexOf(`/${this.restEndpoint}/push`) === 0) {
if (req.query.sessionId === undefined) {
next(new Error('The query parameter "sessionId" is missing!'));
return;
}
this.push.add(req.query.sessionId, req, res);
return;
}
next();
});
this.app.use(compression());
this.app.use((req, res, next) => {
req.parsedUrl = parseUrl(req);
req.rawBody = Buffer.from('', 'base64');
next();
});
this.app.use(bodyParser.json({
limit: `${this.payloadSizeMax}mb`,
verify: (req, res, buf) => {
req.rawBody = buf;
},
}));
this.app.use(bodyParser.xml({
limit: `${this.payloadSizeMax}mb`,
xmlParseOptions: {
normalize: true,
normalizeTags: true,
explicitArray: false,
},
}));
this.app.use(bodyParser.text({
limit: `${this.payloadSizeMax}mb`,
verify: (req, res, buf) => {
req.rawBody = buf;
},
}));
this.app.use(history({
rewrites: [
{
from: new RegExp(`^\/(${this.restEndpoint}|healthz|metrics|css|js|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`),
to: (context) => {
return context.parsedUrl.pathname.toString();
},
},
],
}));
this.app.use(bodyParser.urlencoded({
limit: `${this.payloadSizeMax}mb`,
extended: false,
verify: (req, res, buf) => {
req.rawBody = buf;
},
}));
if (process.env.NODE_ENV !== 'production') {
this.app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, sessionid');
next();
});
}
this.app.use((req, res, next) => {
if (_1.Db.collections.Workflow === null) {
const error = new _1.ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
return _1.ResponseHelper.sendErrorResponse(res, error);
}
next();
});
this.app.get('/healthz', async (req, res) => {
const connection = typeorm_1.getConnectionManager().get();
try {
if (!connection.isConnected) {
throw new Error('No active database connection!');
}
await connection.query('SELECT 1');
}
catch (err) {
n8n_workflow_1.LoggerProxy.error('No Database connection!', err);
const error = new _1.ResponseHelper.ResponseError('No Database connection!', undefined, 503);
return _1.ResponseHelper.sendErrorResponse(res, error);
}
const responseData = {
status: 'ok',
};
_1.ResponseHelper.sendSuccessResponse(res, responseData, true, 200);
});
if (enableMetrics) {
this.app.get('/metrics', async (req, res) => {
const response = await register.metrics();
res.setHeader('Content-Type', register.contentType);
_1.ResponseHelper.sendSuccessResponse(res, response, true, 200);
});
}
this.app.post(`/${this.restEndpoint}/workflows`, _1.ResponseHelper.send(async (req, res) => {
delete req.body.id;
const incomingData = req.body;
const newWorkflow = new WorkflowEntity_1.WorkflowEntity();
Object.assign(newWorkflow, incomingData);
newWorkflow.name = incomingData.name.trim();
const incomingTagOrder = incomingData.tags.slice();
if (incomingData.tags.length) {
newWorkflow.tags = await _1.Db.collections.Tag.findByIds(incomingData.tags, {
select: ['id', 'name'],
});
}
await _1.WorkflowHelpers.replaceInvalidCredentials(newWorkflow);
await this.externalHooks.run('workflow.create', [newWorkflow]);
await _1.WorkflowHelpers.validateWorkflow(newWorkflow);
const savedWorkflow = (await _1.Db.collections
.Workflow.save(newWorkflow)
.catch(_1.WorkflowHelpers.throwDuplicateEntryError));
savedWorkflow.tags = TagHelpers.sortByRequestOrder(savedWorkflow.tags, incomingTagOrder);
savedWorkflow.id = savedWorkflow.id.toString();
await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]);
void InternalHooksManager_1.InternalHooksManager.getInstance().onWorkflowCreated(newWorkflow);
return savedWorkflow;
}));
this.app.get(`/${this.restEndpoint}/workflows/from-url`, _1.ResponseHelper.send(async (req, res) => {
if (req.query.url === undefined) {
throw new _1.ResponseHelper.ResponseError(`The parameter "url" is missing!`, undefined, 400);
}
if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url)) {
throw new _1.ResponseHelper.ResponseError(`The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.`, undefined, 400);
}
const data = await requestPromise.get(req.query.url);
let workflowData;
try {
workflowData = JSON.parse(data);
}
catch (error) {
throw new _1.ResponseHelper.ResponseError(`The URL does not point to valid JSON file!`, undefined, 400);
}
if (workflowData === undefined ||
workflowData.nodes === undefined ||
!Array.isArray(workflowData.nodes) ||
workflowData.connections === undefined ||
typeof workflowData.connections !== 'object' ||
Array.isArray(workflowData.connections)) {
throw new _1.ResponseHelper.ResponseError(`The data in the file does not seem to be a n8n workflow JSON file!`, undefined, 400);
}
return workflowData;
}));
this.app.get(`/${this.restEndpoint}/workflows`, _1.ResponseHelper.send(async (req, res) => {
const findQuery = {
select: ['id', 'name', 'active', 'createdAt', 'updatedAt'],
relations: ['tags'],
};
if (req.query.filter) {
findQuery.where = JSON.parse(req.query.filter);
}
const workflows = await _1.Db.collections.Workflow.find(findQuery);
workflows.forEach((workflow) => {
workflow.id = workflow.id.toString();
workflow.tags = workflow.tags.map(({ id, name }) => ({ id: id.toString(), name }));
});
return workflows;
}));
this.app.get(`/${this.restEndpoint}/workflows/new`, _1.ResponseHelper.send(async (req, res) => {
const requestedName = req.query.name && req.query.name !== '' ? req.query.name : this.defaultWorkflowName;
return await _1.GenericHelpers.generateUniqueName(requestedName, 'workflow');
}));
this.app.get(`/${this.restEndpoint}/workflows/:id`, _1.ResponseHelper.send(async (req, res) => {
const workflow = await _1.Db.collections.Workflow.findOne(req.params.id, {
relations: ['tags'],
});
if (workflow === undefined) {
return undefined;
}
workflow.id = workflow.id.toString();
workflow.tags.forEach((tag) => (tag.id = tag.id.toString()));
return workflow;
}));
this.app.patch(`/${this.restEndpoint}/workflows/:id`, _1.ResponseHelper.send(async (req, res) => {
const _a = req.body, { tags } = _a, updateData = __rest(_a, ["tags"]);
const { id } = req.params;
updateData.id = id;
await _1.WorkflowHelpers.replaceInvalidCredentials(updateData);
await this.externalHooks.run('workflow.update', [updateData]);
const isActive = await this.activeWorkflowRunner.isActive(id);
if (isActive) {
await this.activeWorkflowRunner.remove(id);
}
if (updateData.settings) {
if (updateData.settings.timezone === 'DEFAULT') {
delete updateData.settings.timezone;
}
if (updateData.settings.saveDataErrorExecution === 'DEFAULT') {
delete updateData.settings.saveDataErrorExecution;
}
if (updateData.settings.saveDataSuccessExecution === 'DEFAULT') {
delete updateData.settings.saveDataSuccessExecution;
}
if (updateData.settings.saveManualExecutions === 'DEFAULT') {
delete updateData.settings.saveManualExecutions;
}
if (parseInt(updateData.settings.executionTimeout, 10) === this.executionTimeout) {
delete updateData.settings.executionTimeout;
}
}
updateData.updatedAt = this.getCurrentDate();
await _1.WorkflowHelpers.validateWorkflow(updateData);
await _1.Db.collections
.Workflow.update(id, updateData)
.catch(_1.WorkflowHelpers.throwDuplicateEntryError);
if (tags) {
const tablePrefix = config.get('database.tablePrefix');
await TagHelpers.removeRelations(req.params.id, tablePrefix);
if (tags.length) {
await TagHelpers.createRelations(req.params.id, tags, tablePrefix);
}
}
const workflow = await _1.Db.collections.Workflow.findOne(id, { relations: ['tags'] });
if (workflow === undefined) {
throw new _1.ResponseHelper.ResponseError(`Workflow with id "${id}" could not be found to be updated.`, undefined, 400);
}
if (tags === null || tags === void 0 ? void 0 : tags.length) {
workflow.tags = TagHelpers.sortByRequestOrder(workflow.tags, tags);
}
await this.externalHooks.run('workflow.afterUpdate', [workflow]);
void InternalHooksManager_1.InternalHooksManager.getInstance().onWorkflowSaved(workflow);
if (workflow.active) {
try {
await this.externalHooks.run('workflow.activate', [workflow]);
await this.activeWorkflowRunner.add(id, isActive ? 'update' : 'activate');
}
catch (error) {
updateData.active = false;
await _1.Db.collections.Workflow.update(id, updateData);
workflow.active = false;
throw error;
}
}
workflow.id = workflow.id.toString();
return workflow;
}));
this.app.delete(`/${this.restEndpoint}/workflows/:id`, _1.ResponseHelper.send(async (req, res) => {
const { id } = req.params;
await this.externalHooks.run('workflow.delete', [id]);
const isActive = await this.activeWorkflowRunner.isActive(id);
if (isActive) {
await this.activeWorkflowRunner.remove(id);
}
await _1.Db.collections.Workflow.delete(id);
void InternalHooksManager_1.InternalHooksManager.getInstance().onWorkflowDeleted(id);
await this.externalHooks.run('workflow.afterDelete', [id]);
return true;
}));
this.app.post(`/${this.restEndpoint}/workflows/run`, _1.ResponseHelper.send(async (req, res) => {
const { workflowData } = req.body;
const { runData } = req.body;
const { startNodes } = req.body;
const { destinationNode } = req.body;
const executionMode = 'manual';
const activationMode = 'manual';
const sessionId = _1.GenericHelpers.getSessionId(req);
if (runData === undefined ||
startNodes === undefined ||
startNodes.length === 0 ||
destinationNode === undefined) {
const additionalData = await _1.WorkflowExecuteAdditionalData.getBase();
const nodeTypes = _1.NodeTypes();
const workflowInstance = new n8n_workflow_1.Workflow({
id: workflowData.id,
name: workflowData.name,
nodes: workflowData.nodes,
connections: workflowData.connections,
active: false,
nodeTypes,
staticData: undefined,
settings: workflowData.settings,
});
const needsWebhook = await this.testWebhooks.needsWebhookData(workflowData, workflowInstance, additionalData, executionMode, activationMode, sessionId, destinationNode);
if (needsWebhook) {
return {
waitingForWebhook: true,
};
}
}
workflowData.active = false;
const data = {
destinationNode,
executionMode,
runData,
sessionId,
startNodes,
workflowData,
};
const workflowRunner = new _1.WorkflowRunner();
const executionId = await workflowRunner.run(data);
return {
executionId,
};
}));
this.app.get(`/${this.restEndpoint}/tags`, _1.ResponseHelper.send(async (req, res) => {
if (req.query.withUsageCount === 'true') {
const tablePrefix = config.get('database.tablePrefix');
return TagHelpers.getTagsWithCountDb(tablePrefix);
}
const tags = await _1.Db.collections.Tag.find({ select: ['id', 'name'] });
tags.forEach((tag) => (tag.id = tag.id.toString()));
return tags;
}));
this.app.post(`/${this.restEndpoint}/tags`, _1.ResponseHelper.send(async (req, res) => {
const newTag = new TagEntity_1.TagEntity();
newTag.name = req.body.name.trim();
await this.externalHooks.run('tag.beforeCreate', [newTag]);
await TagHelpers.validateTag(newTag);
const tag = await _1.Db.collections
.Tag.save(newTag)
.catch(TagHelpers.throwDuplicateEntryError);
await this.externalHooks.run('tag.afterCreate', [tag]);
tag.id = tag.id.toString();
return tag;
}));
this.app.patch(`/${this.restEndpoint}/tags/:id`, _1.ResponseHelper.send(async (req, res) => {
const { name } = req.body;
const { id } = req.params;
const newTag = new TagEntity_1.TagEntity();
newTag.id = Number(id);
newTag.name = name.trim();
await this.externalHooks.run('tag.beforeUpdate', [newTag]);
await TagHelpers.validateTag(newTag);
const tag = await _1.Db.collections
.Tag.save(newTag)
.catch(TagHelpers.throwDuplicateEntryError);
await this.externalHooks.run('tag.afterUpdate', [tag]);
tag.id = tag.id.toString();
return tag;
}));
this.app.delete(`/${this.restEndpoint}/tags/:id`, _1.ResponseHelper.send(async (req, res) => {
const id = Number(req.params.id);
await this.externalHooks.run('tag.beforeDelete', [id]);
await _1.Db.collections.Tag.delete({ id });
await this.externalHooks.run('tag.afterDelete', [id]);
return true;
}));
this.app.get(`/${this.restEndpoint}/node-parameter-options`, _1.ResponseHelper.send(async (req, res) => {
const nodeTypeAndVersion = JSON.parse(`${req.query.nodeTypeAndVersion}`);
const path = req.query.path;
let credentials;
const currentNodeParameters = JSON.parse(`${req.query.currentNodeParameters}`);
if (req.query.credentials !== undefined) {
credentials = JSON.parse(req.query.credentials);
}
const methodName = req.query.methodName;
const nodeTypes = _1.NodeTypes();
const loadDataInstance = new n8n_core_1.LoadNodeParameterOptions(nodeTypeAndVersion, nodeTypes, path, currentNodeParameters, credentials);
const additionalData = await _1.WorkflowExecuteAdditionalData.getBase(currentNodeParameters);
return loadDataInstance.getOptions(methodName, additionalData);
}));
this.app.get(`/${this.restEndpoint}/node-types`, _1.ResponseHelper.send(async (req, res) => {
const returnData = [];
const onlyLatest = req.query.onlyLatest === 'true';
const nodeTypes = _1.NodeTypes();
const allNodes = nodeTypes.getAll();
const getNodeDescription = (nodeType) => {
const nodeInfo = Object.assign({}, nodeType.description);
if (req.query.includeProperties !== 'true') {
delete nodeInfo.properties;
}
return nodeInfo;
};
if (onlyLatest) {
allNodes.forEach((nodeData) => {
const nodeType = n8n_workflow_1.NodeHelpers.getVersionedTypeNode(nodeData);
const nodeInfo = getNodeDescription(nodeType);
returnData.push(nodeInfo);
});
}
else {
allNodes.forEach((nodeData) => {
const allNodeTypes = n8n_workflow_1.NodeHelpers.getVersionedTypeNodeAll(nodeData);
allNodeTypes.forEach((element) => {
const nodeInfo = getNodeDescription(element);
returnData.push(nodeInfo);
});
});
}
return returnData;
}));
this.app.post(`/${this.restEndpoint}/node-types`, _1.ResponseHelper.send(async (req, res) => {
const nodeInfos = _.get(req, 'body.nodeInfos', []);
const nodeTypes = _1.NodeTypes();
const returnData = [];
nodeInfos.forEach((nodeInfo) => {
const nodeType = nodeTypes.getByNameAndVersion(nodeInfo.name, nodeInfo.version);
if (nodeType === null || nodeType === void 0 ? void 0 : nodeType.description) {
returnData.push(nodeType.description);
}
});
return returnData;
}));
this.app.get([
`/${this.restEndpoint}/node-icon/:nodeType`,
`/${this.restEndpoint}/node-icon/:scope/:nodeType`,
], async (req, res) => {
try {
const nodeTypeName = `${req.params.scope ? `${req.params.scope}/` : ''}${req.params.nodeType}`;
const nodeTypes = _1.NodeTypes();
const nodeType = nodeTypes.getByNameAndVersion(nodeTypeName);
if (nodeType === undefined) {
res.status(404).send('The nodeType is not known.');
return;
}
if (nodeType.description.icon === undefined) {
res.status(404).send('No icon found for node.');
return;
}
if (!nodeType.description.icon.startsWith('file:')) {
res.status(404).send('Node does not have a file icon.');
return;
}
const filepath = nodeType.description.icon.substr(5);
const maxAge = 7 * 24 * 60 * 60 * 1000;
res.setHeader('Cache-control', `private max-age=${maxAge}`);
res.sendFile(filepath);
}
catch (error) {
return _1.ResponseHelper.sendErrorResponse(res, error);
}
});
this.app.get(`/${this.restEndpoint}/active`, _1.ResponseHelper.send(async (req, res) => {
const activeWorkflows = await this.activeWorkflowRunner.getActiveWorkflows();
return activeWorkflows.map((workflow) => workflow.id.toString());
}));
this.app.get(`/${this.restEndpoint}/active/error/:id`, _1.ResponseHelper.send(async (req, res) => {
const { id } = req.params;
return this.activeWorkflowRunner.getActivationError(id);
}));
this.app.get(`/${this.restEndpoint}/credentials/new`, _1.ResponseHelper.send(async (req, res) => {
const requestedName = req.query.name && req.query.name !== '' ? req.query.name : this.defaultCredentialsName;
return await _1.GenericHelpers.generateUniqueName(requestedName, 'credentials');
}));
this.app.delete(`/${this.restEndpoint}/credentials/:id`, _1.ResponseHelper.send(async (req, res) => {
const { id } = req.params;
await this.externalHooks.run('credentials.delete', [id]);
await _1.Db.collections.Credentials.delete({ id });
return true;
}));
this.app.post(`/${this.restEndpoint}/credentials`, _1.ResponseHelper.send(async (req, res) => {
const incomingData = req.body;
if (!incomingData.name || incomingData.name.length < 3) {
throw new _1.ResponseHelper.ResponseError(`Credentials name must be at least 3 characters long.`, undefined, 400);
}
for (const nodeAccess of incomingData.nodesAccess) {
nodeAccess.date = this.getCurrentDate();
}
const encryptionKey = await n8n_core_1.UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
throw new Error('No encryption key got found to encrypt the credentials!');
}
if (incomingData.name === '') {
throw new Error('Credentials have to have a name set!');
}
const credentials = new n8n_core_1.Credentials({ id: null, name: incomingData.name }, incomingData.type, incomingData.nodesAccess);
credentials.setData(incomingData.data, encryptionKey);
const newCredentialsData = credentials.getDataToSave();
await this.externalHooks.run('credentials.create', [newCredentialsData]);
const result = await _1.Db.collections.Credentials.save(newCredentialsData);
result.data = incomingData.data;
result.id = result.id.toString();
return result;
}));
this.app.post(`/${this.restEndpoint}/credentials-test`, _1.ResponseHelper.send(async (req, res) => {
const incomingData = req.body;
const credentialType = incomingData.credentials.type;
const nodeTypes = _1.NodeTypes();
const allNodes = nodeTypes.getAll();
let foundTestFunction;
const nodeThatCanTestThisCredential = allNodes.find((node) => {
var _a, _b;
if (incomingData.nodeToTestWith &&
node.description.name !== incomingData.nodeToTestWith) {
return false;
}
if (node instanceof n8n_nodes_base_1.NodeVersionedType) {
const versionNames = Object.keys(node.nodeVersions);
for (const versionName of versionNames) {
const nodeType = node.nodeVersions[versionName];
const credentialTestable = (_a = nodeType.description.credentials) === null || _a === void 0 ? void 0 : _a.find((credential) => {
const testFunctionSearch = credential.name === credentialType && !!credential.testedBy;
if (testFunctionSearch) {
foundTestFunction = node.methods.credentialTest[credential.testedBy];
}
return testFunctionSearch;
});
if (credentialTestable) {
return true;
}
}
return false;
}
const credentialTestable = (_b = node.description.credentials) === null || _b === void 0 ? void 0 : _b.find((credential) => {
const testFunctionSearch = credential.name === credentialType && !!credential.testedBy;
if (testFunctionSearch) {
foundTestFunction = node.methods.credentialTest[credential.testedBy];
}
return testFunctionSearch;
});
return !!credentialTestable;
});
if (!nodeThatCanTestThisCredential) {
return Promise.resolve({
status: 'Error',
message: 'There are no nodes that can test this credential.',
});
}
if (foundTestFunction === undefined) {
return Promise.resolve({
status: 'Error',
message: 'No testing function found for this credential.',
});
}
const credentialTestFunctions = n8n_core_1.NodeExecuteFunctions.getCredentialTestFunctions();
const output = await foundTestFunction.call(credentialTestFunctions, incomingData.credentials);
return Promise.resolve(output);
}));
this.app.patch(`/${this.restEndpoint}/credentials/:id`, _1.ResponseHelper.send(async (req, res) => {
const incomingData = req.body;
const { id } = req.params;
if (incomingData.name === '') {
throw new Error('Credentials have to have a name set!');
}
for (const nodeAccess of incomingData.nodesAccess) {
if (!nodeAccess.date) {
nodeAccess.date = this.getCurrentDate();
}
}
const encryptionKey = await n8n_core_1.UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
throw new Error('No encryption key got found to encrypt the credentials!');
}
const result = await _1.Db.collections.Credentials.findOne(id);
if (result === undefined) {
throw new _1.ResponseHelper.ResponseError(`Credentials with the id "${id}" do not exist.`, undefined, 400);
}
const currentlySavedCredentials = new n8n_core_1.Credentials(result, result.type, result.nodesAccess, result.data);
const decryptedData = currentlySavedCredentials.getData(encryptionKey);
if (decryptedData.oauthTokenData) {
incomingData.data.oauthTokenData = decryptedData.oauthTokenData;
}
const credentials = new n8n_core_1.Credentials({ id, name: incomingData.name }, incomingData.type, incomingData.nodesAccess);
credentials.setData(incomingData.data, encryptionKey);
const newCredentialsData = credentials.getDataToSave();
newCredentialsData.updatedAt = this.getCurrentDate();
await this.externalHooks.run('credentials.update', [newCredentialsData]);
await _1.Db.collections.Credentials.update(id, newCredentialsData);
const responseData = await _1.Db.collections.Credentials.findOne(id);
if (responseData === undefined) {
throw new _1.ResponseHelper.ResponseError(`Credentials with id "${id}" could not be found to be updated.`, undefined, 400);
}
responseData.data = '';
responseData.id = responseData.id.toString();
return responseData;
}));
this.app.get(`/${this.restEndpoint}/credentials/:id`, _1.ResponseHelper.send(async (req, res) => {
const findQuery = {};
const includeData = ['true', true].includes(req.query.includeData);
if (!includeData) {
findQuery.select = ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'];
}
const result = await _1.Db.collections.Credentials.findOne(req.params.id);
if (result === undefined) {
return result;
}
let encryptionKey;
if (includeData) {
encryptionKey = await n8n_core_1.UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
throw new Error('No encryption key got found to decrypt the credentials!');
}
const credentials = new n8n_core_1.Credentials(result, result.type, result.nodesAccess, result.data);
result.data = credentials.getData(encryptionKey);
}
result.id = result.id.toString();
return result;
}));
this.app.get(`/${this.restEndpoint}/credentials`, _1.ResponseHelper.send(async (req, res) => {
const findQuery = {};
if (req.query.filter) {
findQuery.where = JSON.parse(req.query.filter);
if (findQuery.where.id !== undefined) {
findQuery.where = { id: findQuery.where.id };
}
}
findQuery.select = ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'];
const results = (await _1.Db.collections.Credentials.find(findQuery));
let encryptionKey;
const includeData = ['true', true].includes(req.query.includeData);
if (includeData) {
encryptionKey = await n8n_core_1.UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
throw new Error('No encryption key got found to decrypt the credentials!');
}
}
let result;
for (result of results) {
result.id = result.id.toString();
}
return results;
}));
this.app.get(`/${this.restEndpoint}/credential-types`, _1.ResponseHelper.send(async (req, res) => {
const returnData = [];
const credentialTypes = _1.CredentialTypes();
credentialTypes.getAll().forEach((credentialData) => {
returnData.push(credentialData);
});
return returnData;
}));
this.app.get(`/${this.restEndpoint}/credential-icon/:credentialType`, async (req, res) => {
try {
const credentialName = req.params.credentialType;
const credentialType = _1.CredentialTypes().getByName(credentialName);
if (credentialType === undefined) {
res.status(404).send('The credentialType is not known.');
return;
}
if (credentialType.icon === undefined) {
res.status(404).send('No icon found for credential.');
return;
}
if (!credentialType.icon.startsWith('file:')) {
res.status(404).send('Credential does not have a file icon.');
return;
}
const filepath = credentialType.icon.substr(5);
const maxAge = 7 * 24 * 60 * 60 * 1000;
res.setHeader('Cache-control', `private max-age=${maxAge}`);
res.sendFile(filepath);
}
catch (error) {
return _1.ResponseHelper.sendErrorResponse(res, error);
}
});
this.app.get(`/${this.restEndpoint}/oauth1-credential/auth`, _1.ResponseHelper.send(async (req, res) => {
if (req.query.id === undefined) {
res.status(500).send('Required credential id is missing!');
return '';
}
const result = await _1.Db.collections.Credentials.findOne(req.query.id);
if (result === undefined) {
res.status(404).send('The credential is not known.');
return '';
}
let encryptionKey;
encryptionKey = await n8n_core_1.UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
res.status(500).send('No encryption key got found to decrypt the credentials!');
return '';
}
const mode = 'internal';
const credentialsHelper = new _1.CredentialsHelper(encryptionKey);
const decryptedDataOriginal = await credentialsHelper.getDecrypted(result, result.type, mode, true);
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type, mode);
const signatureMethod = _.get(oauthCredentials, 'signatureMethod');
const oAuthOptions = {
consumer: {
key: _.get(oauthCredentials, 'consumerKey'),
secret: _.get(oauthCredentials, 'consumerSecret'),
},
signature_method: signatureMethod,
hash_function(base, key) {
const algorithm = signatureMethod === 'HMAC-SHA1' ? 'sha1' : 'sha256';
return crypto_1.createHmac(algorithm, key).update(base).digest('base64');
},
};
const oauthRequestData = {
oauth_callback: `${_1.WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth1-credential/callback?cid=${req.query.id}`,
};
await this.externalHooks.run('oauth1.authenticate', [oAuthOptions, oauthRequestData]);
const oauth = new clientOAuth1(oAuthOptions);
const options = {
method: 'POST',
url: _.get(oauthCredentials, 'requestTokenUrl'),
data: oauthRequestData,
};
const data = oauth.toHeader(oauth.authorize(options));
options.headers = data;
const response = await requestPromise(options);
const responseJson = querystring.parse(response);
const returnUri = `${_.get(oauthCredentials, 'authUrl')}?oauth_token=${responseJson.oauth_token}`;
const credentials = new n8n_core_1.Credentials(result, result.type, result.nodesAccess);
credentials.setData(decryptedDataOriginal, encryptionKey);
const newCredentialsData = credentials.getDataToSave();
newCredentialsData.updatedAt = this.getCurrentDate();
await _1.Db.collections.Credentials.update(req.query.id, newCredentialsData);
return returnUri;
}));
this.app.get(`/${this.restEndpoint}/oauth1-credential/callback`, async (req, res) => {
try {
const { oauth_verifier, oauth_token, cid } = req.query;
if (oauth_verifier === undefined || oauth_token === undefined) {
const errorResponse = new _1.ResponseHelper.ResponseError(`Insufficient parameters for OAuth1 callback. Received following query parameters: ${JSON.stringify(req.query)}`, undefined, 503);
return _1.ResponseHelper.sendErrorResponse(res, errorResponse);
}
const result = await _1.Db.collections.Credentials.findOne(cid);
if (result === undefined) {
const errorResponse = new _1.ResponseHelper.ResponseError('The credential is not known.', undefined, 404);
return _1.ResponseHelper.sendErrorResponse(res, errorResponse);
}
let encryptionKey;
encryptionKey = await n8n_core_1.UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
const errorResponse = new _1.ResponseHelper.ResponseError('No encryption key got found to decrypt the credentials!', undefined, 503);
return _1.ResponseHelper.sendErrorResponse(res, errorResponse);
}
const mode = 'internal';
const credentialsHelper = new _1.CredentialsHelper(encryptionKey);
const decryptedDataOriginal = await credentialsHelper.getDecrypted(result, result.type, mode, true);
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type, mode);
const options = {
method: 'POST',
url: _.get(oauthCredentials, 'accessTokenUrl'),
qs: {
oauth_token,
oauth_verifier,
},
};
let oauthToken;