From 063bb0d84299d08822f342c7dde118c75c9aff0f Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 28 Jan 2026 11:41:03 -0600 Subject: [PATCH] feat: generated passwords default to 20 if unspecified or a lower value is provided @W-21055697@ --- messages/password.generate.md | 10 ++-- src/commands/force/user/password/generate.ts | 2 +- src/commands/org/generate/password.ts | 9 +++- test/commands/password/generate.test.ts | 49 ++++++++++++++++---- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/messages/password.generate.md b/messages/password.generate.md index 0309ec44..c509f084 100644 --- a/messages/password.generate.md +++ b/messages/password.generate.md @@ -25,9 +25,9 @@ To see a password that was previously generated, run "org display user". <%= config.bin %> <%= command.id %> -- Generate a password that contains 12 characters for the original admin user of the scratch org with alias "my-scratch": +- Generate a password that contains 25 characters for the original admin user of the scratch org with alias "my-scratch": - <%= config.bin %> <%= command.id %> --length 12 --target-org my-scratch + <%= config.bin %> <%= command.id %> --length 25 --target-org my-scratch - Generate a password for your default scratch org admin user that uses lower and upper case letters and numbers only: @@ -43,7 +43,7 @@ Comma-separated list of usernames or aliases to assign the password to; must hav # flags.length.summary -Number of characters in the generated password; valid values are between 8 and 100. +Number of characters in the generated password; valid values are between 20 and 100. Default value is 20. # flags.complexity.summary @@ -66,6 +66,10 @@ version 51.0 of the Metadata API. - "features": ["EnableSetPasswordInApi"] - Then try creating the scratch org again. +# defaultingToLength20Password + +Starting in Summer '26, passwords of length below 20 will be explicitly rejected. For now, generating a password of length 20 instead of the requested length. + # scratchFeaturesUrl see https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_scratch_orgs_def_file_config_values.htm diff --git a/src/commands/force/user/password/generate.ts b/src/commands/force/user/password/generate.ts index 99767aee..b42640ae 100644 --- a/src/commands/force/user/password/generate.ts +++ b/src/commands/force/user/password/generate.ts @@ -43,7 +43,7 @@ export class ForceUserPasswordGenerateCommand extends UserPasswordGenerateBaseCo summary: messages.getMessage('flags.length.summary'), min: 8, max: 1000, - default: 13, + default: 20, }), // the higher the value, the stronger the password complexity: Flags.integer({ diff --git a/src/commands/org/generate/password.ts b/src/commands/org/generate/password.ts index 74d84128..688dffd1 100644 --- a/src/commands/org/generate/password.ts +++ b/src/commands/org/generate/password.ts @@ -50,7 +50,7 @@ export class GenerateUserPasswordCommand extends UserPasswordGenerateBaseCommand summary: messages.getMessage('flags.length.summary'), min: 8, max: 1000, - default: 13, + default: 20, }), // the higher the value, the stronger the password complexity: Flags.integer({ @@ -66,9 +66,14 @@ export class GenerateUserPasswordCommand extends UserPasswordGenerateBaseCommand public async run(): Promise { const { flags } = await this.parse(GenerateUserPasswordCommand); + let length: number = flags.length; + if (length < 20) { + this.info(messages.getMessage('defaultingToLength20Password')); + length = 20; + } return this.generate({ usernames: ensureArray(flags['on-behalf-of'] ?? flags['target-org'].getUsername()), - length: flags.length, + length, complexity: flags.complexity, conn: flags['target-org'].getConnection(flags['api-version']), }); diff --git a/test/commands/password/generate.test.ts b/test/commands/password/generate.test.ts index 68cd3dab..160396a7 100644 --- a/test/commands/password/generate.test.ts +++ b/test/commands/password/generate.test.ts @@ -16,6 +16,7 @@ import { Connection, Messages, User } from '@salesforce/core'; import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; import { assert, expect } from 'chai'; // dirty import to stub something we don't want to export from sfdx-core import { SecureBuffer } from '../../../node_modules/@salesforce/core/lib/crypto/secureBuffer.js'; @@ -83,16 +84,44 @@ describe('org:generate:password', () => { expect(result).to.deep.equal(expected); expect(queryStub.callCount).to.equal(1); }); - it('should generate a new passsword of length 12', async () => { - await prepareStubs(false, false); - const result = (await GenerateUserPasswordCommand.run([ - '--target-org', - testOrg.username, - '-l', - '12', - '--json', - ])) as PasswordData; - expect(result.password.length).to.equal(12); + + describe('--length handling', () => { + it('when no length is specified, password should default to length 20', async () => { + await prepareStubs(false, false); + const result = (await GenerateUserPasswordCommand.run([ + '--target-org', + testOrg.username, + '--json', + ])) as PasswordData; + + expect(result.password.length).to.equal(20); + }); + + it('when length <20 is specified, logs info-level message and defaults to 20', async () => { + await prepareStubs(false, false); + const uxStubs = stubSfCommandUx($$.SANDBOX); + const result = (await GenerateUserPasswordCommand.run([ + '--target-org', + testOrg.username, + '--length', + '12', + '--json', + ])) as PasswordData; + expect(result.password.length).to.equal(20); + expect(uxStubs.info.args.flat()).to.include(messages.getMessage('defaultingToLength20Password')); + }); + + it('when length >20 is specified, length is used as-is', async () => { + await prepareStubs(false, false); + const result = (await GenerateUserPasswordCommand.run([ + '--target-org', + testOrg.username, + '--length', + '50', + '--json', + ])) as PasswordData; + expect(result.password.length).to.equal(50); + }); }); it('should throw the correct error with warning message', async () => { await prepareStubs(true);