Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth2 support in salesforce #470

Merged
merged 11 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/four-coats-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openfn/language-salesforce': minor
---

Add `OAuth` support
3 changes: 1 addition & 2 deletions packages/salesforce/configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"required": [
"loginUrl",
"username",
"password",
"securityToken"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtuchi , is securityToken not required anymore?

Copy link
Collaborator Author

@mtuchi mtuchi Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taylordowns2000 according to jsforce, securityToken is optional - https://jsforce.github.io/document/#username-and-password-login

Screenshot 2024-01-26 at 3 58 17 PM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah wow. @aleksa-krolls , can you confirm that we don't need users to provide their security token when creating a new Salesforce credential? (is this an admin setting in a client's salesforce system? something like, "require security token for API access"?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most salesforce instance are configured to require securityToken but if a client have a salesforce instance that does not require securityToken then the job should pass.

The change i introduced also helps with credential creation on lighting which make the Security Token input optional

"password"
]
}
41 changes: 41 additions & 0 deletions packages/salesforce/oauth-configuration-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$comment": "OAuth2",
"properties": {
"access_token": {
"title": "Access Token",
"type": "string",
"description": "Your Salesforce OAuth2 token",
"writeOnly": true,
"minLength": 1,
"examples": [
"00DU0000000Krrw!AQgAQG7lgt2Eq9yaOy_sU9e4pSwcoUTm7YGkGWTnltn0dJBkjdNhmOhdRtMMuakaO9GjXToh5b_enUIcjOGm5uU.mFhxR.R4"
]
},
"other_oparams": {
"type": "object",
"properties": {
"instance_url": {
"type": "string",
"format": "uri",
"title": "Instance URL",
"description": "Your Salesforce instance URL",
"writeOnly": true,
"minLength": 1,
"examples": [
"https://na1.salesforce.com"
]
}
},
"required": [
"instance_url"
]
}
},
"type": "object",
"additionalProperties": true,
"required": [
"access_token",
"other_oparams"
]
}
99 changes: 51 additions & 48 deletions packages/salesforce/src/Adaptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ export function query(qs) {

return connection.query(resolvedQs, function (err, result) {
if (err) {
return console.error(err);
const { message, errorCode } = err;
console.error(`${errorCode}: ${message}`);
throw err;
}

console.log(
Expand Down Expand Up @@ -646,69 +648,72 @@ export function reference(position) {
return state => state.references[position].id;
}

function setApiVersion(apiVersion) {
function getConnection(state, options) {
const { apiVersion } = state.configuration;

const apiVersionRegex = /^\d{2}\.\d$/;
let version = '52.0';

if (apiVersion && apiVersionRegex.test(apiVersion)) {
console.log('Using Salesforce API version', apiVersion);
version = apiVersion;
options.version = apiVersion;
} else {
console.log('Invalid salesforce apiVersion', apiVersion);
console.log('Using Salesforce API version', version);
console.log('apiVersion is not defined');
console.log('We recommend using Salesforce API version 52.0 or latest');
}

return version;
return new jsforce.Connection(options);
}
/**
* Creates a connection.
* @example
* createConnection(state)
* @function
* @param {State} state - Runtime state.
* @returns {State}
*/
function createConnection(state) {
const { loginUrl, apiVersion } = state.configuration;

if (!loginUrl) {
throw new Error('loginUrl missing from configuration.');
}
async function createBasicAuthConnection(state) {
const { loginUrl, username, password, securityToken } = state.configuration;

const connection = getConnection(state, { loginUrl });

await connection
.login(username, securityToken ? password + securityToken : password)
.catch(e => {
console.error(`Failed to connect to salesforce as ${username}`);
throw e;
});

console.info(`Connected to salesforce as ${username}.`);

return {
...state,
connection: apiVersion
? new jsforce.Connection({
loginUrl,
version: setApiVersion(apiVersion),
})
: new jsforce.Connection({ loginUrl }),
connection,
};
}

function createAccessTokenConnection(state) {
const { other_params, access_token } = state.configuration;
const { instance_url } = other_params;

const connection = getConnection(state, {
instanceUrl: instance_url,
accessToken: access_token,
});

console.log(`Connected with ${connection._sessionType} session type`);

return {
...state,
connection,
};
}

/**
* Performs a login.
* @example
* login(state)
* @function
* Creates a connection to Salesforce using Basic Auth or OAuth.
* @function createConnection
* @private
* @param {State} state - Runtime state.
* @returns {State}
*/
function login(state) {
const { username, password, securityToken } = state.configuration;
let { connection } = state;
console.info(`Logging in as ${username}.`);

return (
connection
.login(username, password + securityToken)
// NOTE: Uncomment this to debug connection issues.
// .then(response => {
// console.log(connection);
// console.log(response);
// return state;
// })
.then(() => state)
);
function createConnection(state) {
const { access_token } = state.configuration;

return access_token
? createAccessTokenConnection(state)
: createBasicAuthConnection(state);
}

/**
Expand All @@ -733,13 +738,11 @@ export function execute(...operations) {
// takes each operation as an argument.
return commonExecute(
createConnection,
login,
...flatten(operations),
cleanupState
)({ ...initialState, ...state });
};
}

/**
* Removes unserializable keys from the state.
* @example
Expand Down
Loading