Search code examples
crystal-lang

echo "*" for passwords input in crystal-lang


Is there a way to echo "*" or some other character when processing passwords? Using STDIN.noecho works but doesn't provide any user feedback. console.cr only provides raw and noecho.

Thanks. Still looking...


Solution

  • You're on the right path with STDIN.noecho! Effectively what you needed to do is read from STDIN in raw mode and echo out * every time a key is pressed. Then you'd listen for backspace so you can move the cursor back one and replace with a space, then replace that space with a * when the user typed another character. You also needed to check for the enter and Control+C being pressed as the terminal captures and gives that all to you in raw mode.

    I've based this off the bash implementation found here in the second comment by user Alfe. Update: Changed the code to be inside blocks to hopefully eliminate possible terminal issues.

    # Fix for crystal bug
    STDIN.blocking = true
    
    # STDIN chars without buffering
    STDIN.raw do
        # Dont echo out to the terminal
        STDIN.noecho do
            final_password = ""
            while char = STDIN.read_char
                # If we have a backspace
                if char == '\u{7f}'
                    # Move the cursor back 1 char
                    if final_password != ""
                        STDOUT << "\b \b"
                        STDOUT.flush
                        final_password = final_password[0...-1]
                    end
                    next
                elsif char == '\r'
                    # Enter was pressed we're finished!
                    break
                elsif char == '\u{3}'
                    # Control + C was pressed. Get outta here
                    Process.exit 0
                elsif char.ascii_control?
                    # A Control + [] char was pressed. Not valid for a password
                    next
                end
    
                STDOUT << "*"
                STDOUT.flush
                final_password += char
            end
    
            puts final_password
        end
    end
    
    # Resetting when finished
    STDIN.blocking = false
    

    If this helps you please be sure to mark this as answered!