Skip to content

Commit

Permalink
feat: support refreshing OAuth tokens and prefilling the username on …
Browse files Browse the repository at this point in the history
…the refresh tokens action
  • Loading branch information
Codeneos committed Jan 25, 2023
1 parent 77954b6 commit b31a419
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 29 deletions.
91 changes: 63 additions & 28 deletions packages/util/src/sfdx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ export namespace sfdx {
info(...args: any[]): any;
} = console;

export async function webLogin(options: { instanceUrl?: string; alias?: string }, cancelToken?: CancellationToken) : Promise<SalesforceAuthResult> {
export async function webLogin(options: { instanceUrl?: string; alias?: string, loginHint?: string }, cancelToken?: CancellationToken) : Promise<SalesforceAuthResult> {
const oauthServer = await salesforce.WebOAuthServer.create({
oauthConfig: {
loginUrl: options.instanceUrl ?? 'https://test.salesforce.com'
}
});

await oauthServer.start();
cancelToken?.isCancellationRequested || await open(oauthServer.getAuthorizationUrl(), { wait: false });
const authUrl = `${oauthServer.getAuthorizationUrl()}${options.loginHint ? `&login_hint=${encodeURIComponent(options.loginHint)}` : ''}`;
cancelToken?.isCancellationRequested || await open(authUrl, { wait: false });
const result = oauthServer.authorizeAndSave().then(authInfo => authInfo.getFields(true) as SalesforceAuthResult);

if (cancelToken) {
Expand All @@ -55,19 +56,26 @@ export namespace sfdx {
oauthServer.webServer.close();
});

const caneledPromise = new Promise<SalesforceAuthResult>((_, reject) => cancelToken.onCancellationRequested(() => reject('Operation cancelled')));
const canceledPromise = new Promise<SalesforceAuthResult>((_, reject) => cancelToken.onCancellationRequested(() => reject('Operation cancelled')));

// return race between cancel token
return Promise.race([ caneledPromise, result ]);
return Promise.race([ canceledPromise, result ]);
}

return result;
}

export async function refreshOAuthTokens(usernameOrAlias: string, cancelToken?: CancellationToken) : Promise<SalesforceAuthResult> {
const username = await resolveAlias(usernameOrAlias) || usernameOrAlias;
const authInfo = await salesforce.AuthInfo.create({ username });
return webLogin(authInfo.getFields(false), cancelToken);
const orgDetails = await getOrgDetails(usernameOrAlias);
if (!orgDetails) {
throw new Error(`(sfdx.refreshOAuthTokens) No such org with username or alias ${usernameOrAlias}`);
}

return webLogin({
instanceUrl: orgDetails.instanceUrl,
alias: orgDetails.alias ?? orgDetails.username,
loginHint: orgDetails.username
}, cancelToken);
}

export async function getAllKnownOrgDetails() : Promise<SalesforceOrgInfo[]> {
Expand Down Expand Up @@ -108,8 +116,9 @@ export namespace sfdx {
loginUrl: authFields.loginUrl ?? authFields.instanceUrl,
username: authFields.username,
clientId: authFields.clientId!,
clientSecret: authFields.clientSecret,
refreshToken: authFields.refreshToken!,
alias: stateAggregator.aliases.resolveAlias(authFields.username) ?? undefined
alias: stateAggregator.aliases.get(authFields.username) || undefined
};
} catch(err) {
logger.warn(`Error while parsing SFDX authinfo: ${err.message || err}`);
Expand All @@ -121,6 +130,7 @@ export namespace sfdx {
}

export async function getOrgDetails(usernameOrAlias: string) : Promise<SalesforceOrgInfo | undefined> {
const x = await getAllKnownOrgDetails();
for await (const config of getAllValidatedConfigs()) {
if (config.username?.toLowerCase() === usernameOrAlias?.toLowerCase() ||
config.alias?.toLowerCase() === usernameOrAlias?.toLowerCase()) {
Expand All @@ -129,33 +139,34 @@ export namespace sfdx {
}
}

export async function getOrg(usernameOrAlias: string) : Promise<salesforce.Org> {
const username = await resolveUsername(usernameOrAlias) ?? usernameOrAlias;

try {
const authInfo = await salesforce.AuthInfo.create({ username });
const connection = await salesforce.Connection.create({ authInfo });
const org = await salesforce.Org.create({ connection });
await org.refreshAuth();
return org;
} catch (err) {
if (err.name == 'NamedOrgNotFound') {
throw new Error(`The specified alias "${usernameOrAlias}" does not exists; resolve this error by register the alias using SFDX or try connecting using the username instead`)
}
throw err;
}
/**
* Update the currently stored access token for a username in the local SFDX configuration store
* @param usernameOrAlias Salesforce username or SFDX alias
* @param accessToken New access token to store for the specified username
*/
export async function updateAccessToken(usernameOrAlias: string, accessToken: string) : Promise<void> {
const stateAggregator = await salesforce.StateAggregator.getInstance();
const username = stateAggregator.aliases.get(usernameOrAlias) || usernameOrAlias;
stateAggregator.orgs.update(username, { accessToken });
await stateAggregator.orgs.write(username);
}

/**
* Resolves the username for an SFDX alias, if the specified {@link alias} cannot be mapped back to a valid Salesforce username returns `undefined`.
* @param alias SFDX Alias to resolve to a Salesforce username
*/
export async function resolveUsername(alias: string) : Promise<string | undefined> {
const stateAggregator = await salesforce.StateAggregator.getInstance();
return stateAggregator.aliases.resolveUsername(alias) || undefined;
return stateAggregator.aliases.resolveUsername(alias);
}

export async function resolveAlias(userName: string) : Promise<string | undefined> {
/**
* Returns the alias for a username, if no alias is set returns the username.
* @param userName username to resolve the alias for
*/
export async function resolveAlias(userName: string) : Promise<string> {
const stateAggregator = await salesforce.StateAggregator.getInstance();
userName = stateAggregator.aliases.resolveUsername(userName) || userName;
const aliases = stateAggregator.aliases.resolveAlias(userName);
return aliases || userName;
return stateAggregator.aliases.get(userName) || userName;
}

export async function updateAlias(alias: string, userName: string) : Promise<void> {
Expand All @@ -164,6 +175,10 @@ export namespace sfdx {
await stateAggregator.aliases.write();
}

/**
* @deprecated Use the appropriate Salesforce connection provider instead
* @param usernameOrAlias Username or SFDX alias for the username
*/
export async function getSfdxForceConnection(username: string) : Promise<salesforce.Connection> {
const org = await getOrg(username);
const connection = org.getConnection() as any;
Expand All @@ -172,4 +187,24 @@ export namespace sfdx {
}
return connection as salesforce.Connection;
}

/**
* @deprecated Use the appropriate Salesforce connection provider instead
* @param usernameOrAlias Username or SFDX alias for the username
*/
export async function getOrg(usernameOrAlias: string) : Promise<salesforce.Org> {
const username = await resolveUsername(usernameOrAlias) ?? usernameOrAlias;

try {
const authInfo = await salesforce.AuthInfo.create({ username });
const connection = await salesforce.Connection.create({ authInfo });
const org = await salesforce.Org.create({ connection });
return org;
} catch (err) {
if (err.name == 'NamedOrgNotFound') {
throw new Error(`The specified alias "${usernameOrAlias}" does not exists; resolve this error by register the alias using SFDX or try connecting using the username instead`)
}
throw err;
}
}
}
11 changes: 10 additions & 1 deletion packages/vscode-extension/src/commands/selectOrgCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,17 @@ export default class SelectOrgCommand extends CommandBase {
}

public async execute() : Promise<void> {
const selectionOptions = [
this.newOrgOption,
this.refreshTokensOption
];

const knownOrgs = await this.vlocode.withStatusBarProgress('Loading SFDX org details...', this.getAuthorizedOrgs());
const selectedOrg = await vscode.window.showQuickPick([this.newOrgOption, this.refreshTokensOption].concat(knownOrgs),
if (knownOrgs.length) {
selectionOptions.push({ label: '', kind: -1 }, ...knownOrgs)
}

const selectedOrg = await vscode.window.showQuickPick(selectionOptions,
{ placeHolder: 'Select an existing Salesforce org -or- authorize a new one' });

if (!selectedOrg) {
Expand Down

0 comments on commit b31a419

Please sign in to comment.