Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6c2c335
Adding new controls
acrosman Jul 3, 2021
87f6e0d
Pass auth mode to main.
acrosman Jul 3, 2021
ce64da5
Refactor of login calls.
acrosman Jul 3, 2021
47f80cd
Initial draft of oauth interface (non-functional)
acrosman Jul 3, 2021
09337b4
Removed unused elements
acrosman Jul 3, 2021
8ecc59b
Fixed Login failure message
acrosman Jul 3, 2021
c90fb54
correction to partition setup
acrosman Jul 3, 2021
fa80e68
Further session correction
acrosman Jul 3, 2021
46b44e4
Revert "Further session correction"
acrosman Jul 3, 2021
25c34bf
partion name change
acrosman Jul 3, 2021
57bc59b
Merge branch 'main' into feature/add-oauth
acrosman Jan 16, 2022
ad80c65
Merge branch 'issue-54' into feature/add-oauth
acrosman Jan 16, 2022
95a2e32
minor tweaks
acrosman Mar 12, 2022
d434f52
Merge branch 'main' into feature/add-oauth
acrosman May 26, 2025
b1fc891
Finish Main back merge
acrosman May 26, 2025
a4f9304
Refactor password login into own function
acrosman May 26, 2025
4bcd394
Ensure passwords do not contain whitespace.
acrosman May 26, 2025
264060f
Switch to browser instead of local window
acrosman May 27, 2025
e12abee
Updates to use browser auth not local.
acrosman May 27, 2025
69d340d
And URL pattern filtering on open requests for better security.
acrosman May 27, 2025
28bb033
Remove extra Electron elements
acrosman May 27, 2025
ce3262e
Add schema check Login URL checker
acrosman May 27, 2025
853153a
Adding oauth test stub.
acrosman Oct 12, 2025
f4af7a9
Fix linting error
acrosman Oct 12, 2025
87792ed
Merge branch 'main' into feature/add-oauth
acrosman Apr 19, 2026
2f1922d
Merge branch 'main' into feature/add-oauth
acrosman Apr 19, 2026
f573c98
Remove unneeded tests
acrosman Apr 19, 2026
4df801b
Merge branch 'feature/add-oauth' of github.com:acrosman/Salesforce2Sq…
acrosman Apr 19, 2026
fa4cd9e
Adding oauth preferences and removing multiple connections
acrosman Apr 19, 2026
8d35f15
Update readme with OAuth instructions
acrosman Apr 19, 2026
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
57 changes: 39 additions & 18 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,30 +77,51 @@ <h5 class="modal-title" id="loginModalLabel">Enter Salesforce Login</h5>
<div class="modal-body">
<form>
<div class="form-group">
<label for="login-username">User Name</label>
<input type="text" id="login-username" class="form-control"
aria-describedby="usernameHelp" placeholder="Enter Salesforce Username">
<small id="usernameHelp" class="form-text">Your Salesforce username must be
in the form of an email address.</small>
</div>
<div class="form-group">
<label for="login-password">Password</label>
<input type="password" id="login-password" class="form-control">
</div>
<div class="form-group">
<label for="login-token">Security Token</label>
<input type="password" class="form-control" aria-describedby="tokenHelp"
id="login-token">
<small id="tokenHelp" class="form-text">Likely you will need your Salesforce
Security token. If you do not have it follow <a
href='https://help.salesforce.com/articleView?id=user_security_token.htm&type=5'>these
reset instructions.</a></small>
<label for="sfconnect-radio-selectors">Select Connection Type</label>
<div class="form-check">
<input class="form-check-input" type="radio"
name="sfconnect-radio-selectors" id="sfconnect-oauth" value="oauth"
checked>
<label class="form-check-label" for="sfconnect-oauth">OAuth2</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="sfconnect-radio-selectors" id="sfconnect-password"
value="password">
<label class="form-check-label"
for="sfconnect-password">Username/Password</label>
</div>
</div>
<div class="form-group">
<label for="login-url">Login URL</label>
<input type="url" id="login-url" class="form-control"
value="https://login.salesforce.com">
</div>
<div id="login-password-wrapper">
<div class="form-group">
<label for="login-username">User Name</label>
<input type="text" id="login-username" class="form-control"
aria-describedby="usernameHelp"
placeholder="Enter Salesforce Username">
<small id="usernameHelp" class="form-text">Your Salesforce username must
be
in the form of an email address.</small>
</div>
<div class="form-group">
<label for="login-password">Password</label>
<input type="password" id="login-password" class="form-control">
</div>
<div class="form-group">
<label for="login-token">Security Token</label>
<input type="password" class="form-control" aria-describedby="tokenHelp"
id="login-token">
<small id="tokenHelp" class="form-text">Likely you will need your
Saleforce
Security token. If you do not have it follow <a
href='https://help.salesforce.com/articleView?id=user_security_token.htm&type=5'>these
reset instructions.</a></small>
</div>
</div>
</form>
</div>
<div class="modal-footer">
Expand Down
17 changes: 13 additions & 4 deletions app/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,19 @@ $.when($.ready).then(() => {
}
});

// Setup Object Select All
$('#btn-select-all-objects').on('click', (event) => {
event.preventDefault();
$('#results-table input[type=checkbox]').prop('checked', true);
// Setup login radio behaviors.
$('#login-password-wrapper').hide();
$('input[type=radio][name=sfconnect-radio-selectors]').on('change', (event) => {
if ($(event.target, ':checked').val() === 'oauth') {
$('#login-password-wrapper').hide();
} else {
$('#login-password-wrapper').show();
}
});

// Get the current application preferences.
window.api.send('get_preferences');

// Setup Object Select All
$('#btn-deselect-all-objects').on('click', (event) => {
event.preventDefault();
Expand Down Expand Up @@ -651,8 +658,10 @@ const updateSqlite3Path = (filePath) => {
// ========= Messages to the main process ===============
// Login
document.getElementById('login-trigger').addEventListener('click', () => {
const modeRadio = document.querySelector('input[type=radio][name="sfconnect-radio-selectors"]:checked');
showLoader('Attempting Login');
window.api.send('sf_login', {
mode: modeRadio.value,
username: document.getElementById('login-username').value,
password: document.getElementById('login-password').value,
token: document.getElementById('login-token').value,
Expand Down
3 changes: 2 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function createWindow() {
width: display.workArea.width,
height: display.workArea.height,
frame: true,
partiion: 'persist: secured-partition',
webPreferences: {
devTools: isDev,
nodeIntegration: false, // Disable nodeIntegration for security.
Expand Down Expand Up @@ -90,7 +91,7 @@ function createWindow() {
// https://www.electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content
// https://github.com/doyensec/electronegativity/wiki/PERMISSION_REQUEST_HANDLER_GLOBAL_CHECK
session
.fromPartition('secured-partition')
.fromPartition('persist: secured-partition')
.setPermissionRequestHandler((webContents, permission, callback) => {
callback(false);
});
Expand Down
1 change: 1 addition & 0 deletions src/sf_calls.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const path = require('path');
const electron = require('electron');
const jsforce = require('jsforce');
const knex = require('knex');
const oauth = require('./sf_oauth2');
const constants = require('./constants');

// Get the dialog library from Electron
Expand Down
101 changes: 101 additions & 0 deletions src/sf_oauth2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const electron = require("electron"); // eslint-disable-line

// Extract the needed elements.
const {
BrowserWindow,
session,
} = electron;

// Additional Tooling.
const jsforce = require('jsforce');

function createAuthWindow() {
// Lock down the session.
const authSession = session.fromPartition('auth-partition');
authSession.setPermissionRequestHandler((webContents, permission, callback) => {
callback(false);
});

// Create the browser window.
let authWindow = new BrowserWindow({
Comment thread Fixed
width: 300,
height: 500,
resizable: false,
frame: false,
show: false,
session: authSession,
webPreferences: {
devTools: true,
nodeIntegration: false, // Disable nodeIntegration for security.
nodeIntegrationInWorker: false,
nodeIntegrationInSubFrames: false,
disableBlinkFeatures: 'Auxclick', // See: https://github.com/doyensec/electronegativity/wiki/AUXCLICK_JS_CHECK
contextIsolation: true, // Enabling contextIsolation to protect against prototype pollution.
worldSafeExecuteJavaScript: true, // https://github.com/electron/electron/pull/24712
enableRemoteModule: false, // Turn off remote to avoid temptation.
// preload: path.join(app.getAppPath(), 'app/authPreload.js'),
},
});

// Emitted when the window is closed.
authWindow.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
authWindow = null;
});

authWindow.webContents.on('did-fail-load', () => {
console.log('did-fail-load');
});

return authWindow;
}

function attemptLogin(authDomain) {
const jsfOauth = new jsforce.OAuth2({
loginUrl: authDomain,
// Temporary testing value from trailhead org.
clientId: '3MVG9cHH2bfKACZaQ.djia5w9g24IaXl.LCkYPmKS.T7ud235Q8c1pX0_zN6xkDBj.88d1qLf1pTcmZwGODB.',
redirectUri: 'https://localhost/completesetup',
});

const authWindow = createAuthWindow();

// Prepare to filter only the callbacks for my redirectUri
const filter = {
urls: [`${authDomain}/*`],
};

// Intercept 302's triggered on page load attempt and redirect.
authWindow.webContents.session.webRequest.onHeadersReceived(filter, (details, callback) => {
const { statusCode, responseHeaders } = details;

// This is redirect, so let's try again.
if (statusCode === 302) {
authWindow.loadURL(responseHeaders.Location.pop());
}

// don't forget to let the request proceed
callback({
cancel: false,
});
});

// 'will-navigate' is an event emitted when the window.location changes
// newUrl should contain the tokens you need
authWindow.webContents.on('will-navigate', (event, newUrl) => {
console.log(`Redirected to: ${newUrl}`);
// More complex code to handle tokens goes here.
});

authWindow.once('ready-to-show', () => {
authWindow.show();
});

// Load the authUrl from Salesforce.
const authUrl = jsfOauth.getAuthorizationUrl({ scope: 'api id web refresh_token' });
authWindow.loadURL(authUrl);
}

exports.attemptLogin = attemptLogin;