Skip to content
Open
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: 2 additions & 13 deletions messages/password.generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ By default, new scratch orgs contain one admin user with no password. Use this c

You can also use the --on-behalf-of flag to generate a password for a scratch org user that you've created locally with the "org create user" command. This command doesn't work for users you created in the scratch org using Setup.

To change the password strength, set the --complexity flag to a value between 0 and 5. Each value specifies the types of characters used in the generated password:
To change the password strength, set the --complexity flag to a value between 3 and 5. Each value specifies the types of characters used in the generated password:

0 - lower case letters only
1 - lower case letters and numbers only
2 - lower case letters and symbols only
3 - lower and upper case letters and numbers only
4 - lower and upper case letters and symbols only
5 - lower and upper case letters and numbers and symbols only
Expand Down Expand Up @@ -43,7 +40,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 20 and 100. Default value is 20.
Number of characters in the generated password; valid values are between 20 and 1000. Default value is 20.

# flags.complexity.summary

Expand All @@ -66,14 +63,6 @@ version 51.0 of the Metadata API.
- "features": ["EnableSetPasswordInApi"]
- Then try creating the scratch org again.

# defaultingToLength20Password

Starting in Summer '26, this command will fail if you specify a password length below 20. For now, the command is generating a password of length 20 instead of the requested length.

# defaultingToComplexity3Password

Starting in Summer '26, this command will fail if you specify a password complexity below 3. For now, the command is generating a password of complexity 3 instead of the requested complexity.

# scratchFeaturesUrl

see https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_scratch_orgs_def_file_config_values.htm
Expand Down
4 changes: 2 additions & 2 deletions src/commands/force/user/password/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ export class ForceUserPasswordGenerateCommand extends UserPasswordGenerateBaseCo
length: Flags.integer({
char: 'l',
summary: messages.getMessage('flags.length.summary'),
min: 8,
min: 20,
max: 1000,
default: 20,
}),
// the higher the value, the stronger the password
complexity: Flags.integer({
char: 'c',
summary: messages.getMessage('flags.complexity.summary'),
min: 0,
min: 3,
max: 5,
default: 5,
}),
Expand Down
15 changes: 3 additions & 12 deletions src/commands/org/generate/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ export class GenerateUserPasswordCommand extends UserPasswordGenerateBaseCommand
length: Flags.integer({
char: 'l',
summary: messages.getMessage('flags.length.summary'),
min: 8,
min: 20,
max: 1000,
default: 20,
}),
// the higher the value, the stronger the password
complexity: Flags.integer({
char: 'c',
summary: messages.getMessage('flags.complexity.summary'),
min: 0,
min: 3,
max: 5,
default: 5,
}),
Expand All @@ -66,16 +66,7 @@ export class GenerateUserPasswordCommand extends UserPasswordGenerateBaseCommand

public async run(): Promise<GenerateResult> {
const { flags } = await this.parse(GenerateUserPasswordCommand);
let length: number = flags.length;
if (length < 20) {
this.warn(messages.getMessage('defaultingToLength20Password'));
length = 20;
}
let complexity: number = flags.complexity;
if (complexity < 3) {
this.warn(messages.getMessage('defaultingToComplexity3Password'));
complexity = 3;
}
const { length, complexity } = flags;
return this.generate({
usernames: ensureArray(flags['on-behalf-of'] ?? flags['target-org'].getUsername()),
length,
Expand Down
13 changes: 6 additions & 7 deletions test/allCommands.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ describe('verifies all commands run successfully ', () => {
);
});

it('generates new passwords for main user testing length 11 and complexity 5', () => {
const output = execCmd<{ username: string; password: string }>('org:generate:password --json -l 11 -c 5', {
it('generates new passwords for main user testing length 20 and complexity 5', () => {
const output = execCmd<{ username: string; password: string }>('org:generate:password --json -l 20 -c 5', {
ensureExitCode: 0,
}).jsonOutput?.result;
// Password length gets overridden to 20
Expand Down Expand Up @@ -188,13 +188,12 @@ describe('verifies all commands run successfully ', () => {
expect(output).to.have.property('result').includes.keys(['username', 'password']);
});

it('generates new password for secondary user (onbehalfof) with length 12', () => {
const output = execCmd<{ username: string; password: string }>('org:generate:password -b Other --json -l 12', {
it('generates new password for secondary user (onbehalfof) with length 22', () => {
const output = execCmd<{ username: string; password: string }>('org:generate:password -b Other --json -l 22', {
ensureExitCode: 0,
}).jsonOutput?.result;

// Password length overridden to 20
expect(output?.password.length).to.equal(20);
expect(output?.password.length).to.equal(22);
// testing the default complexity
const passwordAsCharArray = (output?.password ?? '').split('');
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
Expand Down Expand Up @@ -246,7 +245,7 @@ describe('verifies all commands run successfully ', () => {
});
it('generates new password for secondary user (onbehalfof) with length 7 should thrown an error', () => {
const output = execCmd('org:generate:password -b Other --json -l 7', { ensureExitCode: 'nonZero' }).jsonOutput;
expect(output?.message).to.include('Expected an integer greater than or equal to 8 but received: 7');
expect(output?.message).to.include('Expected an integer greater than or equal to 20 but received: 7');
});
it('assigns 2 permsets to the main user', () => {
const output = execCmd<PermsetAssignResult>('org:assign:permset -n PS2 -n PS3 --json', {
Expand Down
56 changes: 16 additions & 40 deletions test/commands/password/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,36 +119,15 @@ describe('org:generate:password', () => {
);
});

it('when complexity <3 is specified, logs warn-level message and defaults to 3', async () => {
it('when complexity <3 is specified, throws a validation error', async () => {
await prepareStubs(false, false);
const uxStubs = stubSfCommandUx($$.SANDBOX);
const result = (await GenerateUserPasswordCommand.run([
'--target-org',
testOrg.username,
'--complexity',
'2',
'--json',
])) as PasswordData;

const passwordAsCharArray: string[] = result.password.split('');

expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
true,
'complexity 3 passwords have digits'
);
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
true,
'complexity 3 passwords have uppercase chars'
);
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
true,
'complexity 3 passwords have lowercase chars'
);
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
false,
'complexity 3 passwords do not have symbols'
);
expect(uxStubs.warn.args.flat()).to.include(messages.getMessage('defaultingToComplexity3Password'));
try {
await GenerateUserPasswordCommand.run(['--target-org', testOrg.username, '--complexity', '2', '--json']);
expect.fail('should have thrown an error');
} catch (result) {
assert(result instanceof Error);
expect(result.message).to.include('Expected an integer greater than or equal to 3');
}
});

it('when complexity >=3 is specified, complexity is used as-is', async () => {
Expand Down Expand Up @@ -198,18 +177,15 @@ describe('org:generate:password', () => {
expect(result.password.length).to.equal(20);
});

it('when length <20 is specified, logs warn-level message and defaults to 20', async () => {
it('when length <20 is specified, throws a validation error', 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.warn.args.flat()).to.include(messages.getMessage('defaultingToLength20Password'));
try {
await GenerateUserPasswordCommand.run(['--target-org', testOrg.username, '--length', '12', '--json']);
expect.fail('should have thrown an error');
} catch (result) {
assert(result instanceof Error);
expect(result.message).to.include('Expected an integer greater than or equal to 20');
}
});

it('when length >20 is specified, length is used as-is', async () => {
Expand Down
20 changes: 13 additions & 7 deletions test/forceCommands.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ describe('verifies legacy force commands run successfully ', () => {
);
});

it('generates new password for main user testing length 11 and complexity 3', () => {
const output = execCmd<{ username: string; password: string }>('force:user:password:generate --json -l 11 -c 3', {
it('generates new password for main user testing length 20 and complexity 3', () => {
const output = execCmd<{ username: string; password: string }>('force:user:password:generate --json -l 20 -c 3', {
ensureExitCode: 0,
}).jsonOutput?.result;
expect(output?.password.length).to.equal(11);
expect(output?.password.length).to.equal(20);
const passwordAsCharArray = (output?.password ?? '').split('');
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
true,
Expand All @@ -190,15 +190,15 @@ describe('verifies legacy force commands run successfully ', () => {
expect(output).to.have.property('result').includes.keys(['username', 'password']);
});

it('generates new password for secondary user (onbehalfof) with length 12', () => {
it('generates new password for secondary user (onbehalfof) with length 30', () => {
const output = execCmd<{ username: string; password: string }>(
'force:user:password:generate -o Other --json -l 12',
'force:user:password:generate -o Other --json -l 30',
{
ensureExitCode: 0,
}
).jsonOutput?.result;

expect(output?.password.length).to.equal(12);
expect(output?.password.length).to.equal(30);
// testing the default complexity
const passwordAsCharArray = (output?.password ?? '').split('');
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
Expand Down Expand Up @@ -253,11 +253,17 @@ describe('verifies legacy force commands run successfully ', () => {
}).jsonOutput;
expect(output?.message).to.include('Expected an integer less than or equal to 5 but received: 7');
});
it('generates new password for secondary user (onbehalfof) with complexity 2 should thrown an error', () => {
const output = execCmd('force:user:password:generate -o Other --json -c 2', {
ensureExitCode: 'nonZero',
}).jsonOutput;
expect(output?.message).to.include('Expected an integer greater than or equal to 3 but received: 2');
});
it('generates new password for secondary user (onbehalfof) with length 7 should thrown an error', () => {
const output = execCmd('force:user:password:generate -o Other --json -l 7', {
ensureExitCode: 'nonZero',
}).jsonOutput;
expect(output?.message).to.include('Expected an integer greater than or equal to 8 but received: 7');
expect(output?.message).to.include('Expected an integer greater than or equal to 20 but received: 7');
});
it('assigns 2 permsets to the main user', () => {
const output = execCmd<PermsetAssignResult>('force:user:permset:assign -n PS2,PS3 --json', {
Expand Down
Loading