Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions messages/secrets-redacted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# redacted.accessToken

[REDACTED] Use 'sf org auth show-access-token' to view

# redacted.sfdxAuthUrl

[REDACTED] Use 'sf org auth show-sfdx-auth-url' to view

# temp.envVarWorkaround

Secrets are now hidden from '%s' command output. Use the 'sf org auth' commands instead. As a temporary workaround, you can set SF_TEMP_SHOW_SECRETS=true to render these secrets. This workaround will be removed in an upcoming release.

# temp.envVarIsSet

The SF_TEMP_SHOW_SECRETS env var is set. This is a temporary env var to continue to show secrets in the '%s' command output. This workaround will be removed in an upcoming CLI release. Switch to use the 'sf org auth' commands to avoid future disruption.
8 changes: 4 additions & 4 deletions messages/sfdxurl.store.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ Authorize an org using a Salesforce DX authorization URL stored in a file or thr

You use the Salesforce DX (SFDX) authorization URL to authorize Salesforce CLI to connect to a target org. The URL contains the required data to accomplish the authorization, such as the client ID, client secret, and instance URL. You must specify the SFDX authorization URL in this format: "%s". Replace <clientId>, <clientSecret>, <refreshToken>, and <instanceUrl> with the values specific to your target org. For <instanceUrl>, don't include a protocol (such as "https://"). Note that although the SFDX authorization URL starts with "force://", it has nothing to do with the actual authorization. Salesforce CLI always communicates with your org using HTTPS.

To see an example of an SFDX authorization URL, run "org display --verbose" on an org.
To see the SFDX authorization URL for an org, run "org auth show-sfdx-auth-url".

You have three options when creating the authorization file. The easiest option is to redirect the output of the "<%= config.bin %> org display --verbose --json" command into a file. For example, using an org with alias my-org that you've already authorized:
You have three options when creating the authorization file. The easiest option is to redirect the output of the "<%= config.bin %> org auth show-sfdx-auth-url --json" command into a file. For example, using an org with alias my-org that you've already authorized:

$ <%= config.bin %> org display --target-org my-org --verbose --json > authFile.json
$ <%= config.bin %> org auth show-sfdx-auth-url --target-org my-org --json > authFile.json

The resulting JSON file contains the URL in the "sfdxAuthUrl" property of the "result" object. You can then reference the file when running this command:

$ <%= config.bin %> <%= command.id %> --sfdx-url-file authFile.json

NOTE: The "<%= config.bin %> org display --verbose" command displays the refresh token only for orgs authorized with the web server flow, and not the JWT bearer flow.
NOTE: The SFDX auth URL is only available for orgs authorized with a web-based OAuth flow, and not the JWT bearer flow.

You can also create a JSON file that has a top-level property named sfdxAuthUrl whose value is the authorization URL. Finally, you can create a normal text file that includes just the URL and nothing else.

Expand Down
22 changes: 20 additions & 2 deletions src/commands/org/list/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
*/

import { loglevel, SfCommand } from '@salesforce/sf-plugins-core';
import { AuthInfo, Messages, OrgAuthorization } from '@salesforce/core';
import { AuthInfo, envVars, Messages, OrgAuthorization } from '@salesforce/core';
type AuthListResult = Omit<OrgAuthorization, 'aliases'> & { alias: string };
export type AuthListResults = AuthListResult[];
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'list');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-auth', 'secrets-redacted');

export default class ListAuth extends SfCommand<AuthListResults> {
public static readonly summary = messages.getMessage('summary');
Expand All @@ -40,10 +41,18 @@ export default class ListAuth extends SfCommand<AuthListResults> {
this.log(messages.getMessage('noResultsFound'));
return [];
}
// TODO: Remove env var workaround
const showSecretsEnvVarIsSet = envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false);
const accessTokenRedacted = secretsMessages.getMessage('redacted.accessToken');

const mappedAuths: AuthListResults = auths.map((auth: OrgAuthorization) => {
const { aliases, ...rest } = auth;
// core3 moved to aliases as a string[], revert to alias as a string
return { ...rest, alias: aliases ? aliases.join(',') : '' };
return {
...rest,
alias: aliases ? aliases.join(',') : '',
accessToken: rest.accessToken ? (showSecretsEnvVarIsSet ? rest.accessToken : accessTokenRedacted) : undefined,
};
});

const hasErrors = auths.filter((auth) => !!auth.error).length > 0;
Expand All @@ -58,6 +67,15 @@ export default class ListAuth extends SfCommand<AuthListResults> {
})),
title: 'authenticated orgs',
});
// TODO: Remove after env var workaround is removed
if (this.jsonEnabled()) {
if (showSecretsEnvVarIsSet) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org list auth']));
} else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org list auth']));
}
}

return mappedAuths;
} catch (err) {
this.log(messages.getMessage('noResultsFound'));
Expand Down
26 changes: 23 additions & 3 deletions src/commands/org/login/access-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@
*/

import { Flags, loglevel, SfCommand } from '@salesforce/sf-plugins-core';
import { AuthFields, AuthInfo, Messages, matchesAccessToken, SfError, StateAggregator } from '@salesforce/core';
import {
AuthFields,
AuthInfo,
envVars,
Messages,
matchesAccessToken,
SfError,
StateAggregator,
} from '@salesforce/core';
import { env } from '@salesforce/kit';
import { InferredFlags } from '@oclif/core/interfaces';
import common from '../../../common.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'accesstoken.store');
const commonMessages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-auth', 'secrets-redacted');

const ACCESS_TOKEN_FORMAT = '"<org id>!<accesstoken>"';

Expand Down Expand Up @@ -94,11 +104,21 @@ export default class LoginAccessToken extends SfCommand<AuthFields> {
await this.saveAuthInfo(authInfo);
const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [
authInfo.getUsername(),
authInfo.getFields(true).orgId,
authInfo.getFields().orgId,
]);
this.logSuccess(successMsg);
}
return authInfo.getFields(true);

// TODO: Remove env var workaround
if (this.jsonEnabled()) {
if (envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false)) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org login access-token']));
} else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org login access-token']));
}
}

return common.redactAuthFields(authInfo.getFields(true));
}

private async saveAuthInfo(authInfo: AuthInfo): Promise<void> {
Expand Down
15 changes: 13 additions & 2 deletions src/commands/org/login/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
*/

import { Flags, SfCommand, loglevel } from '@salesforce/sf-plugins-core';
import { AuthFields, AuthInfo, AuthRemover, Logger, Messages, SfError } from '@salesforce/core';
import { AuthFields, AuthInfo, AuthRemover, envVars, Logger, Messages, SfError } from '@salesforce/core';
import { Interfaces } from '@oclif/core';
import common from '../../../common.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'jwt.grant');
const commonMessages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-auth', 'secrets-redacted');

export default class LoginJwt extends SfCommand<AuthFields> {
public static readonly summary = messages.getMessage('summary');
Expand Down Expand Up @@ -118,7 +119,17 @@ export default class LoginJwt extends SfCommand<AuthFields> {

const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [result.username, result.orgId]);
this.logSuccess(successMsg);
return result;

// TODO: Remove env var workaround
if (this.jsonEnabled()) {
if (envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false)) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org login jwt']));
} else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org login jwt']));
}
}

return common.redactAuthFields(result);
}

private async initAuthInfo(): Promise<AuthInfo> {
Expand Down
18 changes: 14 additions & 4 deletions src/commands/org/login/sfdx-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

import fs from 'node:fs/promises';
import { Flags, SfCommand, loglevel } from '@salesforce/sf-plugins-core';
import { AuthFields, AuthInfo, Messages } from '@salesforce/core';
import { AuthFields, AuthInfo, envVars, Messages } from '@salesforce/core';
import { AnyJson } from '@salesforce/ts-types';
import { parseJson } from '@salesforce/kit';
import common from '../../../common.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'sfdxurl.store');
const commonMessages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-auth', 'secrets-redacted');

const AUTH_URL_FORMAT = 'force://<clientId>:<clientSecret>:<refreshToken>@<instanceUrl>';

Expand Down Expand Up @@ -118,13 +119,22 @@ export default class LoginSfdxUrl extends SfCommand<AuthFields> {
setDefaultDevHub: flags['set-default-dev-hub'],
});

// ensure the clientSecret field... even if it is empty
const result = { clientSecret: '', ...authInfo.getFields(true) };
const result = authInfo.getFields(true);
await AuthInfo.identifyPossibleScratchOrgs(result, authInfo);

const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [result.username, result.orgId]);
this.logSuccess(successMsg);
return result;

// TODO: Remove env var workaround
if (this.jsonEnabled()) {
if (envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false)) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org login sfdx-url']));
} else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org login sfdx-url']));
}
}

return common.redactAuthFields(result);
}
}

Expand Down
41 changes: 35 additions & 6 deletions src/commands/org/login/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,23 @@
import { createHash } from 'node:crypto';
import open, { apps, AppName } from 'open';
import { Flags, SfCommand, loglevel } from '@salesforce/sf-plugins-core';
import { AuthFields, AuthInfo, Logger, Messages, OAuth2Config, SfError, WebOAuthServer } from '@salesforce/core';
import {
AuthFields,
AuthInfo,
envVars,
Logger,
Messages,
OAuth2Config,
SfError,
WebOAuthServer,
} from '@salesforce/core';
import { Env } from '@salesforce/kit';
import common from '../../../common.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'web.login');
const commonMessages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-auth', 'secrets-redacted');

export const CODE_BUILDER_STATE_ENV_VAR = 'CODE_BUILDER_STATE';

Expand Down Expand Up @@ -132,17 +142,16 @@ export default class LoginWeb extends SfCommand<AuthFields> {
if (await common.shouldExitCommand(flags['no-prompt'])) return {};

// Add ca/eca to already existing auth info.
// TODO: deprecate Client App login until use-case arises.
if (flags['client-app'] && flags.username) {
// 1. get username authinfo
const userAuthInfo = await AuthInfo.create({
username: flags.username,
});

const authFields = userAuthInfo.getFields(true);

// 2. web-auth and save name, clientId, accessToken, and refreshToken in `apps` object
const oauthConfig: OAuth2Config = {
loginUrl: authFields.loginUrl,
loginUrl: userAuthInfo.getFields().loginUrl,
clientId: flags['client-id'],
...{ clientSecret: await this.secretPrompt({ message: commonMessages.getMessage('clientSecretStdin') }) },
};
Expand All @@ -155,7 +164,17 @@ export default class LoginWeb extends SfCommand<AuthFields> {
});

this.logSuccess(messages.getMessage('linkedClientApp', [flags['client-app'], flags.username]));
return userAuthInfo.getFields(true);

// TODO: Remove env var workaround
if (this.jsonEnabled()) {
if (envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false)) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org login web']));
} else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org login web']));
}
}

return common.redactAuthFields(userAuthInfo.getFields(true));
}

const oauthConfig: OAuth2Config = {
Expand Down Expand Up @@ -183,7 +202,17 @@ export default class LoginWeb extends SfCommand<AuthFields> {

const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [fields.username, fields.orgId]);
this.logSuccess(successMsg);
return fields;

// TODO: Remove env var workaround
if (this.jsonEnabled()) {
if (envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false)) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org login web']));
} else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org login web']));
}
}

return common.redactAuthFields(fields);
} catch (err) {
Logger.childFromRoot('LoginWebCommand').debug(err);
if (err instanceof SfError && err.name === 'AuthCodeExchangeError') {
Expand Down
19 changes: 18 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
* limitations under the License.
*/

import { Logger, SfdcUrl, SfProject, Messages, SfError, Global, Mode } from '@salesforce/core';
import { AuthFields, envVars, Logger, SfdcUrl, SfProject, Messages, SfError, Global, Mode } from '@salesforce/core';
import { getString, isObject } from '@salesforce/ts-types';
import { prompts, StandardColors } from '@salesforce/sf-plugins-core';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-auth', 'secrets-redacted');

const resolveLoginUrl = async (instanceUrl?: string): Promise<string> => {
const logger = await Logger.child('Common', { tag: 'resolveLoginUrl' });
Expand Down Expand Up @@ -55,7 +56,23 @@ const shouldExitCommand = async (noPrompt?: boolean): Promise<boolean> =>
? false
: !(await prompts.confirm({ message: StandardColors.info(messages.getMessage('warnAuth', ['sf'])), ms: 60_000 }));

// TODO: Remove env var workaround
const redactAuthFields = (fields: AuthFields): AuthFields => {
const showSecretsEnvVarIsSet = envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false);
if (showSecretsEnvVarIsSet) return fields;

const redactedAccessToken = secretsMessages.getMessage('redacted.accessToken');
return {
...fields,
accessToken: fields.accessToken ? redactedAccessToken : undefined,
refreshToken: undefined,
clientSecret: undefined,
password: undefined,
};
};

export default {
shouldExitCommand,
resolveLoginUrl,
redactAuthFields,
};
4 changes: 2 additions & 2 deletions test/commands/org/list/auth.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
import { expect } from 'chai';
import { Env } from '@salesforce/kit';
import { ensureString, getString } from '@salesforce/ts-types';
import { expectUrlToExist, expectOrgIdToExist, expectAccessTokenToExist } from '../../../testHelper.js';
import { expectUrlToExist, expectOrgIdToExist } from '../../../testHelper.js';
import { AuthListResults } from '../../../../src/commands/org/list/auth.js';

describe('org:list:auth NUTs', () => {
Expand All @@ -43,7 +43,7 @@ describe('org:list:auth NUTs', () => {
const json = execCmd<AuthListResults>('org:list:auth --json', { ensureExitCode: 0 }).jsonOutput
?.result as AuthListResults;
const auth = json[0];
expectAccessTokenToExist(auth);
expect(auth.accessToken).to.include('[REDACTED]');
expectOrgIdToExist(auth);
expectUrlToExist(auth, 'instanceUrl');
expect(auth.username).to.equal(username);
Expand Down
Loading
Loading