Skip to content

Commit f4448b0

Browse files
authored
fix(cli-repl): prompt for password when redirecting output MONGOSH-1136 (#2116)
* prompt for password when redirecting output * inject promptOutput * add tests * feedback
1 parent 8eea5b3 commit f4448b0

File tree

3 files changed

+59
-6
lines changed

3 files changed

+59
-6
lines changed

packages/cli-repl/src/cli-repl.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ export type CliReplOptions = {
7878
input: Readable;
7979
/** The stream to write shell output to. */
8080
output: Writable;
81+
/**
82+
* The stream to write prompt output to when requesting data from user, like password.
83+
* Helpful when user wants to redirect the output to a file or null device.
84+
* If not provided, the `output` stream will be used.
85+
*/
86+
promptOutput?: Writable;
8187
/** The set of home directory paths used by this shell instance. */
8288
shellHomePaths: ShellHomePaths;
8389
/** The ordered list of paths in which to look for a global configuration file. */
@@ -112,6 +118,7 @@ export class CliRepl implements MongoshIOProvider {
112118
logWriter?: MongoLogWriter;
113119
input: Readable;
114120
output: Writable;
121+
promptOutput: Writable;
115122
analyticsOptions?: AnalyticsOptions;
116123
segmentAnalytics?: SegmentAnalytics;
117124
toggleableAnalytics: ToggleableAnalytics = new ToggleableAnalytics();
@@ -132,6 +139,7 @@ export class CliRepl implements MongoshIOProvider {
132139
this.cliOptions = options.shellCliOptions;
133140
this.input = options.input;
134141
this.output = options.output;
142+
this.promptOutput = options.promptOutput ?? options.output;
135143
this.analyticsOptions = options.analyticsOptions;
136144
this.onExit = options.onExit;
137145

@@ -1003,22 +1011,19 @@ export class CliRepl implements MongoshIOProvider {
10031011

10041012
/**
10051013
* Require the user to enter a password.
1006-
*
1007-
* @param {string} driverUrl - The driver URI.
1008-
* @param {DevtoolsConnectOptions} driverOptions - The driver options.
10091014
*/
10101015
async requirePassword(): Promise<string> {
10111016
const passwordPromise = askpassword({
10121017
input: this.input,
1013-
output: this.output,
1018+
output: this.promptOutput,
10141019
replacementCharacter: '*',
10151020
});
1016-
this.output.write('Enter password: ');
1021+
this.promptOutput.write('Enter password: ');
10171022
try {
10181023
try {
10191024
return (await passwordPromise).toString();
10201025
} finally {
1021-
this.output.write('\n');
1026+
this.promptOutput.write('\n');
10221027
}
10231028
} catch (error: any) {
10241029
await this._fatalError(error);

packages/cli-repl/src/run.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,48 @@ describe('CLI entry point', function () {
9797
'MongoshInvalidInputError: [COMMON-10001] Invalid URI: /'
9898
);
9999
});
100+
101+
context('prompts for password', function () {
102+
it('requests password when user is not redirecting output', async function () {
103+
const args = [...pathToRun, 'mongodb://amy@localhost:27017'];
104+
const proc = childProcess.spawn(process.execPath, args, {
105+
stdio: ['pipe'],
106+
env: { ...process.env, TEST_USE_STDOUT_FOR_PASSWORD: '1' },
107+
});
108+
let stdout = '';
109+
let promptedForPassword = false;
110+
proc.stdout?.setEncoding('utf8').on('data', (chunk) => {
111+
stdout += chunk;
112+
if (stdout.includes('Enter password')) {
113+
promptedForPassword = true;
114+
proc.stdin?.write('\n');
115+
}
116+
});
117+
118+
const [code] = await once(proc, 'exit');
119+
expect(code).to.equal(1);
120+
expect(promptedForPassword).to.be.true;
121+
});
122+
123+
it('requests password when user is redirecting output', async function () {
124+
const args = [...pathToRun, 'mongodb://amy@localhost:27017'];
125+
const proc = childProcess.spawn(process.execPath, args, {
126+
stdio: ['pipe', 'ignore', 'pipe'],
127+
env: { ...process.env },
128+
});
129+
let stderr = '';
130+
let promptedForPassword = false;
131+
proc.stderr?.setEncoding('utf8').on('data', (chunk) => {
132+
stderr += chunk;
133+
if (stderr.includes('Enter password')) {
134+
promptedForPassword = true;
135+
proc.stdin?.write('\n');
136+
}
137+
});
138+
139+
const [code] = await once(proc, 'exit');
140+
expect(code).to.equal(1);
141+
expect(promptedForPassword).to.be.true;
142+
});
143+
});
100144
});

packages/cli-repl/src/run.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ async function main() {
217217
getCryptLibraryPaths,
218218
input: process.stdin,
219219
output: process.stdout,
220+
promptOutput:
221+
process.env.TEST_USE_STDOUT_FOR_PASSWORD || process.stdout.isTTY
222+
? process.stdout
223+
: process.stderr,
220224
// Node.js 20.0.0 made p.exit(undefined) behave as p.exit(0) rather than p.exit()
221225
onExit: (code?: number | undefined) =>
222226
code === undefined ? process.exit() : process.exit(code),

0 commit comments

Comments
 (0)