From 2235ea96e8fd924025b3ab384e95044d707aa46f Mon Sep 17 00:00:00 2001 From: ozweiss Date: Tue, 30 Jul 2024 17:51:50 +0300 Subject: [PATCH 1/2] calendly oauth --- .../CalendlyOAuth2Api.credentials.ts | 52 +++++++++++++++++ .../nodes-base/credentials/icons/Calendly.svg | 1 + .../nodes/Calendly/CalendlyTrigger.node.ts | 40 +++++++++++-- .../nodes/Calendly/GenericFunctions.ts | 57 +++++++------------ packages/nodes-base/package.json | 1 + 5 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/credentials/icons/Calendly.svg diff --git a/packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts new file mode 100644 index 0000000000000..200d238c6d73f --- /dev/null +++ b/packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts @@ -0,0 +1,52 @@ +import type { ICredentialType, INodeProperties, Icon } from 'n8n-workflow'; + +export class CalendlyOAuth2Api implements ICredentialType { + name = 'calendlyOAuth2Api'; + + extends = ['oAuth2Api']; + + displayName = 'Calendly OAuth2 API'; + + documentationUrl = 'calendly'; + + icon: Icon = 'file:icons/Calendly.svg'; + + properties: INodeProperties[] = [ + { + displayName: 'Grant Type', + name: 'grantType', + type: 'hidden', + default: 'authorizationCode', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden', + default: 'https://auth.calendly.com/oauth/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden', + default: 'https://auth.calendly.com/oauth/token', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden', + default: 'header', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden', + default: '', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden', + default: '', + }, + ]; +} diff --git a/packages/nodes-base/credentials/icons/Calendly.svg b/packages/nodes-base/credentials/icons/Calendly.svg new file mode 100644 index 0000000000000..195a7461e33b6 --- /dev/null +++ b/packages/nodes-base/credentials/icons/Calendly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts index d2e7b127237d2..581777386f3c3 100644 --- a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts +++ b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts @@ -26,6 +26,20 @@ export class CalendlyTrigger implements INodeType { { name: 'calendlyApi', required: true, + displayOptions: { + show: { + authentication: ['apiKey'], + }, + }, + }, + { + name: 'calendlyOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: ['oAuth2'], + }, + }, }, ], webhooks: [ @@ -37,6 +51,23 @@ export class CalendlyTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'OAuth2 (recommended)', + value: 'oAuth2', + }, + { + name: 'API Key or Personal Access Token', + value: 'apiKey', + }, + ], + default: 'oAuth2', + }, { displayName: 'Scope', name: 'scope', @@ -86,9 +117,8 @@ export class CalendlyTrigger implements INodeType { const webhookUrl = this.getNodeWebhookUrl('default'); const webhookData = this.getWorkflowStaticData('node'); const events = this.getNodeParameter('events') as string; - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); // remove condition once API Keys are deprecated if (authenticationType === 'apiKey') { @@ -149,9 +179,8 @@ export class CalendlyTrigger implements INodeType { const webhookData = this.getWorkflowStaticData('node'); const webhookUrl = this.getNodeWebhookUrl('default'); const events = this.getNodeParameter('events') as string; - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); // remove condition once API Keys are deprecated if (authenticationType === 'apiKey') { @@ -201,8 +230,7 @@ export class CalendlyTrigger implements INodeType { }, async delete(this: IHookFunctions): Promise { const webhookData = this.getWorkflowStaticData('node'); - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); // remove condition once API Keys are deprecated if (authenticationType === 'apiKey') { diff --git a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts index 4befbb5ceed21..f39aa877618b2 100644 --- a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts @@ -1,6 +1,4 @@ import type { - ICredentialDataDecryptedObject, - ICredentialTestFunctions, IDataObject, IExecuteFunctions, ILoadOptionsFunctions, @@ -10,12 +8,24 @@ import type { IRequestOptions, } from 'n8n-workflow'; -export function getAuthenticationType(data: string): 'accessToken' | 'apiKey' { +function getAuthenticationTypeFromApiKey(data: string): 'accessToken' | 'apiKey' { // The access token is a JWT, so it will always include dots to separate // header, payoload and signature. return data.includes('.') ? 'accessToken' : 'apiKey'; } +export async function getAuthenticationType( + this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, +): Promise<'accessToken' | 'apiKey'> { + const authentication = this.getNodeParameter('authentication', 0) as string; + if (authentication === 'apiKey') { + const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; + return getAuthenticationTypeFromApiKey(apiKey); + } else { + return 'accessToken'; + } +} + export async function calendlyApiRequest( this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, @@ -26,9 +36,7 @@ export async function calendlyApiRequest( uri?: string, option: IDataObject = {}, ): Promise { - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); const headers: IDataObject = { 'Content-Type': 'application/json', @@ -57,37 +65,10 @@ export async function calendlyApiRequest( delete options.qs; } options = Object.assign({}, options, option); - return await this.helpers.requestWithAuthentication.call(this, 'calendlyApi', options); -} - -export async function validateCredentials( - this: ICredentialTestFunctions, - decryptedCredentials: ICredentialDataDecryptedObject, -): Promise { - const credentials = decryptedCredentials; - const { apiKey } = credentials as { - apiKey: string; - }; - - const authenticationType = getAuthenticationType(apiKey); - - const options: IRequestOptions = { - method: 'GET', - uri: '', - json: true, - }; - - if (authenticationType === 'accessToken') { - Object.assign(options, { - headers: { Authorization: `Bearer ${apiKey}` }, - uri: 'https://api.calendly.com/users/me', - }); - } else { - Object.assign(options, { - headers: { 'X-TOKEN': apiKey }, - uri: 'https://calendly.com/api/v1/users/me', - }); - } - return await this.helpers.request(options); + const credentialsType = + (this.getNodeParameter('authentication', 0) as string) === 'apiKey' + ? 'calendlyApi' + : 'calendlyOAuth2Api'; + return await this.helpers.requestWithAuthentication.call(this, credentialsType, options); } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6dc3b1ddf1513..d063d8736226b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -52,6 +52,7 @@ "dist/credentials/BubbleApi.credentials.js", "dist/credentials/CalApi.credentials.js", "dist/credentials/CalendlyApi.credentials.js", + "dist/credentials/CalendlyOAuth2Api.credentials.js", "dist/credentials/CarbonBlackApi.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/CircleCiApi.credentials.js", From 5288759985c019a59e8d86d3551299f69b5c7ee1 Mon Sep 17 00:00:00 2001 From: ozweiss Date: Tue, 30 Jul 2024 18:00:57 +0300 Subject: [PATCH 2/2] change default auth to apiKey to not break older workflows --- packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts index 581777386f3c3..40a10cab92cfa 100644 --- a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts +++ b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts @@ -66,7 +66,7 @@ export class CalendlyTrigger implements INodeType { value: 'apiKey', }, ], - default: 'oAuth2', + default: 'apiKey', }, { displayName: 'Scope',