Build a Password Field for the Terminal using Nodejs
source link: https://blog.bitsrc.io/build-a-password-field-for-the-terminal-using-nodejs-31cd6cfa235
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How to build a reusable password field for the command line interface
You’re probably familiar with the popular password field that shows up whenever you want to push your local repo to remote using Git Bash. It prompts for a password, and whenever you type, nothing is displayed on the terminal. I was confused at first and thought maybe my terminal lost focus. I tried typing several times until I finally decided to hit the Enter key. It was then that I realize my text was registered and my anxiety quickly dissipated.
Have you ever thought about how they managed to implement that? If you wanna know what happens beneath then this article is for you.
This article will cover two topics:
- Building a password field for the terminal using Nodejs
- Sharing the Nodejs password field module/component to a component collection in bit.dev (to share with others and reuse in different projects)
Setup
Create a Node project and make it look like this:
pswd-node - - src - pswnode.js - package.json - index.js
We will be working on the pswnode.js
file.
Indefinite password
The trick here is to hook into the terminal/command-line event that triggers the data
event when a key is pressed. We will have the power to write to the terminal screen whatever we want.
process.stdin
is used to capture inputs to the terminal, it emits the data event whenever when type on the terminal. So we will register our function handler to the process.stdin data
event to call the handler whenever we type on the terminal. It is in this handler we will display anything and in this case configure our password.
// src/pswnode.js const stdout = process.stdout const stdin = process.stdin const readline = require("readline")
We set the process
objects to shorter variable names :) to avoid long typing.
stdout is for writing to the terminal stdin is for capturing inputs to the terminal.
// src/pswnode.js const stdout = process.stdout const stdin = process.stdin const readline = require("readline")stdout.write("password:") stdin.setRawMode(true) stdin.resume() stdin.setEncoding('utf-8') let input = ''
We write "password:"
to the screen, yes to notify users that they are to input their password. Next, we set raw mode to true so as not to get streams when enter is pressed. stdin.resume, this will resume the terminal in the parent process to avoid the Node app quitting. We set the keycode encoding to utf-8
so to get the key names in string, not in binary.
The input variable is what we will use to gather the password. We will concatenate each keystroke to it, to form the password.
... const pn = (data) => { const c = data switch (c) { case '\u0004': // Ctrl-d case '\r': case '\n': return enter() case '\u0003': // Ctrl-c return ctrlc() default: // backspace if (c.charCodeAt(0) === 8) return backspace() else return newchar(c) } }
We have the function handler here, this function will be called anytime we type on the terminal.
The keystroke is received in the data
param. We used a switch statement to perform different actions based on the value of the param data
. We have actions for when enter is pressed; the enter function is called. Action for ctrl-c, when we want to exit the ctrlc function is called. The default case checks if the data is a backspace, to call the backspace function. If not newchar is called, this will handle the general keys.
// src/pswnode.js ... stdin.on("data", pn)function enter() { stdin.removeListener('data', pn) l("\nYour password is: " + input) stdin.setRawMode(false) stdin.pause() }function ctrlc() { stdin.removeListener('data', pn) stdin.setRawMode(false) stdin.pause() }function newchar(c) { input += c }function backspace() { input = input.slice(0, input.length - 1) }
Here, we register the function handler. The stdin.on(...)
is used to do so. The first argument states the event you want to hook into and the second argument, the function you want to be called when the event of the first arg is fired/emitted. Here, we put data
that is the event emitted when we type on the terminal and we put our pn
function handler in the second arg.
We defined all the functions we saw above.
We have enter
, called when the Enter key is pressed. When pressed it means the user is done entering password, so we remove the function handler from the data
event, log the user's password we snagged in the input variable, then we set raw mode to false and pause.
The ctrlc
function handles the ctrl-c key, it means a sudden exit. Here it removes the pn handler from the data
event then we set raw mode to false and pause.
The newchar
handles any other key, it accepts the key in its c param, then concatenates the param to the input variable, this builds up the password in the input variable as the user types in. Then the function exits. With this nothing is printed to the terminal :) the user won't see his/her password because we didn't write it to the stdout
stream but the password will be held in the input
variable.
The backspace
handles the delete key, this key is pressed when the user wants to delete a character from the password. The user doesn't see it but builds it up mentally, so he knows when an incorrect password character is typed and wants to delete. We use the slice
method in String to remove the last character in the string we built up in the input variable then reassign the result to the input
.
When the user types
n
=> input = n
n
=> input = nn
a
=> input = nna
m
=> input = nnam
i
=> input = nnami
Pressing backspace, the input will be nnam
.
So, our full code will be:
Now run the pswnode.js file node src/pswnode
. You will see password:
printed, now type in.
$ node src/pswnode password:
After typing press enter
$ node src/pswnode password: Your password is: nnamdi
Your password is:
will be displayed along with the password you typed. Mine is nnamdi
.
Masking with *
Now, let’s make the user feel some output.
We will display an *
asterisk whenever a key is pressed. We will add stdout.write("*")
in our newchar
function.
// src/pswnode.js ... function newchar(c) { input += c stdout.write("*") } ...
Now, when we type, * is displayed for each keystroke.
$ node src/pswnode password: ******
On enter
:
$ node src/pswnode password: ****** Your password is: nnamdi
We have a problem if we hit the backspace to delete a character. The asterisk should decrement by one but none of that is happening. We have to refactor our backspace
function.
We will require the readline
library:
// src/pswnode.js const stdout = process.stdout const stdin = process.stdin const readline = require("readline")...
The terminal is made up of cells where data can be written. To write to a cell you must triangulate the position of the cell in an x-y plane. The readline
library has methods to helps us use the x-y plane of the terminal to position our cursor. The position of the cursor determines where data will be written to next.
Now, in this backspace problem, we need to move the cursor backward and write an empty string to it. This removes the *
data on the cell. But first, we need to find the x,y position of the previous cell.
The position can be determined by adding the "password:"
length and the input
length and subtracting the result by 1. This will give us the x position. The y- will be 0 because "password:"
is displayed at the topmost level.
With the x-y position gotten, we will use readline.cursorTo(...)
method to place the cursor at the previous line, then we will write empty string sing stdout and use readline.readline.moveCursor(stdout, -1, 0)
to move the cursor display move backwards.
// src/pswnode.js ... function backspace() { const pslen = "password:".length readline.cursorTo(stdout, (pslen + input.length) - 1, 0) stdout.write(" ") readline.moveCursor(stdout, -1, 0) input = input.slice(0, input.length - 1) }
Now, we run it:
$ node src/pswnode password: ******
Now if we press backspace the last * will be deleted
$ node src/pswnode password: *****
Press backspace again:
$ node src/pswnode password: ****
Press it again:
$ node src/pswnode password: ***
See the * decrements by 1.
Limiting our password
What we have above is an indefinite password field that you can type on as long as you want. Now, let’s make the password limited to a number like 6 or 7.
Simple, we add an if
statement check in the newchar
function:
// src/pswnode.js ... function newchar(c) { if (input.length != 7) { input += c stdout.write("*") } } ...
We limited our password to a length of 7. Once we type and exceed 7, * will not be displayed and the keystroke will not be joined to the input
variable.
Let’s try it:
$ node src/pswnode password: *******
I typed nnamdii
, 7 characters. Now, type i
:
$ node src/pswnode password: *******
Nothing is displayed. Now press enter
$ node src/pswnode password: ******* Your password is: nnamdii
Our password is still nnamdii
.
We have hardcoded our values so far, we will make it flexible very soon.
Now, let’s see another way we can make a terminal password field using Nodejs.
Using readline.question(...)
Create a file pswnode2.js
in the src
folder and open it with a file editor, VS Code preferably.
Now require some libraries:
// src/pswnode2.js const readline = require("readline") const Writable = require("stream").Writable const stdout = process.stdout
We required the readline
library and the required the stream
library then accessed the Writable
class.
Now, we will create our Writable
instance with our configurations:
// src/pswnode2.js ... const mutedStdOut = new Writable({ write: function(chunk, encoding, callback) { if (!this.muted) stdout.write(chunk, encoding) callback() } }) mutedStdOut.muted = false
We have our own write
method. this write
method in Writable
instance is called whenever we type to the terminal. Here we hook into it to not display anything typed by the user.
// src/pswnode2 ... const rdl = readline.createInterface({ input: process.stdin, output: mutedStdOut, terminal: true })rdl.question("password:", (chunk) => { l("\nYour password is: " + chunk) rdl.close() })mutedStdOut.muted = true
We create a ReadLine
instance using the createInterface
method. We set the input
to process.stdin
and set the ouput to our Writable
instance mutedStdOut
.
Next, we set the question :) passing in the name to display and the callback function. This callback function will be called when the user presses the enter button.
The password typed is passed to the callback function in the chunk
param
Now, let’s run the file:
$ node src/pswnode2 password:
Now, type your password. I type nnamdi
. Notice nothing is displayed.
Now, if we hit the enter key:
$ node src/pswnode2 password: Your password is: nnamdi
See our password is displayed.
Making our first implementation flexible
Now, it has an option for the name you want to be displayed and the password length. It has default configuration in case any is missing. I made the default name and length to be password:
and 7 respectively.
To run it, we can just create an instance of Password passing in our options and calling start
method.
// src/pswnode.js ... const pass = new Password({ ask: "emm your password: ", passLength: 10 }) pass.start()
Running this will give us:
$ node src/pswnode emm your password: ********** Your password is: 1234567899
Let’s share it using Bit
Bit is a tool and a platform that enables you to share and collaborate on modules/components across repositories to build faster as a team.
With Bit you can get all the benefits of working in a multi-repository environment while gaining the code-sharing flexibility of a monorepo.
Example: Lamda util functions in a bit component collectionTo use Bit , make sure your component is re-usable and strictly obeys the SRP. Our Password implementation is a good example of a component shareable using Bit.
To move our implementation to Bit is super simple, first install the bit CLI globally.
$ npm i bit-bin -g
Login (after signing up on bit.dev )
$ bit login
Then, init a Bit enviorment in the project folder:
$ bit init
This will create a bit.json and .bitmap files in your root folder.
Now, we add the pswnode component
$ bit add src/pswnode.js tracking 1 new component
Bit will add the pswnode.js to its list of staged files.
To make our component backward compatible, let’s configure a Bit compiler:
$ bit import bit.envs/compilers/babel --compiler
This command will install the Bit Babel transpiler and set it as our default build step for all components in our project.
Next, we tag our component with a version:
$ bit tag --all 0.1 --message "initial message version"
Move over to Bit’s playground bit.dev and create a collection .
You have a collection name. Publish your component to the Bit registry:
$ bit export <account-name>.<collection-name> exported 1 component to <account-name>.<collection-name>
Hooray!!:ok_woman: all other devs can see your component!!
Conclusion
You can now proudly tell Git Bash that you now know how it works.
There are many more ways to implement the password field in the terminal using Nodejs. I urge you to implement yours, I would like to see what you can come up with.
If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email or DM me
Thanks !!!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK