Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli-repl): prompt for password when redirecting output MONGOSH-1136 #2116

Merged
merged 6 commits into from
Aug 13, 2024
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
17 changes: 11 additions & 6 deletions packages/cli-repl/src/cli-repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ export type CliReplOptions = {
input: Readable;
/** The stream to write shell output to. */
output: Writable;
/**
* The stream to write prompt output to when requesting data from user, like password.
* Helpful when user wants to redirect the output to a file or null device.
* If not provided, the `output` stream will be used.
*/
promptOutput?: Writable;
/** The set of home directory paths used by this shell instance. */
shellHomePaths: ShellHomePaths;
/** The ordered list of paths in which to look for a global configuration file. */
Expand Down Expand Up @@ -112,6 +118,7 @@ export class CliRepl implements MongoshIOProvider {
logWriter?: MongoLogWriter;
input: Readable;
output: Writable;
promptOutput: Writable;
analyticsOptions?: AnalyticsOptions;
segmentAnalytics?: SegmentAnalytics;
toggleableAnalytics: ToggleableAnalytics = new ToggleableAnalytics();
Expand All @@ -132,6 +139,7 @@ export class CliRepl implements MongoshIOProvider {
this.cliOptions = options.shellCliOptions;
this.input = options.input;
this.output = options.output;
this.promptOutput = options.promptOutput ?? options.output;
this.analyticsOptions = options.analyticsOptions;
this.onExit = options.onExit;

Expand Down Expand Up @@ -1003,22 +1011,19 @@ export class CliRepl implements MongoshIOProvider {

/**
* Require the user to enter a password.
*
* @param {string} driverUrl - The driver URI.
* @param {DevtoolsConnectOptions} driverOptions - The driver options.
*/
async requirePassword(): Promise<string> {
const passwordPromise = askpassword({
input: this.input,
output: this.output,
output: this.promptOutput,
replacementCharacter: '*',
});
this.output.write('Enter password: ');
this.promptOutput.write('Enter password: ');
try {
try {
return (await passwordPromise).toString();
} finally {
this.output.write('\n');
this.promptOutput.write('\n');
}
} catch (error: any) {
await this._fatalError(error);
Expand Down
44 changes: 44 additions & 0 deletions packages/cli-repl/src/run.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,48 @@ describe('CLI entry point', function () {
'MongoshInvalidInputError: [COMMON-10001] Invalid URI: /'
);
});

context('prompts for password', function () {
it('requests password when user is not redirecting output', async function () {
const args = [...pathToRun, 'mongodb://amy@localhost:27017'];
const proc = childProcess.spawn(process.execPath, args, {
stdio: ['pipe'],
env: { ...process.env, TEST_USE_STDOUT_FOR_PASSWORD: '1' },
});
let stdout = '';
let promptedForPassword = false;
proc.stdout?.setEncoding('utf8').on('data', (chunk) => {
stdout += chunk;
if (stdout.includes('Enter password')) {
promptedForPassword = true;
proc.stdin?.write('\n');
}
});

const [code] = await once(proc, 'exit');
expect(code).to.equal(1);
expect(promptedForPassword).to.be.true;
});

it('requests password when user is redirecting output', async function () {
const args = [...pathToRun, 'mongodb://amy@localhost:27017'];
const proc = childProcess.spawn(process.execPath, args, {
stdio: ['pipe', 'ignore', 'pipe'],
env: { ...process.env },
});
let stderr = '';
let promptedForPassword = false;
proc.stderr?.setEncoding('utf8').on('data', (chunk) => {
stderr += chunk;
if (stderr.includes('Enter password')) {
promptedForPassword = true;
proc.stdin?.write('\n');
}
});

const [code] = await once(proc, 'exit');
expect(code).to.equal(1);
expect(promptedForPassword).to.be.true;
});
});
});
4 changes: 4 additions & 0 deletions packages/cli-repl/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ async function main() {
getCryptLibraryPaths,
input: process.stdin,
output: process.stdout,
promptOutput:
process.env.TEST_USE_STDOUT_FOR_PASSWORD || process.stdout.isTTY
? process.stdout
: process.stderr,
// Node.js 20.0.0 made p.exit(undefined) behave as p.exit(0) rather than p.exit()
onExit: (code?: number | undefined) =>
code === undefined ? process.exit() : process.exit(code),
Expand Down
Loading