From de7a6d6dfb884be6901b0cab23f668fdc8a3e940 Mon Sep 17 00:00:00 2001 From: Rupenieks <32895755+Rupenieks@users.noreply.github.com> Date: Fri, 3 Jul 2020 11:22:39 +0200 Subject: [PATCH 1/3] :construction: Setup Added OAuth2 credentials, UI, generic functions changes --- .../credentials/BitlyOAuth2Api.credentials.ts | 80 +++++++++++++++++++ packages/nodes-base/nodes/Bitly/Bitly.node.ts | 37 ++++++++- .../nodes/Bitly/GenericFunctions.ts | 27 ++++--- packages/nodes-base/package.json | 1 + 4 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts new file mode 100644 index 000000000..2ba8ba3d3 --- /dev/null +++ b/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts @@ -0,0 +1,80 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class BitlyOAuth2Api implements ICredentialType { + name = 'bitlyOAuth2Api'; + displayName = 'Bitly OAuth2 API'; + + extends = [ + 'oAuth2Api', + ]; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://bitly.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api-ssl.bitly.com/oauth/access_token', + required: true, + }, + { + displayName: 'Client ID', + name: 'clientId', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + { + displayName: 'Client Secret', + name: 'clientSecret', + type: 'string' as NodePropertyTypes, + typeOptions: { + password: true, + }, + default: '', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + description: 'For some services additional query parameters have to be set which can be defined here.', + placeholder: 'access_type=offline', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'options' as NodePropertyTypes, + options: [ + { + name: 'Body', + value: 'body', + description: 'Send credentials in body', + }, + { + name: 'Header', + value: 'header', + description: 'Send credentials as Basic Auth header', + }, + ], + default: 'header', + description: 'Resource to consume.', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Bitly/Bitly.node.ts b/packages/nodes-base/nodes/Bitly/Bitly.node.ts index 725185300..9b7aed885 100644 --- a/packages/nodes-base/nodes/Bitly/Bitly.node.ts +++ b/packages/nodes-base/nodes/Bitly/Bitly.node.ts @@ -37,9 +37,44 @@ export class Bitly implements INodeType { { name: 'bitlyApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'bitlyOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, { displayName: 'Resource', name: 'resource', diff --git a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts index 1564c4949..85a2c7094 100644 --- a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts @@ -6,14 +6,12 @@ import { ILoadOptionsFunctions, } from 'n8n-core'; import { IDataObject } from 'n8n-workflow'; +import { attachmentFields } from '../Salesforce/AttachmentDescription'; export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('bitlyApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } + const authenticationMethod = this.getNodeParameter('authentication', 0) as string; let options: OptionsWithUri = { - headers: { Authorization: `Bearer ${credentials.accessToken}`}, + headers: {}, method, qs, body, @@ -24,10 +22,21 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | if (Object.keys(options.body).length === 0) { delete options.body; } - try { - return await this.helpers.request!(options); - } catch (err) { - throw new Error(err); + + try{ + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('bitlyApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + options.headers = { Authorization: `Bearer ${credentials.accessToken}`}; + + return await this.helpers.request!(options); + } else { + return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options); + } + } catch(error) { + throw new Error(error); } } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c19d8e234..d5370e6b7 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -37,6 +37,7 @@ "dist/credentials/BannerbearApi.credentials.js", "dist/credentials/BitbucketApi.credentials.js", "dist/credentials/BitlyApi.credentials.js", + "dist/credentials/BitlyOAuth2Api.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/ClearbitApi.credentials.js", "dist/credentials/ClickUpApi.credentials.js", From c6e060904e2683db71ea1ae4448ec777350ea9f8 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 8 Jul 2020 15:15:25 -0400 Subject: [PATCH 2/3] :zap: Fix issue with OAuth2 --- packages/cli/src/Server.ts | 19 ++++++++++-------- .../credentials/BitlyOAuth2Api.credentials.ts | 20 ++++--------------- .../nodes/Bitly/GenericFunctions.ts | 3 ++- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 1b3750a20..cc87aa897 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1194,6 +1194,15 @@ class App { let options = {}; + const oAuth2Parameters = { + clientId: _.get(oauthCredentials, 'clientId') as string, + clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string, + accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string, + authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string, + redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`, + scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',') + }; + if (_.get(oauthCredentials, 'authentication', 'header') as string === 'body') { options = { body: { @@ -1201,16 +1210,10 @@ class App { client_secret: _.get(oauthCredentials, 'clientSecret', '') as string, }, }; + delete oAuth2Parameters.clientSecret; } - const oAuthObj = new clientOAuth2({ - clientId: _.get(oauthCredentials, 'clientId') as string, - clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string, - accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string, - authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string, - redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`, - scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',') - }); + const oAuthObj = new clientOAuth2(oAuth2Parameters); const oauthToken = await oAuthObj.code.getToken(req.originalUrl, options); diff --git a/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts index 2ba8ba3d3..0919e3063 100644 --- a/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/BitlyOAuth2Api.credentials.ts @@ -7,7 +7,7 @@ import { export class BitlyOAuth2Api implements ICredentialType { name = 'bitlyOAuth2Api'; displayName = 'Bitly OAuth2 API'; - + extends = [ 'oAuth2Api', ]; @@ -55,25 +55,13 @@ export class BitlyOAuth2Api implements ICredentialType { type: 'hidden' as NodePropertyTypes, default: '', description: 'For some services additional query parameters have to be set which can be defined here.', - placeholder: 'access_type=offline', + placeholder: '', }, { displayName: 'Authentication', name: 'authentication', - type: 'options' as NodePropertyTypes, - options: [ - { - name: 'Body', - value: 'body', - description: 'Send credentials in body', - }, - { - name: 'Header', - value: 'header', - description: 'Send credentials as Basic Auth header', - }, - ], - default: 'header', + type: 'hidden' as NodePropertyTypes, + default: 'body', description: 'Resource to consume.', }, ]; diff --git a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts index 85a2c7094..edc8873a1 100644 --- a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts @@ -33,7 +33,8 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } else { - return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options); + //@ts-ignore + return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, 'Bearer'); } } catch(error) { throw new Error(error); From d76e90541d9658c629d6154f67c6bd1771754773 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 9 Aug 2020 19:23:51 -0400 Subject: [PATCH 3/3] :zap: Small improvements --- packages/nodes-base/nodes/Bitly/Bitly.node.ts | 3 +++ .../nodes/Bitly/GenericFunctions.ts | 25 ++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/nodes/Bitly/Bitly.node.ts b/packages/nodes-base/nodes/Bitly/Bitly.node.ts index 9b7aed885..d712238ef 100644 --- a/packages/nodes-base/nodes/Bitly/Bitly.node.ts +++ b/packages/nodes-base/nodes/Bitly/Bitly.node.ts @@ -1,6 +1,7 @@ import { IExecuteFunctions, } from 'n8n-core'; + import { IDataObject, INodeTypeDescription, @@ -9,10 +10,12 @@ import { ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; + import { linkFields, linkOperations } from './LinkDescription'; + import { bitlyApiRequest, bitlyApiRequestAllItems, diff --git a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts index edc8873a1..b44a894ad 100644 --- a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts @@ -1,12 +1,17 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, + } from 'request'; + import { IExecuteFunctions, IExecuteSingleFunctions, IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; -import { attachmentFields } from '../Salesforce/AttachmentDescription'; + +import { + IDataObject, + } from 'n8n-workflow'; export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const authenticationMethod = this.getNodeParameter('authentication', 0) as string; @@ -33,11 +38,19 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } else { - //@ts-ignore - return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, 'Bearer'); + + return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, { tokenType: 'Bearer' }); } } catch(error) { - throw new Error(error); + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + const errorBody = error.response.body; + throw new Error(`Bitly error response [${error.statusCode}]: ${errorBody.message}`); + } + + // Expected error data did not get returned so throw the actual error + throw error; } }