From f066245a3a7a83513786395536ad27fa6177e486 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 19 May 2026 12:59:14 -0500 Subject: [PATCH 1/4] feat: remove sensitive output --- messages/secrets-redacted.md | 15 +++++++ messages/sfdxurl.store.md | 8 ++-- src/commands/org/list/auth.ts | 22 +++++++++- src/commands/org/login/access-token.ts | 24 ++++++++++- src/commands/org/login/jwt.ts | 15 ++++++- src/commands/org/login/sfdx-url.ts | 18 ++++++-- src/commands/org/login/web.ts | 36 ++++++++++++++-- src/common.ts | 19 ++++++++- test/commands/org/login/access-token.test.ts | 36 +++++++++++++--- test/commands/org/login/login.web.test.ts | 44 +++++++++++++++++--- 10 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 messages/secrets-redacted.md diff --git a/messages/secrets-redacted.md b/messages/secrets-redacted.md new file mode 100644 index 00000000..69160de4 --- /dev/null +++ b/messages/secrets-redacted.md @@ -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. diff --git a/messages/sfdxurl.store.md b/messages/sfdxurl.store.md index eaf00067..ff79cece 100644 --- a/messages/sfdxurl.store.md +++ b/messages/sfdxurl.store.md @@ -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 , , , and with the values specific to your target org. For , 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. diff --git a/src/commands/org/list/auth.ts b/src/commands/org/list/auth.ts index ce074864..b158094f 100644 --- a/src/commands/org/list/auth.ts +++ b/src/commands/org/list/auth.ts @@ -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 & { 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 { public static readonly summary = messages.getMessage('summary'); @@ -40,10 +41,18 @@ export default class ListAuth extends SfCommand { 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; @@ -58,6 +67,15 @@ export default class ListAuth extends SfCommand { })), 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')); diff --git a/src/commands/org/login/access-token.ts b/src/commands/org/login/access-token.ts index 3207354a..0b16a951 100644 --- a/src/commands/org/login/access-token.ts +++ b/src/commands/org/login/access-token.ts @@ -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 = '"!"'; @@ -98,7 +108,17 @@ export default class LoginAccessToken extends SfCommand { ]); 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 { diff --git a/src/commands/org/login/jwt.ts b/src/commands/org/login/jwt.ts index 5fed5ab8..a4c41286 100644 --- a/src/commands/org/login/jwt.ts +++ b/src/commands/org/login/jwt.ts @@ -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 { public static readonly summary = messages.getMessage('summary'); @@ -118,7 +119,17 @@ export default class LoginJwt extends SfCommand { 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 { diff --git a/src/commands/org/login/sfdx-url.ts b/src/commands/org/login/sfdx-url.ts index 546b109c..cecf7e71 100644 --- a/src/commands/org/login/sfdx-url.ts +++ b/src/commands/org/login/sfdx-url.ts @@ -16,7 +16,7 @@ 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'; @@ -24,6 +24,7 @@ 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://::@'; @@ -118,13 +119,22 @@ export default class LoginSfdxUrl extends SfCommand { 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); } } diff --git a/src/commands/org/login/web.ts b/src/commands/org/login/web.ts index f09cda08..ce2830b6 100644 --- a/src/commands/org/login/web.ts +++ b/src/commands/org/login/web.ts @@ -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'; @@ -155,7 +165,17 @@ export default class LoginWeb extends SfCommand { }); 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 = { @@ -183,7 +203,17 @@ export default class LoginWeb extends SfCommand { 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') { diff --git a/src/common.ts b/src/common.ts index 01579c1d..9e349143 100644 --- a/src/common.ts +++ b/src/common.ts @@ -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 => { const logger = await Logger.child('Common', { tag: 'resolveLoginUrl' }); @@ -55,7 +56,23 @@ const shouldExitCommand = async (noPrompt?: boolean): Promise => ? 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, }; diff --git a/test/commands/org/login/access-token.test.ts b/test/commands/org/login/access-token.test.ts index 539fea84..778f218c 100644 --- a/test/commands/org/login/access-token.test.ts +++ b/test/commands/org/login/access-token.test.ts @@ -32,6 +32,14 @@ describe('org:login:access-token', () => { username: 'foo@baz.org', } as const satisfies AuthFields; + const redactedAuthFields = { + ...authFields, + accessToken: "[REDACTED] Use 'sf org auth show-access-token' to view", + refreshToken: undefined, + clientSecret: undefined, + password: undefined, + }; + /* eslint-disable camelcase */ const userInfo = { preferred_username: 'foo@baz.org', @@ -65,7 +73,7 @@ describe('org:login:access-token', () => { const result = await Store.run(['--instance-url', 'https://foo.bar.org.salesforce.com']); expect(prompterStubs.secret.callCount).to.equal(1); expect(stubSfCommandUxStubs.logSuccess.callCount).to.equal(1); - expect(result).to.deep.equal(authFields); + expect(result).to.deep.equal(redactedAuthFields); }); it('should show invalid access token provided as input', async () => { @@ -91,7 +99,7 @@ describe('org:login:access-token', () => { }, }); const result = await Store.run(['--instance-url', 'https://foo.bar.org.salesforce.com']); - expect(result).to.deep.equal(authFields); + expect(result).to.deep.equal(redactedAuthFields); expect(prompterStubs.secret.callCount).to.equal(1); expect(prompterStubs.confirm.callCount).to.equal(1); }); @@ -105,7 +113,7 @@ describe('org:login:access-token', () => { }, }); const result = await Store.run(['--instance-url', 'https://foo.bar.org.salesforce.com']); - expect(result).to.deep.equal(authFields); + expect(result).to.deep.equal(redactedAuthFields); expect(prompterStubs.confirm.callCount).to.equal(0); }); @@ -118,7 +126,7 @@ describe('org:login:access-token', () => { .returns(undefined); const result = await Store.run(['--instance-url', 'https://foo.bar.org.salesforce.com']); - expect(result).to.deep.equal(authFields); + expect(result).to.deep.equal(redactedAuthFields); // no prompts needed when using Env expect(prompterStubs.confirm.callCount).to.equal(0); expect(prompterStubs.secret.callCount).to.equal(0); @@ -133,9 +141,27 @@ describe('org:login:access-token', () => { .returns(undefined); const result = await Store.run(['--instance-url', 'https://foo.bar.org.salesforce.com']); - expect(result).to.deep.equal(authFields); + expect(result).to.deep.equal(redactedAuthFields); // no prompts needed when using Env expect(prompterStubs.confirm.callCount).to.equal(0); expect(prompterStubs.secret.callCount).to.equal(0); }); + + describe('secret redaction WITH env var (SF_TEMP_SHOW_SECRETS)', () => { + const SHOW_TOKENS_ENV = 'SF_TEMP_SHOW_SECRETS'; + + beforeEach(() => { + process.env[SHOW_TOKENS_ENV] = 'true'; + $$.SANDBOX.stub(env, 'getString').withArgs('SF_ACCESS_TOKEN').returns(accessToken); + }); + + afterEach(() => { + delete process.env[SHOW_TOKENS_ENV]; + }); + + it('shows real auth fields when env var is set', async () => { + const result = await Store.run(['--instance-url', 'https://foo.bar.org.salesforce.com', '--no-prompt']); + expect(result.accessToken).to.equal(accessToken); + }); + }); }); diff --git a/test/commands/org/login/login.web.test.ts b/test/commands/org/login/login.web.test.ts index fda263d1..11964131 100644 --- a/test/commands/org/login/login.web.test.ts +++ b/test/commands/org/login/login.web.test.ts @@ -90,13 +90,19 @@ describe('org:login:web', () => { it('should return auth fields after successful auth', async () => { const login = await createNewLoginCommand([], false, undefined); const result = await login.run(); - expect(result).to.deep.equal(authFields); + expect(result.accessToken).to.include('[REDACTED]'); + expect(result.refreshToken).to.be.undefined; + expect(result.clientSecret).to.be.undefined; + expect(result.username).to.equal(authFields.username); }); it('should set alias', async () => { const login = await createNewLoginCommand(['--alias', 'MyAlias'], false, undefined); const result = await login.run(); - expect(result).to.deep.equal(authFields); + expect(result.accessToken).to.include('[REDACTED]'); + expect(result.refreshToken).to.be.undefined; + expect(result.clientSecret).to.be.undefined; + expect(result.username).to.equal(authFields.username); expect(authInfoStub.handleAliasAndDefaultSettings.args[0]).to.deep.equal([ { alias: 'MyAlias', @@ -109,14 +115,20 @@ describe('org:login:web', () => { it('should set target-org', async () => { const login = await createNewLoginCommand(['--set-default'], false, undefined); const result = await login.run(); - expect(result).to.deep.equal(authFields); + expect(result.accessToken).to.include('[REDACTED]'); + expect(result.refreshToken).to.be.undefined; + expect(result.clientSecret).to.be.undefined; + expect(result.username).to.equal(authFields.username); expect(authInfoStub.handleAliasAndDefaultSettings.callCount).to.equal(1); }); it('should set target-dev-hub', async () => { const login = await createNewLoginCommand(['--set-default-dev-hub'], false, undefined); const result = await login.run(); - expect(result).to.deep.equal(authFields); + expect(result.accessToken).to.include('[REDACTED]'); + expect(result.refreshToken).to.be.undefined; + expect(result.clientSecret).to.be.undefined; + expect(result.username).to.equal(authFields.username); expect(authInfoStub.handleAliasAndDefaultSettings.callCount).to.equal(1); }); @@ -147,7 +159,10 @@ describe('org:login:web', () => { stubMethod($$.SANDBOX, Env.prototype, 'getBoolean').withArgs('CODE_BUILDER').returns(true); const login = await createNewLoginCommand([], false, undefined); const result = await login.run(); - expect(result).to.deep.equal(authFields); + expect(result.accessToken).to.include('[REDACTED]'); + expect(result.refreshToken).to.be.undefined; + expect(result.clientSecret).to.be.undefined; + expect(result.username).to.equal(authFields.username); }); it('should prompt for client secret when clientid is present', async () => { @@ -355,4 +370,23 @@ describe('org:login:web', () => { expect(verificationCodeCall).to.not.exist; expect(logSuccessStub.callCount).to.equal(1); }); + + describe('secret redaction WITH env var (SF_TEMP_SHOW_SECRETS)', () => { + const SHOW_TOKENS_ENV = 'SF_TEMP_SHOW_SECRETS'; + + beforeEach(() => { + process.env[SHOW_TOKENS_ENV] = 'true'; + }); + + afterEach(() => { + delete process.env[SHOW_TOKENS_ENV]; + }); + + it('shows real auth fields when env var is set', async () => { + const login = await createNewLoginCommand([], false, undefined); + const result = await login.run(); + expect(result.accessToken).to.equal(authFields.accessToken); + expect(result.username).to.equal(authFields.username); + }); + }); }); From 4a7802f8a0f8b52042d54a0ffca3025432911528 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 19 May 2026 13:16:59 -0500 Subject: [PATCH 2/4] chore: dont decrypt --- src/commands/org/login/access-token.ts | 2 +- src/commands/org/login/web.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/commands/org/login/access-token.ts b/src/commands/org/login/access-token.ts index 0b16a951..bd1083cf 100644 --- a/src/commands/org/login/access-token.ts +++ b/src/commands/org/login/access-token.ts @@ -104,7 +104,7 @@ export default class LoginAccessToken extends SfCommand { await this.saveAuthInfo(authInfo); const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [ authInfo.getUsername(), - authInfo.getFields(true).orgId, + authInfo.getFields().orgId, ]); this.logSuccess(successMsg); } diff --git a/src/commands/org/login/web.ts b/src/commands/org/login/web.ts index ce2830b6..1055dbf2 100644 --- a/src/commands/org/login/web.ts +++ b/src/commands/org/login/web.ts @@ -142,17 +142,16 @@ export default class LoginWeb extends SfCommand { 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') }) }, }; From a7e14f3edaf4f5424823b53ca9fa13279ee87782 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 19 May 2026 17:11:30 -0500 Subject: [PATCH 3/4] chore: nuts --- test/commands/org/list/auth.nut.ts | 4 ++-- test/commands/org/login/access-token.nut.ts | 18 ++++++++++-------- test/commands/org/login/login.jwt.nut.ts | 6 +++--- test/commands/org/login/login.sfdx-url.nut.ts | 11 +++-------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/test/commands/org/list/auth.nut.ts b/test/commands/org/list/auth.nut.ts index 6d18db0b..7c36a9dc 100644 --- a/test/commands/org/list/auth.nut.ts +++ b/test/commands/org/list/auth.nut.ts @@ -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', () => { @@ -43,7 +43,7 @@ describe('org:list:auth NUTs', () => { const json = execCmd('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); diff --git a/test/commands/org/login/access-token.nut.ts b/test/commands/org/login/access-token.nut.ts index fb2371ac..a09842bd 100644 --- a/test/commands/org/login/access-token.nut.ts +++ b/test/commands/org/login/access-token.nut.ts @@ -18,7 +18,7 @@ import { expect } from 'chai'; import { Env } from '@salesforce/kit'; import { ensureString, getString } from '@salesforce/ts-types'; import { AuthFields } from '@salesforce/core'; -import { expectAccessTokenToExist, expectOrgIdToExist, expectUrlToExist } from '../../../testHelper.js'; +import { expectOrgIdToExist, expectUrlToExist } from '../../../testHelper.js'; let testSession: TestSession; @@ -36,18 +36,21 @@ describe('org:login:access-token NUTs', () => { ensureString(env.getString('TESTKIT_JWT_KEY')); testSession = await TestSession.create(); const jwtKeyFilePath = prepareForJwt(testSession.homeDir); - const res = execCmd<{ accessToken: string }>( + execCmd( `org:login:jwt -f ${jwtKeyFilePath} -i ${clientId} -o ${username} --set-default-dev-hub --instance-url ${instanceUrl} --json`, - { - ensureExitCode: 0, - } + { ensureExitCode: 0 } ); - accessToken = res.jsonOutput?.result.accessToken as string; + // Get the real access token using the dedicated show command + const tokenRes = execCmd<{ accessToken: string }>(`org:auth:show-access-token -o ${username} --json`, { + ensureExitCode: 0, + }); + accessToken = tokenRes.jsonOutput?.result.accessToken as string; env.setString('SF_ACCESS_TOKEN', accessToken); execCmd(`auth:logout -p -o ${username}`, { ensureExitCode: 0 }); }); after(async () => { + delete process.env.SF_ACCESS_TOKEN; await testSession?.clean(); }); @@ -60,12 +63,11 @@ describe('org:login:access-token NUTs', () => { const cmdresult = execCmd(command, { ensureExitCode: 0 }); const json = cmdresult.jsonOutput?.result as AuthFields; - expectAccessTokenToExist(json); + expect(json.accessToken).to.include('[REDACTED]'); expectOrgIdToExist(json); expectUrlToExist(json, 'instanceUrl'); expectUrlToExist(json, 'loginUrl'); expect(json.username).to.equal(username); - expect(json.accessToken).to.equal(accessToken); }); it('should authorize an org using access token (human readable)', () => { diff --git a/test/commands/org/login/login.jwt.nut.ts b/test/commands/org/login/login.jwt.nut.ts index cbb0b6be..84d219b6 100644 --- a/test/commands/org/login/login.jwt.nut.ts +++ b/test/commands/org/login/login.jwt.nut.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as path from 'node:path'; +import path from 'node:path'; import { execCmd, TestSession, prepareForJwt } from '@salesforce/cli-plugins-testkit'; import { expect } from 'chai'; import { Env } from '@salesforce/kit'; import { ensureString, getString } from '@salesforce/ts-types'; import { AuthFields } from '@salesforce/core'; -import { expectUrlToExist, expectOrgIdToExist, expectAccessTokenToExist } from '../../../testHelper.js'; +import { expectUrlToExist, expectOrgIdToExist } from '../../../testHelper.js'; let testSession: TestSession; @@ -47,7 +47,7 @@ describe('org:login:jwt NUTs', () => { it('should authorize an org using jwt (json)', () => { const command = `org:login:jwt -d -o ${username} -i ${clientId} -f ${jwtKey} -r ${instanceUrl} --json`; const json = execCmd(command, { ensureExitCode: 0 }).jsonOutput?.result as AuthFields; - expectAccessTokenToExist(json); + expect(json.accessToken).to.include('[REDACTED]'); expectOrgIdToExist(json); expectUrlToExist(json, 'instanceUrl'); expectUrlToExist(json, 'loginUrl'); diff --git a/test/commands/org/login/login.sfdx-url.nut.ts b/test/commands/org/login/login.sfdx-url.nut.ts index e978b760..cd617a28 100644 --- a/test/commands/org/login/login.sfdx-url.nut.ts +++ b/test/commands/org/login/login.sfdx-url.nut.ts @@ -18,12 +18,7 @@ import { expect } from 'chai'; import { Env } from '@salesforce/kit'; import { ensureString, getString } from '@salesforce/ts-types'; import { AuthFields } from '@salesforce/core'; -import { - expectAccessTokenToExist, - expectOrgIdToExist, - expectPropsToExist, - expectUrlToExist, -} from '../../../testHelper.js'; +import { expectOrgIdToExist, expectUrlToExist } from '../../../testHelper.js'; let testSession: TestSession; @@ -51,8 +46,8 @@ describe('org:login:sfdx-url NUTs', () => { const command = `org:login:sfdx-url -d -f ${authUrl} --json`; const json = execCmd(command, { ensureExitCode: 0 }).jsonOutput?.result as AuthFields; - expectPropsToExist(json, 'refreshToken'); - expectAccessTokenToExist(json); + expect(json.accessToken).to.include('[REDACTED]'); + expect(json.refreshToken).to.be.undefined; expectOrgIdToExist(json); expectUrlToExist(json, 'instanceUrl'); expectUrlToExist(json, 'loginUrl'); From cb06317e19a81d0ffe932da6da92387b73e1116a Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Tue, 19 May 2026 17:32:07 -0500 Subject: [PATCH 4/4] chore: use authinfo --- test/commands/org/login/access-token.nut.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/commands/org/login/access-token.nut.ts b/test/commands/org/login/access-token.nut.ts index a09842bd..00c81483 100644 --- a/test/commands/org/login/access-token.nut.ts +++ b/test/commands/org/login/access-token.nut.ts @@ -17,7 +17,7 @@ import { execCmd, TestSession, prepareForJwt } from '@salesforce/cli-plugins-tes import { expect } from 'chai'; import { Env } from '@salesforce/kit'; import { ensureString, getString } from '@salesforce/ts-types'; -import { AuthFields } from '@salesforce/core'; +import { AuthFields, AuthInfo } from '@salesforce/core'; import { expectOrgIdToExist, expectUrlToExist } from '../../../testHelper.js'; let testSession: TestSession; @@ -40,11 +40,9 @@ describe('org:login:access-token NUTs', () => { `org:login:jwt -f ${jwtKeyFilePath} -i ${clientId} -o ${username} --set-default-dev-hub --instance-url ${instanceUrl} --json`, { ensureExitCode: 0 } ); - // Get the real access token using the dedicated show command - const tokenRes = execCmd<{ accessToken: string }>(`org:auth:show-access-token -o ${username} --json`, { - ensureExitCode: 0, - }); - accessToken = tokenRes.jsonOutput?.result.accessToken as string; + // Get the real access token from the auth file directly + const authInfo = await AuthInfo.create({ username }); + accessToken = authInfo.getFields(true).accessToken!; env.setString('SF_ACCESS_TOKEN', accessToken); execCmd(`auth:logout -p -o ${username}`, { ensureExitCode: 0 }); });