【问题标题】:How to hide password in the nodejs console?如何在nodejs控制台中隐藏密码?
【发布时间】:2014-07-25 02:38:07
【问题描述】:

我想隐藏密码输入。我在 stackoverflow 中看到了很多答案,但是如果按退格键,我将无法验证值。条件返回 false。

我尝试了几种解决方案来覆盖该函数,但如果我按下退格键,我会遇到缓冲区问题,我得到了不可见字符 \b

我按:“A”,退格,“B”,我的缓冲区中有这个:“\u0041\u0008\u0042”(toString() = 'A\bB')而不是“B”。

我有:

var readline = require('readline');

var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question("password : ", function(password) {
    console.log("Your password : " + password);
});

【问题讨论】:

    标签: javascript node.js


    【解决方案1】:

    这可以通过 readline 通过静音流拦截输出来处理,就像在 npm (https://github.com/isaacs/read/blob/master/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;
    

    【讨论】:

    • 虽然这段代码运行良好,但我想知道它是否正确。您不会将回调传递给stdout.write,而是无条件地成功调用它,并以这种方式抑制任何错误。不幸的是,传递回调会使其停止工作,并且需要在超时 0 的情况下完成静音(这对我来说就像是一种竞争条件)。
    • 不要错过Enter键:if (!this.muted || ['\n', '\r\n'].includes(chunk.toString())) ...
    【解决方案2】:

    覆盖应用readline接口的_writeToOutput:https://github.com/nodejs/node/blob/v9.5.0/lib/readline.js#L291

    要隐藏您的密码输入,您可以使用:

    第一个解决方案:“密码:[=-]”

    此解决方案在您按下触摸时会有动画:

    password : [-=]
    password : [=-]
    

    代码:

    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);
    };
    

    这个序列 "\x1B[2K\x1BD" 使用两个转义序列:

    • Esc [2K : 清除整行。
    • Esc D : 将窗口向上移动/滚动一行。

    要了解更多信息,请阅读: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);
    

    【讨论】:

    • 感谢@Subject,代码按我的预期工作。请您稍微解释一下 \033[2K\033D 的含义?
    • \033[2K\033D,使用两个序列“Esc[2K”和“EscD”。首先,清除整行,然后将窗口向上移动/滚动一行。
    • 如果您使用严格模式,请将 '\033[2K\033[200D' 更改为 '\x1B[2K\x1B[200D'。这只是将弃用的八进制转义更改为等效的十六进制转义。
    • 第二种解决方案有一个奇怪的返回键行为。它打印Password: ,然后如果你输入'a'并按回车,第一输出行的总内容是Password: **,没有换行符。任何后续的控制台日志都将在同一行之后立即记录。我通过测试rl._writeToOutput 中的换行符来修复它,如下所示:if (rl.stdoutMuted && stringToWrite != '\r\n' && stringToWrite != '\n' && stringToWrite != '\r')
    • @chrwoizi 是的,擦除密码时也是如此,因为界面将重新渲染整个测试行。尝试``` if (password) { rl._writeToOutput = (s) => { const v = s.split(question) if (v.length == '2') { rl.output.write(question) rl. output.write(''.repeat(v[1].length)) } else { rl.output.write('') } } } `` (github.com/artdecocode/reloquent/blob/master/src/lib/ask.js#L23) 或 NPM 包多嘴
    【解决方案3】:

    您可以使用readline-sync 模块代替节点的readline

    密码隐藏功能是通过它的“hideEchoBack”选项内置的。

    https://www.npmjs.com/package/readline-sync

    【讨论】:

    • readline-syncttys 一起允许您在不显示输入字符的情况下进行密码提示。感谢您的链接! :)
    【解决方案4】:

    使用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();
    });
    

    【讨论】:

    • 说明了很好的解决方案/简单 + 有效,并且不需要安装模块或覆盖私有 API。轻松添加标志以在 keypress 处理之间切换,具体取决于是否应隐藏问题响应。
    • 这个解决方案对我不起作用,因为每个字符在被替换之前都会短暂显示
    【解决方案5】:

    我的解决方案,从网上的各个方面拼凑而成:

    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);
      });
    });
    

    用法是这样的:

    // 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));
    

    【讨论】:

      【解决方案6】:

      想要添加到标记的解决方案#2。

      当我们检测到行尾时,我相信我们应该删除事件处理程序,而不仅仅是 stdin.pause()。如果您在其他地方等待 rl.question/rl.prompt,这可能是个问题。 在这些情况下,如果使用了stdin.pause(),它只会退出程序而不会给出任何错误,而且调试起来会很烦人。

      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);
          });
      }
      

      【讨论】:

        【解决方案7】:

        也可以使用tty.ReadStream
        更改process.stdin的模式
        禁用回显输入字符。

        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 );
        } );
        

        【讨论】:

          【解决方案8】:

          您可以按照here 的建议使用prompt 模块。

          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;
          }
          

          【讨论】:

          • 我无法在 async 主函数中正常工作,此实现是否仅在顺序执行中可行?
          【解决方案9】:

          承诺的 typescript 原生版本:

          这也将处理多个question 调用(正如@jeffrey-woo 指出的那样)。我选择不将输入替换为*,因为它感觉不是很unix-y,而且我发现如果输入太快有时会出现故障。

          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);
              });
            });
          

          用法:

          (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);
          });
          

          【讨论】:

            【解决方案10】:

            这是我的解决方案,它不需要任何外部库(除了 readline)或大量代码。

            // 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
            });
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-03-15
              • 2019-08-17
              • 1970-01-01
              • 2011-04-03
              • 2015-01-26
              • 2019-06-02
              相关资源
              最近更新 更多