Reputation: 13157
I want to hide password input. I see many answers in stackoverflow but I can't verify value if I press backspace. The condition return false.
I tried several solution to overwrite the function but I got an issue with buffer if I press backspace, I got invisible character \b
.
I press : "A", backspace, "B", I have in my buffer this : "\u0041\u0008\u0042" (toString() = 'A\bB') and not "B".
I have :
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("password : ", function(password) {
console.log("Your password : " + password);
});
Upvotes: 60
Views: 48189
Reputation: 412
This is my comprehensive solution that will run in node without anything from npm. It uses promises and has an option for disabling the echo back when typing. This works in node 20.11.1 and doesn't use the deprecated/removed things other answers have and has no race condition. When it is silent no replacement symbols are shown but it does support backspace and pasting.
'use strict';
const readline = require('readline');
const stream = require('stream');
const prompt = (query, promptOptions = {}) =>
new Promise((resolve) => {
const mutableStdout = new stream.Writable({
write: (chunk, encoding, callback) => {
//don't print anything typed when silent
if (!promptOptions.silent) {
process.stdout.write(chunk, encoding);
}
callback();
},
});
const rl = readline.createInterface({
input: process.stdin,
output: mutableStdout,
terminal: promptOptions.silent,
});
//have to print here so that query is shown even when stdin is silent
process.stdout.write(query);
rl.question('', (answer) => {
if (promptOptions.silent) {
rl.history = rl.history.slice(1);
//enter will submit without printing. this puts the output on the next line
process.stdout.write('\n');
}
rl.close();
resolve(answer);
});
});
async function main() {
const user = await prompt('User Name: ');
const pass = await prompt('Password: ', { silent: true });
console.log(`${user}:${pass}`);
}
const shell = {
GENERIC_FAILURE_CODE: 1,
SUCCESS_CODE: 0,
};
main()
.then(() => {
process.exit(shell.SUCCESS_CODE);
})
.catch((error) => {
console.error('main failed', error);
process.exit(shell.GENERIC_FAILURE_CODE);
});
Upvotes: 1
Reputation: 16056
This might be the easiest solution:
import { createInterface } from 'node:readline/promises';
const rl = createInterface({
input: process.stdin,
output: null,
terminal: true,
});
const password = await rl.question();
rl.close();
This answer is close to the same but more complicated.
Upvotes: 2
Reputation: 1422
This can be handled with readline by intercepting the output through a muted stream, as is done in the read project on npm (https://github.com/isaacs/read/blob/v1.0.7/lib/read.js):
var readline = require('readline');
var Writable = require('stream').Writable;
var mutableStdout = new Writable({
write: function(chunk, encoding, callback) {
if (!this.muted)
process.stdout.write(chunk, encoding);
callback();
}
});
mutableStdout.muted = false;
var rl = readline.createInterface({
input: process.stdin,
output: mutableStdout,
terminal: true
});
rl.question('Password: ', function(password) {
console.log('\nPassword is ' + password);
rl.close();
});
mutableStdout.muted = true;
Upvotes: 65
Reputation: 38112
The accepted answer suggests we imitate the read
package (https://www.npmjs.com/package/read), but why not just use it? It has almost 5M weekly downloads, regularly updated, and among the contributors you have the guy behind npm
itself...
import { read } from 'read';
const password = await read({
prompt: "password: ",
silent: true,
replace: "*"
};
console.log("Your password: " + password);
Upvotes: 4
Reputation: 6915
I liked the _writeToOutput
override, but the solutions here don't seem to handle the arrow-up/history case; I've added that, and TypeScripted it. Makes use of the fact that readline sends the full query string when you navigate history, and only a single character normally.
Checking the input or using a proper library are better choices, but not always worth the effort. This ios quick-and-dirty, probably still breaks in cases, but I haven't found them (TM):
let echo = '*'; // or '' if you prefer
let first: string|undefined;
(this.readline as any)._writeToOutput = (c: string) => {
if (first==undefined || c.length!=1) {
if (first==undefined) first = c;
if (c.startsWith(first)) {
// rewriting prompt
(this.readline as any).output?.write(first);
c = c.slice(first.length);
} else if (!c.trim()) {
// user pressed enter, show the enter
(this.readline as any).output?.write(c);
c = '';
}
}
for (let i = 0; i < c.length; i++) {
// all other input, and bits after the prompt, use echo char
(this.readline as any).output?.write(echo);
}
};
And after completion, don't forget this from https://stackoverflow.com/a/24037546:
(this.readline as any).history = (this.readline as any).history?.slice(1);
Upvotes: 0
Reputation: 3673
Unix like style. No symbols on put/paste at all.
rl.input.on('keypress', (c) => {
if (c.charCodeAt() === 127) {
const len = rl.line.length
readline.moveCursor(rl.output, -len, 0)
readline.clearLine(rl.output, 1)
return
}
readline.moveCursor(rl.output, -1, 0)
readline.clearLine(rl.output, 1)
})
Upvotes: 0
Reputation: 1164
Here's my own take on this, cherry picking from the other answers here. When the user types, there is no output. This is to prevent any data leaks.
const readline = require("readline");
const hiddenQuestion = (query) =>
new Promise((resolve) => {
console.log(query);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl._writeToOutput = () => {};
const stdin = process.openStdin();
process.stdin.on("data", (char) => {
char = char.toString("utf-8");
switch (char) {
case "\n":
case "\r":
case "\u0004":
// Finished writing their response
stdin.pause();
break;
// You might make this case optional, (Ctrl-C)
case "\u0003":
// Ctrl-C
process.exit(0);
default:
process.stdout.clearLine();
readline.cursorTo(process.stdout, 0);
break;
}
});
rl.question("", (value) => {
rl.history = rl.history.slice(1);
rl.close();
resolve(value);
});
});
// Usage example:
void (async () => {
const password = await hiddenQuestion("What is your password?");
// do what you want with the password...
})();
Upvotes: 0
Reputation: 18526
A promisified typescript native version:
This will also handle multiple question
calls (as @jeffrey-woo pointed out). I chose not to replace input with *
, as it didn't feel very unix-y, and I found it to be glitchy sometimes if typing too fast anway.
import readline from 'readline';
export const question = (question: string, options: { hidden?: boolean } = {}) =>
new Promise<string>((resolve, reject) => {
const input = process.stdin;
const output = process.stdout;
type Rl = readline.Interface & { history: string[] };
const rl = readline.createInterface({ input, output }) as Rl;
if (options.hidden) {
const onDataHandler = (charBuff: Buffer) => {
const char = charBuff + '';
switch (char) {
case '\n':
case '\r':
case '\u0004':
input.removeListener('data', onDataHandler);
break;
default:
output.clearLine(0);
readline.cursorTo(output, 0);
output.write(question);
break;
}
};
input.on('data', onDataHandler);
}
rl.question(question, (answer) => {
if (options.hidden) rl.history = rl.history.slice(1);
rl.close();
resolve(answer);
});
});
Usage:
(async () => {
const hiddenValue = await question('This will be hidden', { hidden: true });
const visibleValue = await question('This will be visible');
console.log('hidden value', hiddenValue);
console.log('visible value', visibleValue);
});
Upvotes: 3
Reputation: 16906
Here's my solution which doesn't require any external libraries (besides readline) or a lot of code.
// turns off echo, but also doesn't process backspaces
// also captures ctrl+c, ctrl+d
process.stdin.setRawMode(true);
const rl = require('readline').createInterface({input: process.stdin});
rl.on('close', function() { process.exit(0); }); // on ctrl+c, doesn't work? :(
rl.on('line', function(line) {
if (/\u0003\.test(line)/) process.exit(0); // on ctrl+c, but after return :(
// process backspaces
while (/\u007f/.test(line)) {
line = line.replace(/[^\u007f]\u007f/, '').replace(/^\u007f+/, '');
}
// do whatever with line
});
Upvotes: 1
Reputation: 1225
Another method using readline
:
var readline = require("readline"),
rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.input.on("keypress", function (c, k) {
// get the number of characters entered so far:
var len = rl.line.length;
// move cursor back to the beginning of the input:
readline.moveCursor(rl.output, -len, 0);
// clear everything to the right of the cursor:
readline.clearLine(rl.output, 1);
// replace the original input with asterisks:
for (var i = 0; i < len; i++) {
rl.output.write("*");
}
});
rl.question("Enter your password: ", function (pw) {
// pw == the user's input:
console.log(pw);
rl.close();
});
Upvotes: 10
Reputation: 18493
You can use the prompt module, as suggested here.
const prompt = require('prompt');
const properties = [
{
name: 'username',
validator: /^[a-zA-Z\s\-]+$/,
warning: 'Username must be only letters, spaces, or dashes'
},
{
name: 'password',
hidden: true
}
];
prompt.start();
prompt.get(properties, function (err, result) {
if (err) { return onErr(err); }
console.log('Command-line input received:');
console.log(' Username: ' + result.username);
console.log(' Password: ' + result.password);
});
function onErr(err) {
console.log(err);
return 1;
}
Upvotes: 2
Reputation: 13157
Overwrite _writeToOutput of application's readline interface : https://github.com/nodejs/node/blob/v9.5.0/lib/readline.js#L291
To hide your password input, you can use :
This solution has animation when you press a touch :
password : [-=]
password : [=-]
The code :
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.stdoutMuted = true;
rl.query = "Password : ";
rl.question(rl.query, function(password) {
console.log('\nPassword is ' + password);
rl.close();
});
rl._writeToOutput = function _writeToOutput(stringToWrite) {
if (rl.stdoutMuted)
rl.output.write("\x1B[2K\x1B[200D"+rl.query+"["+((rl.line.length%2==1)?"=-":"-=")+"]");
else
rl.output.write(stringToWrite);
};
This sequence "\x1B[2K\x1BD" uses two escapes sequences :
To learn more, read this : http://ascii-table.com/ansi-escape-sequences-vt-100.php
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.stdoutMuted = true;
rl.question('Password: ', function(password) {
console.log('\nPassword is ' + password);
rl.close();
});
rl._writeToOutput = function _writeToOutput(stringToWrite) {
if (rl.stdoutMuted)
rl.output.write("*");
else
rl.output.write(stringToWrite);
};
rl.history = rl.history.slice(1);
Upvotes: 56
Reputation: 37045
My solution, scraped together from various bits online:
import readline from 'readline';
export const hiddenQuestion = query => new Promise((resolve, reject) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const stdin = process.openStdin();
process.stdin.on('data', char => {
char = char + '';
switch (char) {
case '\n':
case '\r':
case '\u0004':
stdin.pause();
break;
default:
process.stdout.clearLine();
readline.cursorTo(process.stdout, 0);
process.stdout.write(query + Array(rl.line.length + 1).join('*'));
break;
}
});
rl.question(query, value => {
rl.history = rl.history.slice(1);
resolve(value);
});
});
Usage is like this:
// import { hiddenQuestion } from './hidden-question.js';
const main = async () => {
console.log('Enter your password and I will tell you your password! ');
const password = await hiddenQuestion('> ');
console.log('Your password is "' + password + '". ');
};
main().catch(error => console.error(error));
Upvotes: 8
Reputation: 2561
Also one can use tty.ReadStream
changing mode of process.stdin
to disable echoing input characters.
let read_Line_Str = "";
let credentials_Obj = {};
process.stdin.setEncoding('utf8');
process.stdin.setRawMode( true );
process.stdout.write( "Enter password:" );
process.stdin.on( 'readable', () => {
const chunk = process.stdin.read();
if ( chunk !== null ) {
read_Line_Str += chunk;
if(
chunk == "\n" ||
chunk == "\r" ||
chunk == "\u0004"
){
process.stdout.write( "\n" );
process.stdin.setRawMode( false );
process.stdin.emit('end'); /// <- this invokes on.end
}else{
// providing visual feedback
process.stdout.write( "*" );
}
}else{
//console.log( "readable data chunk is null|empty" );
}
} );
process.stdin.on( 'end', () => {
credentials_Obj.user = process.env.USER;
credentials_Obj.host = 'localhost';
credentials_Obj.database = process.env.USER;
credentials_Obj.password = read_Line_Str.trim();
credentials_Obj.port = 5432;
//
connect_To_DB( credentials_Obj );
} );
Upvotes: 3
Reputation: 982
You can use the readline-sync
module instead of node's readline
.
Password-hiding functionality is built in via it's "hideEchoBack" option.
https://www.npmjs.com/package/readline-sync
Upvotes: 17
Reputation: 51
Wanted to add to the marked solution#2.
When we detect the line-ends, I believe we should remove the event handler instead of just stdin.pause()
. This can be an issue if you are waiting on rl.question/rl.prompt elsewhere.
In those cases, if stdin.pause()
was used, it would just exit the program without giving any errors and can be quite annoying to debug.
function hidden(query, callback) {
var stdin = process.openStdin();
var onDataHandler = function(char) {
char = char + "";
switch (char) {
case "\n": case "\r": case "\u0004":
// Remove this handler
stdin.removeListener("data",onDataHandler);
break;//stdin.pause(); break;
default:
process.stdout.write("\033[2K\033[200D" + query + Array(rl.line.length+1).join("*"));
break;
}
}
process.stdin.on("data", onDataHandler);
rl.question(query, function(value) {
rl.history = rl.history.slice(1);
callback(value);
});
}
Upvotes: 5