diff --git a/packages/nodes-base/credentials/ERPNextApi.credentials.ts b/packages/nodes-base/credentials/ERPNextApi.credentials.ts index 6ca1016457..264aae74a7 100644 --- a/packages/nodes-base/credentials/ERPNextApi.credentials.ts +++ b/packages/nodes-base/credentials/ERPNextApi.credentials.ts @@ -1,4 +1,6 @@ import { + IAuthenticateGeneric, + ICredentialTestRequest, ICredentialType, INodeProperties, } from 'n8n-workflow'; @@ -66,5 +68,27 @@ export class ERPNextApi implements ICredentialType { }, }, }, + { + displayName: 'Ignore SSL Issues', + name: 'allowUnauthorizedCerts', + type: 'boolean', + description: 'Whether to connect even if SSL certificate validation is not possible', + default: false, + }, ]; + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=token {{$credentials.apiKey}}:{{$credentials.apiSecret}}', + }, + }, + }; + test: ICredentialTestRequest = { + request: { + baseURL: '={{$credentials.environment === "cloudHosted" ? "https://" + $credentials.subdomain + ".erpnext.com" : $credentials.domain}}', + url: '/api/resource/Doctype', + skipSslCertificateValidation: '={{ $credentials.allowUnauthorizedCerts }}', + }, + }; } diff --git a/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts b/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts index 9afc73f5cc..81b799b35d 100644 --- a/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts @@ -12,7 +12,6 @@ import { IHookFunctions, IWebhookFunctions, NodeApiError, - NodeOperationError } from 'n8n-workflow'; export async function erpNextApiRequest( @@ -31,13 +30,13 @@ export async function erpNextApiRequest( headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', - Authorization: `token ${credentials.apiKey}:${credentials.apiSecret}`, }, method, body, qs: query, uri: uri || `${baseUrl}${resource}`, json: true, + rejectUnauthorized: !credentials.allowUnauthorizedCerts as boolean, }; options = Object.assign({}, options, option); @@ -50,7 +49,7 @@ export async function erpNextApiRequest( delete options.qs; } try { - return await this.helpers.request!(options); + return await this.helpers.requestWithAuthentication.call(this, 'erpNextApi',options); } catch (error) { if (error.statusCode === 403) { throw new NodeApiError(this.getNode(), { message: 'DocType unavailable.' }); @@ -105,4 +104,5 @@ type ERPNextApiCredentials = { environment: 'cloudHosted' | 'selfHosted'; subdomain?: string; domain?: string; + allowUnauthorizedCerts?: boolean; }; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 0e8712d5ef..304466db49 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -261,8 +261,34 @@ export interface IAuthenticateRuleResponseSuccessBody extends IAuthenticateRuleB value: any; }; } + +type Override = Omit & B; + +export namespace DeclarativeRestApiSettings { + // The type below might be extended + // with new options that need to be parsed as expressions + export type HttpRequestOptions = Override< + IHttpRequestOptions, + { skipSslCertificateValidation?: string | boolean; url?: string } + >; + + export type ResultOptions = { + maxResults?: number | string; + options: HttpRequestOptions; + paginate?: boolean | string; + preSend: PreSendAction[]; + postReceive: Array<{ + data: { + parameterValue: string | IDataObject | undefined; + }; + actions: PostReceiveAction[]; + }>; + requestOperations?: IN8nRequestOperations; + }; +} + export interface ICredentialTestRequest { - request: IHttpRequestOptions; + request: DeclarativeRestApiSettings.HttpRequestOptions; rules?: IAuthenticateRuleResponseCode[] | IAuthenticateRuleResponseSuccessBody[]; } @@ -487,7 +513,7 @@ export interface IN8nRequestOperations { | IN8nRequestOperationPaginationOffset | (( this: IExecutePaginationFunctions, - requestOptions: IRequestOptionsFromParameters, + requestOptions: DeclarativeRestApiSettings.ResultOptions, ) => Promise); } @@ -600,7 +626,7 @@ export interface IExecuteSingleFunctions { export interface IExecutePaginationFunctions extends IExecuteSingleFunctions { makeRoutingRequest( this: IAllExecuteFunctions, - requestOptions: IRequestOptionsFromParameters, + requestOptions: DeclarativeRestApiSettings.ResultOptions, ): Promise; } export interface IExecuteWorkflowInfo { @@ -889,7 +915,7 @@ export interface ILoadOptions { routing?: { operations?: IN8nRequestOperations; output?: INodeRequestOutput; - request?: IHttpRequestOptionsFromParameters; + request?: DeclarativeRestApiSettings.HttpRequestOptions; }; } @@ -1069,7 +1095,7 @@ export interface INodeTypeBaseDescription { export interface INodePropertyRouting { operations?: IN8nRequestOperations; // Should be changed, does not sound right output?: INodeRequestOutput; - request?: IHttpRequestOptionsFromParameters; + request?: DeclarativeRestApiSettings.HttpRequestOptions; send?: INodeRequestSend; } @@ -1147,24 +1173,6 @@ export interface IPostReceiveSort extends IPostReceiveBase { }; } -export interface IHttpRequestOptionsFromParameters extends Partial { - url?: string; -} - -export interface IRequestOptionsFromParameters { - maxResults?: number | string; - options: IHttpRequestOptionsFromParameters; - paginate?: boolean | string; - preSend: PreSendAction[]; - postReceive: Array<{ - data: { - parameterValue: string | IDataObject | undefined; - }; - actions: PostReceiveAction[]; - }>; - requestOperations?: IN8nRequestOperations; -} - export interface INodeTypeDescription extends INodeTypeBaseDescription { version: number | number[]; defaults: INodeParameters; @@ -1178,7 +1186,7 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription { credentials?: INodeCredentialDescription[]; maxNodes?: number; // How many nodes of that type can be created in a workflow polling?: boolean; - requestDefaults?: IHttpRequestOptionsFromParameters; + requestDefaults?: DeclarativeRestApiSettings.HttpRequestOptions; requestOperations?: IN8nRequestOperations; hooks?: { [key: string]: INodeHookDescription[] | undefined; diff --git a/packages/workflow/src/NodeErrors.ts b/packages/workflow/src/NodeErrors.ts index c8b0b52bbb..399c228460 100644 --- a/packages/workflow/src/NodeErrors.ts +++ b/packages/workflow/src/NodeErrors.ts @@ -292,7 +292,7 @@ export class NodeApiError extends NodeError { } // if it's an error generated by axios // look for descriptions in the response object - if (error.isAxiosError) { + if (error.isAxiosError && error.response) { error = error.response as JsonObject; } diff --git a/packages/workflow/src/RoutingNode.ts b/packages/workflow/src/RoutingNode.ts index a103dcabc3..683bd77721 100644 --- a/packages/workflow/src/RoutingNode.ts +++ b/packages/workflow/src/RoutingNode.ts @@ -24,7 +24,7 @@ import { INodeParameters, INodePropertyOptions, INodeType, - IRequestOptionsFromParameters, + DeclarativeRestApiSettings, IRunExecutionData, ITaskDataConnections, IWorkflowDataProxyAdditionalKeys, @@ -127,7 +127,7 @@ export class RoutingNode { executeData, this.mode, ); - const requestData: IRequestOptionsFromParameters = { + const requestData: DeclarativeRestApiSettings.ResultOptions = { options: { qs: {}, body: {}, @@ -214,8 +214,8 @@ export class RoutingNode { } mergeOptions( - destinationOptions: IRequestOptionsFromParameters, - sourceOptions?: IRequestOptionsFromParameters, + destinationOptions: DeclarativeRestApiSettings.ResultOptions, + sourceOptions?: DeclarativeRestApiSettings.ResultOptions, ): void { if (sourceOptions) { destinationOptions.paginate = destinationOptions.paginate ?? sourceOptions.paginate; @@ -375,7 +375,7 @@ export class RoutingNode { async rawRoutingRequest( executeSingleFunctions: IExecuteSingleFunctions, - requestData: IRequestOptionsFromParameters, + requestData: DeclarativeRestApiSettings.ResultOptions, itemIndex: number, runIndex: number, credentialType?: string, @@ -434,7 +434,7 @@ export class RoutingNode { } async makeRoutingRequest( - requestData: IRequestOptionsFromParameters, + requestData: DeclarativeRestApiSettings.ResultOptions, executeSingleFunctions: IExecuteSingleFunctions, itemIndex: number, runIndex: number, @@ -452,7 +452,7 @@ export class RoutingNode { const executePaginationFunctions = { ...executeSingleFunctions, - makeRoutingRequest: async (requestOptions: IRequestOptionsFromParameters) => { + makeRoutingRequest: async (requestOptions: DeclarativeRestApiSettings.ResultOptions) => { return this.rawRoutingRequest( executeSingleFunctions, requestOptions, @@ -591,8 +591,8 @@ export class RoutingNode { runIndex: number, path: string, additionalKeys?: IWorkflowDataProxyAdditionalKeys, - ): IRequestOptionsFromParameters | undefined { - const returnData: IRequestOptionsFromParameters = { + ): DeclarativeRestApiSettings.ResultOptions | undefined { + const returnData: DeclarativeRestApiSettings.ResultOptions = { options: { qs: {}, body: {}, diff --git a/packages/workflow/test/RoutingNode.test.ts b/packages/workflow/test/RoutingNode.test.ts index 8da36731a4..86b1afe720 100644 --- a/packages/workflow/test/RoutingNode.test.ts +++ b/packages/workflow/test/RoutingNode.test.ts @@ -2,7 +2,7 @@ import { INode, INodeExecutionData, INodeParameters, - IRequestOptionsFromParameters, + DeclarativeRestApiSettings, IRunExecutionData, RoutingNode, Workflow, @@ -46,7 +46,7 @@ describe('RoutingNode', () => { nodeParameters: INodeParameters; nodeTypeProperties: INodeProperties; }; - output: IRequestOptionsFromParameters | undefined; + output: DeclarativeRestApiSettings.ResultOptions | undefined; }> = [ { description: 'single parameter, only send defined, fixed value',