mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 08:34:07 -08:00
7ce7285f7a
* Changes to types so that credentials can be always loaded from DB This first commit changes all return types from the execute functions and calls to get credentials to be async so we can use await. This is a first step as previously credentials were loaded in memory and always available. We will now be loading them from the DB which requires turning the whole call chain async. * Fix updated files * Removed unnecessary credential loading to improve performance * Fix typo * ⚡ Fix issue * Updated new nodes to load credentials async * ⚡ Remove not needed comment Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
354 lines
8.1 KiB
TypeScript
354 lines
8.1 KiB
TypeScript
import {
|
|
IHookFunctions,
|
|
IWebhookFunctions,
|
|
} from 'n8n-core';
|
|
|
|
import {
|
|
INodeType,
|
|
INodeTypeDescription,
|
|
IWebhookResponseData,
|
|
} from 'n8n-workflow';
|
|
|
|
import {
|
|
pipedriveApiRequest,
|
|
} from './GenericFunctions';
|
|
|
|
import * as basicAuth from 'basic-auth';
|
|
|
|
import {
|
|
Response,
|
|
} from 'express';
|
|
|
|
function authorizationError(resp: Response, realm: string, responseCode: number, message?: string) {
|
|
if (message === undefined) {
|
|
message = 'Authorization problem!';
|
|
if (responseCode === 401) {
|
|
message = 'Authorization is required!';
|
|
} else if (responseCode === 403) {
|
|
message = 'Authorization data is wrong!';
|
|
}
|
|
}
|
|
|
|
resp.writeHead(responseCode, { 'WWW-Authenticate': `Basic realm="${realm}"` });
|
|
resp.end(message);
|
|
return {
|
|
noWebhookResponse: true,
|
|
};
|
|
}
|
|
|
|
export class PipedriveTrigger implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'Pipedrive Trigger',
|
|
name: 'pipedriveTrigger',
|
|
icon: 'file:pipedrive.svg',
|
|
group: ['trigger'],
|
|
version: 1,
|
|
description: 'Starts the workflow when Pipedrive events occur',
|
|
defaults: {
|
|
name: 'Pipedrive Trigger',
|
|
color: '#559922',
|
|
},
|
|
inputs: [],
|
|
outputs: ['main'],
|
|
credentials: [
|
|
{
|
|
name: 'pipedriveApi',
|
|
required: true,
|
|
displayOptions: {
|
|
show: {
|
|
authentication: [
|
|
'apiToken',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'pipedriveOAuth2Api',
|
|
required: true,
|
|
displayOptions: {
|
|
show: {
|
|
authentication: [
|
|
'oAuth2',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'httpBasicAuth',
|
|
required: true,
|
|
displayOptions: {
|
|
show: {
|
|
incomingAuthentication: [
|
|
'basicAuth',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
webhooks: [
|
|
{
|
|
name: 'default',
|
|
httpMethod: 'POST',
|
|
responseMode: 'onReceived',
|
|
path: 'webhook',
|
|
},
|
|
],
|
|
properties: [
|
|
{
|
|
displayName: 'Authentication',
|
|
name: 'authentication',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'API Token',
|
|
value: 'apiToken',
|
|
},
|
|
{
|
|
name: 'OAuth2',
|
|
value: 'oAuth2',
|
|
},
|
|
],
|
|
default: 'apiToken',
|
|
description: 'Method of authentication.',
|
|
},
|
|
{
|
|
displayName: 'Incoming Authentication',
|
|
name: 'incomingAuthentication',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Basic Auth',
|
|
value: 'basicAuth',
|
|
},
|
|
{
|
|
name: 'None',
|
|
value: 'none',
|
|
},
|
|
],
|
|
default: 'none',
|
|
description: 'If authentication should be activated for the webhook (makes it more secure).',
|
|
},
|
|
{
|
|
displayName: 'Action',
|
|
name: 'action',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'All',
|
|
value: '*',
|
|
description: 'Any change',
|
|
},
|
|
{
|
|
name: 'Added',
|
|
value: 'added',
|
|
description: 'Data got added',
|
|
},
|
|
{
|
|
name: 'Deleted',
|
|
value: 'deleted',
|
|
description: 'Data got deleted',
|
|
},
|
|
{
|
|
name: 'Merged',
|
|
value: 'merged',
|
|
description: 'Data got merged',
|
|
},
|
|
{
|
|
name: 'Updated',
|
|
value: 'updated',
|
|
description: 'Data got updated',
|
|
},
|
|
],
|
|
default: '*',
|
|
description: 'Type of action to receive notifications about.',
|
|
},
|
|
{
|
|
displayName: 'Object',
|
|
name: 'object',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'All',
|
|
value: '*',
|
|
},
|
|
{
|
|
name: 'Activity',
|
|
value: 'activity',
|
|
},
|
|
{
|
|
name: 'Activity Type',
|
|
value: 'activityType',
|
|
},
|
|
{
|
|
name: 'Deal',
|
|
value: 'deal',
|
|
},
|
|
{
|
|
name: 'Note',
|
|
value: 'note',
|
|
},
|
|
{
|
|
name: 'Organization',
|
|
value: 'organization',
|
|
},
|
|
{
|
|
name: 'Person',
|
|
value: 'person',
|
|
},
|
|
{
|
|
name: 'Pipeline',
|
|
value: 'pipeline',
|
|
},
|
|
{
|
|
name: 'Product',
|
|
value: 'product',
|
|
},
|
|
{
|
|
name: 'Stage',
|
|
value: 'stage',
|
|
},
|
|
{
|
|
name: 'User',
|
|
value: 'user',
|
|
},
|
|
],
|
|
default: '*',
|
|
description: 'Type of object to receive notifications about.',
|
|
},
|
|
],
|
|
};
|
|
|
|
// @ts-ignore (because of request)
|
|
webhookMethods = {
|
|
default: {
|
|
async checkExists(this: IHookFunctions): Promise<boolean> {
|
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
|
|
const webhookData = this.getWorkflowStaticData('node');
|
|
|
|
const eventAction = this.getNodeParameter('action') as string;
|
|
|
|
const eventObject = this.getNodeParameter('object') as string;
|
|
|
|
// Webhook got created before so check if it still exists
|
|
const endpoint = `/webhooks`;
|
|
|
|
const responseData = await pipedriveApiRequest.call(this, 'GET', endpoint, {});
|
|
|
|
if (responseData.data === undefined) {
|
|
return false;
|
|
}
|
|
|
|
for (const existingData of responseData.data) {
|
|
if (existingData.subscription_url === webhookUrl
|
|
&& existingData.event_action === eventAction
|
|
&& existingData.event_object === eventObject) {
|
|
// The webhook exists already
|
|
webhookData.webhookId = existingData.id;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
async create(this: IHookFunctions): Promise<boolean> {
|
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
const incomingAuthentication = this.getNodeParameter('incomingAuthentication', 0) as string;
|
|
const eventAction = this.getNodeParameter('action') as string;
|
|
const eventObject = this.getNodeParameter('object') as string;
|
|
|
|
const endpoint = `/webhooks`;
|
|
|
|
const body = {
|
|
event_action: eventAction,
|
|
event_object: eventObject,
|
|
subscription_url: webhookUrl,
|
|
http_auth_user: undefined as string | undefined,
|
|
http_auth_password: undefined as string | undefined,
|
|
};
|
|
|
|
if (incomingAuthentication === 'basicAuth') {
|
|
const httpBasicAuth = await this.getCredentials('httpBasicAuth');
|
|
|
|
if (httpBasicAuth === undefined || !httpBasicAuth.user || !httpBasicAuth.password) {
|
|
// Data is not defined on node so can not authenticate
|
|
return false;
|
|
}
|
|
|
|
body.http_auth_user = httpBasicAuth.user as string;
|
|
body.http_auth_password = httpBasicAuth.password as string;
|
|
}
|
|
|
|
const responseData = await pipedriveApiRequest.call(this, 'POST', endpoint, body);
|
|
|
|
if (responseData.data === undefined || responseData.data.id === undefined) {
|
|
// Required data is missing so was not successful
|
|
return false;
|
|
}
|
|
|
|
const webhookData = this.getWorkflowStaticData('node');
|
|
webhookData.webhookId = responseData.data.id as string;
|
|
|
|
return true;
|
|
},
|
|
async delete(this: IHookFunctions): Promise<boolean> {
|
|
const webhookData = this.getWorkflowStaticData('node');
|
|
|
|
if (webhookData.webhookId !== undefined) {
|
|
const endpoint = `/webhooks/${webhookData.webhookId}`;
|
|
const body = {};
|
|
|
|
try {
|
|
await pipedriveApiRequest.call(this, 'DELETE', endpoint, body);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
|
|
// Remove from the static workflow data so that it is clear
|
|
// that no webhooks are registred anymore
|
|
delete webhookData.webhookId;
|
|
delete webhookData.webhookEvents;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
};
|
|
|
|
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
|
const req = this.getRequestObject();
|
|
const resp = this.getResponseObject();
|
|
const realm = 'Webhook';
|
|
|
|
const incomingAuthentication = this.getNodeParameter('incomingAuthentication', 0) as string;
|
|
|
|
if (incomingAuthentication === 'basicAuth') {
|
|
// Basic authorization is needed to call webhook
|
|
const httpBasicAuth = await this.getCredentials('httpBasicAuth');
|
|
|
|
if (httpBasicAuth === undefined || !httpBasicAuth.user || !httpBasicAuth.password) {
|
|
// Data is not defined on node so can not authenticate
|
|
return authorizationError(resp, realm, 500, 'No authentication data defined on node!');
|
|
}
|
|
|
|
const basicAuthData = basicAuth(req);
|
|
|
|
if (basicAuthData === undefined) {
|
|
// Authorization data is missing
|
|
return authorizationError(resp, realm, 401);
|
|
}
|
|
|
|
if (basicAuthData.name !== httpBasicAuth!.user || basicAuthData.pass !== httpBasicAuth!.password) {
|
|
// Provided authentication data is wrong
|
|
return authorizationError(resp, realm, 403);
|
|
}
|
|
}
|
|
|
|
return {
|
|
workflowData: [
|
|
this.helpers.returnJsonArray(req.body),
|
|
],
|
|
};
|
|
}
|
|
}
|