mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 05:17:28 -08:00
feat(ERPNext Node): Add credential test and add support for unauthorized certs (#3732)
* ✨ Add cred injection, cred testing, allow unauthorized certs * Add support for skipping SSL for cred testing * 📘 Add partial override for request options types (#3739) * Change field names and fix error handling * Fix typo Co-authored-by: Omar Ajoue <krynble@gmail.com> Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
parent
1965407030
commit
a02b206170
|
@ -1,4 +1,6 @@
|
||||||
import {
|
import {
|
||||||
|
IAuthenticateGeneric,
|
||||||
|
ICredentialTestRequest,
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} 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 }}',
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
IHookFunctions,
|
IHookFunctions,
|
||||||
IWebhookFunctions,
|
IWebhookFunctions,
|
||||||
NodeApiError,
|
NodeApiError,
|
||||||
NodeOperationError
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export async function erpNextApiRequest(
|
export async function erpNextApiRequest(
|
||||||
|
@ -31,13 +30,13 @@ export async function erpNextApiRequest(
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `token ${credentials.apiKey}:${credentials.apiSecret}`,
|
|
||||||
},
|
},
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
qs: query,
|
qs: query,
|
||||||
uri: uri || `${baseUrl}${resource}`,
|
uri: uri || `${baseUrl}${resource}`,
|
||||||
json: true,
|
json: true,
|
||||||
|
rejectUnauthorized: !credentials.allowUnauthorizedCerts as boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
options = Object.assign({}, options, option);
|
options = Object.assign({}, options, option);
|
||||||
|
@ -50,7 +49,7 @@ export async function erpNextApiRequest(
|
||||||
delete options.qs;
|
delete options.qs;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await this.helpers.request!(options);
|
return await this.helpers.requestWithAuthentication.call(this, 'erpNextApi',options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.statusCode === 403) {
|
if (error.statusCode === 403) {
|
||||||
throw new NodeApiError(this.getNode(), { message: 'DocType unavailable.' });
|
throw new NodeApiError(this.getNode(), { message: 'DocType unavailable.' });
|
||||||
|
@ -105,4 +104,5 @@ type ERPNextApiCredentials = {
|
||||||
environment: 'cloudHosted' | 'selfHosted';
|
environment: 'cloudHosted' | 'selfHosted';
|
||||||
subdomain?: string;
|
subdomain?: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
|
allowUnauthorizedCerts?: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -261,8 +261,34 @@ export interface IAuthenticateRuleResponseSuccessBody extends IAuthenticateRuleB
|
||||||
value: any;
|
value: any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Override<A extends object, B extends object> = Omit<A, keyof B> & 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 {
|
export interface ICredentialTestRequest {
|
||||||
request: IHttpRequestOptions;
|
request: DeclarativeRestApiSettings.HttpRequestOptions;
|
||||||
rules?: IAuthenticateRuleResponseCode[] | IAuthenticateRuleResponseSuccessBody[];
|
rules?: IAuthenticateRuleResponseCode[] | IAuthenticateRuleResponseSuccessBody[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,7 +513,7 @@ export interface IN8nRequestOperations {
|
||||||
| IN8nRequestOperationPaginationOffset
|
| IN8nRequestOperationPaginationOffset
|
||||||
| ((
|
| ((
|
||||||
this: IExecutePaginationFunctions,
|
this: IExecutePaginationFunctions,
|
||||||
requestOptions: IRequestOptionsFromParameters,
|
requestOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||||
) => Promise<INodeExecutionData[]>);
|
) => Promise<INodeExecutionData[]>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,7 +626,7 @@ export interface IExecuteSingleFunctions {
|
||||||
export interface IExecutePaginationFunctions extends IExecuteSingleFunctions {
|
export interface IExecutePaginationFunctions extends IExecuteSingleFunctions {
|
||||||
makeRoutingRequest(
|
makeRoutingRequest(
|
||||||
this: IAllExecuteFunctions,
|
this: IAllExecuteFunctions,
|
||||||
requestOptions: IRequestOptionsFromParameters,
|
requestOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||||
): Promise<INodeExecutionData[]>;
|
): Promise<INodeExecutionData[]>;
|
||||||
}
|
}
|
||||||
export interface IExecuteWorkflowInfo {
|
export interface IExecuteWorkflowInfo {
|
||||||
|
@ -889,7 +915,7 @@ export interface ILoadOptions {
|
||||||
routing?: {
|
routing?: {
|
||||||
operations?: IN8nRequestOperations;
|
operations?: IN8nRequestOperations;
|
||||||
output?: INodeRequestOutput;
|
output?: INodeRequestOutput;
|
||||||
request?: IHttpRequestOptionsFromParameters;
|
request?: DeclarativeRestApiSettings.HttpRequestOptions;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1069,7 +1095,7 @@ export interface INodeTypeBaseDescription {
|
||||||
export interface INodePropertyRouting {
|
export interface INodePropertyRouting {
|
||||||
operations?: IN8nRequestOperations; // Should be changed, does not sound right
|
operations?: IN8nRequestOperations; // Should be changed, does not sound right
|
||||||
output?: INodeRequestOutput;
|
output?: INodeRequestOutput;
|
||||||
request?: IHttpRequestOptionsFromParameters;
|
request?: DeclarativeRestApiSettings.HttpRequestOptions;
|
||||||
send?: INodeRequestSend;
|
send?: INodeRequestSend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1147,24 +1173,6 @@ export interface IPostReceiveSort extends IPostReceiveBase {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHttpRequestOptionsFromParameters extends Partial<IHttpRequestOptions> {
|
|
||||||
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 {
|
export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||||
version: number | number[];
|
version: number | number[];
|
||||||
defaults: INodeParameters;
|
defaults: INodeParameters;
|
||||||
|
@ -1178,7 +1186,7 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||||
credentials?: INodeCredentialDescription[];
|
credentials?: INodeCredentialDescription[];
|
||||||
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
||||||
polling?: boolean;
|
polling?: boolean;
|
||||||
requestDefaults?: IHttpRequestOptionsFromParameters;
|
requestDefaults?: DeclarativeRestApiSettings.HttpRequestOptions;
|
||||||
requestOperations?: IN8nRequestOperations;
|
requestOperations?: IN8nRequestOperations;
|
||||||
hooks?: {
|
hooks?: {
|
||||||
[key: string]: INodeHookDescription[] | undefined;
|
[key: string]: INodeHookDescription[] | undefined;
|
||||||
|
|
|
@ -292,7 +292,7 @@ export class NodeApiError extends NodeError {
|
||||||
}
|
}
|
||||||
// if it's an error generated by axios
|
// if it's an error generated by axios
|
||||||
// look for descriptions in the response object
|
// look for descriptions in the response object
|
||||||
if (error.isAxiosError) {
|
if (error.isAxiosError && error.response) {
|
||||||
error = error.response as JsonObject;
|
error = error.response as JsonObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeType,
|
INodeType,
|
||||||
IRequestOptionsFromParameters,
|
DeclarativeRestApiSettings,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskDataConnections,
|
ITaskDataConnections,
|
||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
|
@ -127,7 +127,7 @@ export class RoutingNode {
|
||||||
executeData,
|
executeData,
|
||||||
this.mode,
|
this.mode,
|
||||||
);
|
);
|
||||||
const requestData: IRequestOptionsFromParameters = {
|
const requestData: DeclarativeRestApiSettings.ResultOptions = {
|
||||||
options: {
|
options: {
|
||||||
qs: {},
|
qs: {},
|
||||||
body: {},
|
body: {},
|
||||||
|
@ -214,8 +214,8 @@ export class RoutingNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeOptions(
|
mergeOptions(
|
||||||
destinationOptions: IRequestOptionsFromParameters,
|
destinationOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||||
sourceOptions?: IRequestOptionsFromParameters,
|
sourceOptions?: DeclarativeRestApiSettings.ResultOptions,
|
||||||
): void {
|
): void {
|
||||||
if (sourceOptions) {
|
if (sourceOptions) {
|
||||||
destinationOptions.paginate = destinationOptions.paginate ?? sourceOptions.paginate;
|
destinationOptions.paginate = destinationOptions.paginate ?? sourceOptions.paginate;
|
||||||
|
@ -375,7 +375,7 @@ export class RoutingNode {
|
||||||
|
|
||||||
async rawRoutingRequest(
|
async rawRoutingRequest(
|
||||||
executeSingleFunctions: IExecuteSingleFunctions,
|
executeSingleFunctions: IExecuteSingleFunctions,
|
||||||
requestData: IRequestOptionsFromParameters,
|
requestData: DeclarativeRestApiSettings.ResultOptions,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
credentialType?: string,
|
credentialType?: string,
|
||||||
|
@ -434,7 +434,7 @@ export class RoutingNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
async makeRoutingRequest(
|
async makeRoutingRequest(
|
||||||
requestData: IRequestOptionsFromParameters,
|
requestData: DeclarativeRestApiSettings.ResultOptions,
|
||||||
executeSingleFunctions: IExecuteSingleFunctions,
|
executeSingleFunctions: IExecuteSingleFunctions,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
|
@ -452,7 +452,7 @@ export class RoutingNode {
|
||||||
|
|
||||||
const executePaginationFunctions = {
|
const executePaginationFunctions = {
|
||||||
...executeSingleFunctions,
|
...executeSingleFunctions,
|
||||||
makeRoutingRequest: async (requestOptions: IRequestOptionsFromParameters) => {
|
makeRoutingRequest: async (requestOptions: DeclarativeRestApiSettings.ResultOptions) => {
|
||||||
return this.rawRoutingRequest(
|
return this.rawRoutingRequest(
|
||||||
executeSingleFunctions,
|
executeSingleFunctions,
|
||||||
requestOptions,
|
requestOptions,
|
||||||
|
@ -591,8 +591,8 @@ export class RoutingNode {
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
path: string,
|
path: string,
|
||||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||||
): IRequestOptionsFromParameters | undefined {
|
): DeclarativeRestApiSettings.ResultOptions | undefined {
|
||||||
const returnData: IRequestOptionsFromParameters = {
|
const returnData: DeclarativeRestApiSettings.ResultOptions = {
|
||||||
options: {
|
options: {
|
||||||
qs: {},
|
qs: {},
|
||||||
body: {},
|
body: {},
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
IRequestOptionsFromParameters,
|
DeclarativeRestApiSettings,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
RoutingNode,
|
RoutingNode,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
@ -46,7 +46,7 @@ describe('RoutingNode', () => {
|
||||||
nodeParameters: INodeParameters;
|
nodeParameters: INodeParameters;
|
||||||
nodeTypeProperties: INodeProperties;
|
nodeTypeProperties: INodeProperties;
|
||||||
};
|
};
|
||||||
output: IRequestOptionsFromParameters | undefined;
|
output: DeclarativeRestApiSettings.ResultOptions | undefined;
|
||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
description: 'single parameter, only send defined, fixed value',
|
description: 'single parameter, only send defined, fixed value',
|
||||||
|
|
Loading…
Reference in a new issue