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
4 changes: 4 additions & 0 deletions messages/web.login.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ Unable to open the browser you specified (%s).
# error.cannotOpenBrowser.actions

- Ensure that %s is installed on your computer. Or specify a different browser using the --browser flag.

# verificationCode

- Your verification code is %s. Enter this in the browser window that just opened. SECURITY NOTE: Enter this PIN only if you initiated this login.
17 changes: 17 additions & 0 deletions src/commands/org/login/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@
* limitations under the License.
*/

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 { Env } from '@salesforce/kit';
import common from '../../../common.js';

export const CODE_BUILDER_STATE_ENV_VAR = 'CODE_BUILDER_STATE';

export const getVerificationCode = (codeBuilderState: string): string => {
const hash = createHash('sha256').update(codeBuilderState, 'utf8').digest('hex');
return hash.substring(0, 4);
};

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'web.login');
const commonMessages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');
Expand Down Expand Up @@ -112,6 +120,15 @@ export default class LoginWeb extends SfCommand<AuthFields> {
throw new SfError(messages.getMessage('error.headlessWebAuth'));
}

// Display verification code for Code Builder mode if env is set
const env = new Env();
const codeBuilderState = env.getString(CODE_BUILDER_STATE_ENV_VAR);
if (codeBuilderState) {
const verificationCode = getVerificationCode(codeBuilderState);

this.log(messages.getMessage('verificationCode', [verificationCode]));
}

if (await common.shouldExitCommand(flags['no-prompt'])) return {};

// Add ca/eca to already existing auth info.
Expand Down
57 changes: 55 additions & 2 deletions test/commands/org/login/login.web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

import { Config } from '@oclif/core';
import { AuthFields, AuthInfo, SfError } from '@salesforce/core';
import { AuthFields, AuthInfo, SfError, Messages } from '@salesforce/core';
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup';
import { StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon';
import { assert, expect } from 'chai';
import { Env } from '@salesforce/kit';
import { SfCommand, Ux } from '@salesforce/sf-plugins-core';
import LoginWeb, { ExecuteLoginFlowParams } from '../../../../src/commands/org/login/web.js';
import LoginWeb, {
ExecuteLoginFlowParams,
CODE_BUILDER_STATE_ENV_VAR,
getVerificationCode,
} from '../../../../src/commands/org/login/web.js';

describe('org:login:web', () => {
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'web.login');
const $$ = new TestContext();
const testData = new MockTestOrgData();
const config = stubInterface<Config>($$.SANDBOX, {
Expand Down Expand Up @@ -302,4 +308,51 @@ describe('org:login:web', () => {
expect(callArgs.clientApp?.username).to.equal('test@example.com');
expect(callArgs.scopes).to.be.undefined;
});

it('should display verification code when CODE_BUILDER_STATE env var is set', async () => {
const codeBuilderState = 'CODE_BUILDER_STATE';
const envStub = $$.SANDBOX.stub(Env.prototype, 'getString');
envStub.withArgs(CODE_BUILDER_STATE_ENV_VAR).returns(codeBuilderState);
envStub.returns(''); // Default for other calls

$$.SANDBOX.stub(Env.prototype, 'getBoolean').returns(false); // Prevent container mode checks

const logStub = stubMethod($$.SANDBOX, SfCommand.prototype, 'log');

const login = await createNewLoginCommand([], false, undefined);
await login.run();

// Verify that log was called with the verification code message
const verificationCode = getVerificationCode(codeBuilderState);
const calls = logStub.getCalls();
const verificationCodeCall = calls.find(
(call) => typeof call.args[0] === 'string' && call.args[0].includes(verificationCode)
);
expect(verificationCodeCall).to.exist;
expect(verificationCode).to.match(/^[0-9a-f]{4}$/);
expect(verificationCodeCall?.args[0]).to.include(messages.getMessage('verificationCode', [verificationCode]));
});

it('should not display verification code when CODE_BUILDER_STATE env var is not set', async () => {
const envStub = stubMethod($$.SANDBOX, Env.prototype, 'getString');
envStub.withArgs('CODE_BUILDER_STATE').returns(undefined);
envStub.returns('');

$$.SANDBOX.stub(Env.prototype, 'getBoolean').returns(false); // Prevent container mode checks

const logStub = $$.SANDBOX.stub(SfCommand.prototype, 'log');
const logSuccessStub = $$.SANDBOX.stub(SfCommand.prototype, 'logSuccess');

const login = await createNewLoginCommand([], false, undefined);
await login.run();

// Verify that log was NOT called for verification code
expect(logStub.callCount).to.equal(0);
const calls = logSuccessStub.getCalls();
const verificationCodeCall = calls.find(
(call) => call.args[0]?.includes('verification code') || call.args[0]?.includes('Enter this')
);
expect(verificationCodeCall).to.not.exist;
expect(logSuccessStub.callCount).to.equal(1);
});
});