diff --git a/config/__tests__/helpers.spec.js b/config/__tests__/helpers.spec.js new file mode 100644 index 0000000000..d682b35f80 --- /dev/null +++ b/config/__tests__/helpers.spec.js @@ -0,0 +1,23 @@ +const { PassThrough } = require('stream'); +const { askSilentQuestion } = require('../helpers'); + +describe('askSilentQuestion', () => { + it('shows the prompt without echoing the answer', async () => { + const input = new PassThrough(); + const output = new PassThrough(); + let written = ''; + + input.isTTY = true; + output.isTTY = true; + output.columns = 80; + output.on('data', (chunk) => { + written += chunk.toString(); + }); + + const answerPromise = askSilentQuestion('Enter new password: ', input, output); + input.write('visible-secret\n'); + + await expect(answerPromise).resolves.toBe('visible-secret'); + expect(written).toBe('Enter new password: \n'); + }); +}); diff --git a/config/helpers.js b/config/helpers.js index 9636f8c9d5..12264b66a0 100644 --- a/config/helpers.js +++ b/config/helpers.js @@ -5,6 +5,7 @@ const fs = require('fs'); const path = require('path'); const readline = require('readline'); +const { Writable } = require('stream'); const { execSync } = require('child_process'); /** @typedef {(message: string) => void} ConsoleColor */ @@ -27,6 +28,43 @@ const askQuestion = (query) => { ); }; +/** + * @param {string} query + * @param {NodeJS.ReadableStream} [input] + * @param {NodeJS.WritableStream} [destination] + * @returns {Promise} + */ +const askSilentQuestion = (query, input = process.stdin, destination = process.stdout) => { + let muted = false; + const output = new Writable({ + write(chunk, encoding, callback) { + if (!muted) { + destination.write(chunk, encoding); + } + callback(); + }, + }); + output.isTTY = destination.isTTY; + + const rl = readline.createInterface({ + input, + output, + terminal: input.isTTY, + }); + + destination.write(query); + muted = true; + + return new Promise((resolve) => + rl.question('', (answer) => { + muted = false; + destination.write('\n'); + rl.close(); + resolve(answer); + }), + ); +}; + /** @param {string} query @returns {Promise} */ const askMultiLineQuestion = (query) => { const rl = readline.createInterface({ @@ -91,6 +129,7 @@ coloredConsole.gray = (/** @type {string} */ msg) => console.log('\x1b[90m%s\x1b module.exports = { askQuestion, + askSilentQuestion, askMultiLineQuestion, silentExit, isDockerRunning, diff --git a/config/reset-password.js b/config/reset-password.js index 310bac0998..4fdc3631a0 100644 --- a/config/reset-password.js +++ b/config/reset-password.js @@ -4,14 +4,22 @@ const readline = require('readline'); const mongoose = require('mongoose'); const { User } = require('@librechat/data-schemas').createModels(mongoose); require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const { askSilentQuestion } = require('./helpers'); const connect = require('./connect'); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, -}); +const question = (query) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); -const question = (query) => new Promise((resolve) => rl.question(query, resolve)); + return new Promise((resolve) => + rl.question(query, (answer) => { + rl.close(); + resolve(answer); + }), + ); +}; const resetPassword = async () => { try { @@ -29,13 +37,13 @@ const resetPassword = async () => { let newPassword; while (!validPassword) { - newPassword = await question('Enter new password: '); + newPassword = await askSilentQuestion('Enter new password: '); if (newPassword.length < 8) { console.log('Password must be at least 8 characters! Please try again.'); continue; } - const confirmPassword = await question('Confirm new password: '); + const confirmPassword = await askSilentQuestion('Confirm new password: '); if (newPassword !== confirmPassword) { console.log('Passwords do not match! Please try again.'); continue; @@ -60,8 +68,6 @@ const resetPassword = async () => { } catch (err) { console.error('Error resetting password:', err); process.exit(1); - } finally { - rl.close(); } };