Skip to content

Commit

Permalink
feat(Cloudflare Node): add Cloudflare node (#4271)
Browse files Browse the repository at this point in the history
* ✨ Cloudflare node

* ⚡ Add paired items

* Added codex file for Cloudflare

* ⚡ Improvements

Co-authored-by: Jonathan Bennetts <jonathan.bennetts@gmail.com>
  • Loading branch information
RicardoE105 and Joffcom authored Oct 7, 2022
1 parent 1b320cd commit 94a02c6
Show file tree
Hide file tree
Showing 7 changed files with 523 additions and 0 deletions.
35 changes: 35 additions & 0 deletions packages/nodes-base/credentials/CloudflareApi.credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';

export class CloudflareApi implements ICredentialType {
name = 'cloudflareApi';
displayName = 'Cloudflare API';
documentationUrl = 'cloudflare';
properties: INodeProperties[] = [
{
displayName: 'API Token',
name: 'apiToken',
type: 'string',
default: '',
},
];

authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
'Authorization': '=Bearer {{$credentials.apiToken}}',
},
},
};

test: ICredentialTestRequest = {
request: {
baseURL: 'https://api.cloudflare.com/client/v4/user/tokens/verify',
},
};
}
18 changes: 18 additions & 0 deletions packages/nodes-base/nodes/Cloudflare/Cloudflare.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.cloudflare",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/cloudflare"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.cloudflare/"
}
]
}
}
179 changes: 179 additions & 0 deletions packages/nodes-base/nodes/Cloudflare/Cloudflare.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { IExecuteFunctions } from 'n8n-core';

import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';

import { cloudflareApiRequest, cloudflareApiRequestAllItems } from './GenericFunctions';

import { zoneCertificateFields, zoneCertificateOperations } from './ZoneCertificateDescription';

export class Cloudflare implements INodeType {
description: INodeTypeDescription = {
displayName: 'Cloudflare',
name: 'cloudflare',
icon: 'file:cloudflare.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Cloudflare API',
defaults: {
name: 'Cloudflare',
color: '#000000',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'cloudflareApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Zone Certificate',
value: 'zoneCertificate',
},
],
default: 'zoneCertificate',
},
...zoneCertificateOperations,
...zoneCertificateFields,
],
};

methods = {
loadOptions: {
async getZones(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const { result: zones } = await cloudflareApiRequest.call(this, 'GET', '/zones');
for (const zone of zones) {
returnData.push({
name: zone.name,
value: zone.id,
});
}
return returnData;
},
},
};

async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length;
const qs: IDataObject = {};
let responseData;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;

for (let i = 0; i < length; i++) {
try {
if (resource === 'zoneCertificate') {
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-delete-certificate
if (operation === 'delete') {
const zoneId = this.getNodeParameter('zoneId', i) as string;
const certificateId = this.getNodeParameter('certificateId', i) as string;

responseData = await cloudflareApiRequest.call(
this,
'DELETE',
`/zones/${zoneId}/origin_tls_client_auth/${certificateId}`,
{},
);
responseData = responseData.result;
}
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-get-certificate-details
if (operation === 'get') {
const zoneId = this.getNodeParameter('zoneId', i) as string;
const certificateId = this.getNodeParameter('certificateId', i) as string;

responseData = await cloudflareApiRequest.call(
this,
'GET',
`/zones/${zoneId}/origin_tls_client_auth/${certificateId}`,
{},
);
responseData = responseData.result;
}
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-list-certificates
if (operation === 'getMany') {
const zoneId = this.getNodeParameter('zoneId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const filters = this.getNodeParameter('filters', i, {}) as IDataObject;

Object.assign(qs, filters);

if (returnAll) {
responseData = await cloudflareApiRequestAllItems.call(
this,
'result',
'GET',
`/zones/${zoneId}/origin_tls_client_auth`,
{},
qs,
);
} else {
const limit = this.getNodeParameter('limit', i) as number;
Object.assign(qs, { per_page: limit });
responseData = await cloudflareApiRequest.call(
this,
'GET',
`/zones/${zoneId}/origin_tls_client_auth`,
{},
qs,
);
responseData = responseData.result;
}
}
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-upload-certificate
if (operation === 'upload') {
const zoneId = this.getNodeParameter('zoneId', i) as string;
const certificate = this.getNodeParameter('certificate', i) as string;
const privateKey = this.getNodeParameter('privateKey', i) as string;

const body: IDataObject = {
certificate,
private_key: privateKey,
};

responseData = await cloudflareApiRequest.call(
this,
'POST',
`/zones/${zoneId}/origin_tls_client_auth`,
body,
qs,
);

responseData = responseData.result;
}
}

returnData.push(
...this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), {
itemData: { item: i },
}),
);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: error.message } });
continue;
}
throw error;
}
}

return [returnData as INodeExecutionData[]];
}
}
65 changes: 65 additions & 0 deletions packages/nodes-base/nodes/Cloudflare/GenericFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
OptionsWithUri,
} from 'request';

import {
IExecuteFunctions,
IExecuteSingleFunctions,
ILoadOptionsFunctions,
IPollFunctions,
} from 'n8n-core';

import {
IDataObject, NodeApiError,
} from 'n8n-workflow';

export async function cloudflareApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any

const options: OptionsWithUri = {
method,
body,
qs,
uri: `https://api.cloudflare.com/client/v4${resource}`,
json: true,
};

try {
if (Object.keys(headers).length !== 0) {
options.headers = Object.assign({}, options.headers, headers);
}
if (Object.keys(body).length === 0) {
delete options.body;
}
return await this.helpers.requestWithAuthentication.call(this, 'cloudflareApi', options);
} catch (error) {
throw new NodeApiError(this.getNode(), error);
}
}

export async function cloudflareApiRequestAllItems(
this: IExecuteFunctions | ILoadOptionsFunctions,
propertyName: string,
method: string,
endpoint: string,
body: IDataObject = {},
query: IDataObject = {},
// tslint:disable-next-line:no-any
): Promise<any> {
const returnData: IDataObject[] = [];

let responseData;
query.page = 1;

do {
responseData = await cloudflareApiRequest.call(
this,
method,
endpoint,
body,
query,
);
query.page++;
returnData.push.apply(returnData, responseData[propertyName]);
} while (responseData.result_info.total_pages !== responseData.result_info.page);
return returnData;
}
Loading

0 comments on commit 94a02c6

Please sign in to comment.