diff --git a/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialEdit.vue
index eff526f020..f52eb9d38f 100644
--- a/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialEdit.vue
+++ b/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialEdit.vue
@@ -170,11 +170,10 @@ const isCredentialTestable = computed(() => {
return false;
}
- const hasUntestableExpressions = Object.values(credentialData.value).reduce(
- (accu: boolean, value: CredentialInformation) =>
- accu || (typeof value === 'string' && isExpression(value) && !isTestableExpression(value)),
- false,
- );
+ const hasUntestableExpressions = credentialProperties.value.some((prop) => {
+ const value = credentialData.value[prop.name];
+ return typeof value === 'string' && isExpression(value) && !isTestableExpression(value);
+ });
if (hasUntestableExpressions) {
return false;
}
diff --git a/packages/nodes-base/credentials/SalesforceJwtApi.credentials.ts b/packages/nodes-base/credentials/SalesforceJwtApi.credentials.ts
index a7fb72092d..01c5aa6316 100644
--- a/packages/nodes-base/credentials/SalesforceJwtApi.credentials.ts
+++ b/packages/nodes-base/credentials/SalesforceJwtApi.credentials.ts
@@ -1,4 +1,14 @@
-import type { ICredentialType, INodeProperties } from 'n8n-workflow';
+import type { AxiosRequestConfig } from 'axios';
+import axios from 'axios';
+import jwt from 'jsonwebtoken';
+import moment from 'moment-timezone';
+import type {
+ ICredentialDataDecryptedObject,
+ ICredentialTestRequest,
+ ICredentialType,
+ IHttpRequestOptions,
+ INodeProperties,
+} from 'n8n-workflow';
export class SalesforceJwtApi implements ICredentialType {
name = 'salesforceJwtApi';
@@ -52,4 +62,62 @@ export class SalesforceJwtApi implements ICredentialType {
'Use the multiline editor. Make sure it is in standard PEM key format:
-----BEGIN PRIVATE KEY-----
KEY DATA GOES HERE
-----END PRIVATE KEY-----',
},
];
+
+ async authenticate(
+ credentials: ICredentialDataDecryptedObject,
+ requestOptions: IHttpRequestOptions,
+ ): Promise {
+ const now = moment().unix();
+ const authUrl =
+ credentials.environment === 'sandbox'
+ ? 'https://test.salesforce.com'
+ : 'https://login.salesforce.com';
+ const signature = jwt.sign(
+ {
+ iss: credentials.clientId as string,
+ sub: credentials.username as string,
+ aud: authUrl,
+ exp: now + 3 * 60,
+ },
+ credentials.privateKey as string,
+ {
+ algorithm: 'RS256',
+ header: {
+ alg: 'RS256',
+ },
+ },
+ );
+
+ const axiosRequestConfig: AxiosRequestConfig = {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ method: 'POST',
+ data: new URLSearchParams({
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+ assertion: signature,
+ }).toString(),
+ url: `${authUrl}/services/oauth2/token`,
+ responseType: 'json',
+ };
+ const result = await axios(axiosRequestConfig);
+ const { access_token } = result.data as { access_token: string };
+
+ return {
+ ...requestOptions,
+ headers: {
+ ...requestOptions.headers,
+ Authorization: `Bearer ${access_token}`,
+ },
+ };
+ }
+
+ test: ICredentialTestRequest = {
+ request: {
+ baseURL:
+ '={{$credentials?.environment === "sandbox" ? "https://test.salesforce.com" : "https://login.salesforce.com"}}',
+ url: '/services/oauth2/userinfo',
+ method: 'GET',
+ },
+ };
}