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', + }, + }; }