mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -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 {
|
||||
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 }}',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -261,8 +261,34 @@ export interface IAuthenticateRuleResponseSuccessBody extends IAuthenticateRuleB
|
|||
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 {
|
||||
request: IHttpRequestOptions;
|
||||
request: DeclarativeRestApiSettings.HttpRequestOptions;
|
||||
rules?: IAuthenticateRuleResponseCode[] | IAuthenticateRuleResponseSuccessBody[];
|
||||
}
|
||||
|
||||
|
@ -487,7 +513,7 @@ export interface IN8nRequestOperations {
|
|||
| IN8nRequestOperationPaginationOffset
|
||||
| ((
|
||||
this: IExecutePaginationFunctions,
|
||||
requestOptions: IRequestOptionsFromParameters,
|
||||
requestOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||
) => Promise<INodeExecutionData[]>);
|
||||
}
|
||||
|
||||
|
@ -600,7 +626,7 @@ export interface IExecuteSingleFunctions {
|
|||
export interface IExecutePaginationFunctions extends IExecuteSingleFunctions {
|
||||
makeRoutingRequest(
|
||||
this: IAllExecuteFunctions,
|
||||
requestOptions: IRequestOptionsFromParameters,
|
||||
requestOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||
): Promise<INodeExecutionData[]>;
|
||||
}
|
||||
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<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 {
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue