fh icon indicating copy to clipboard operation
fh copied to clipboard

`fh login` does not properly read token at the prompt

Open antoineco opened this issue 10 months ago • 4 comments

When pasting the FlakeHub token at the fh login prompt, the input isn't read properly.

I first see a prompt without cursor:

$ fh login
Log in to FlakeHub: https://flakehub.com/token/create?description=FlakeHub+CLI+on+...
And then follow the prompts below:

> Paste your token here:

Then, after pasting, the input line is garbled and the prompt appears again, this time with ******** characters:

$ fh login
Log in to FlakeHub: https://flakehub.com/token/create?description=FlakeHub+CLI+on+...
And then follow the prompts below:

akehub1_redactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredactedredacted> Paste your token here: ********
Error:
   0: Checking the validity of the provided token
   1: The provided token was invalid. Please try again, or contact [email protected] if the problem persists.

Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.

Consider reporting this error using this URL: https://github.com/DeterminateSystems/fh/issues/new?...
Image

In the screenshot above, the second prompt seems to appear at the beginning of a new line, but when I copy the text on screen there is no newline character before that prompt. I think the program is trying to adjust to the terminal's current width.

After the command returns, my cursor does not come back and I have to issue a reset command to fix my terminal.

Shell: zsh 5.9 (aarch64-apple-darwin24.2.0) Terminal: wezterm 20250116-151613-6c443bee

Metadata

key value
version 0.1.21
os macos
arch aarch64

antoineco avatar Feb 25 '25 15:02 antoineco

Hi @antoineco! I'd recommend using determinate-nixd login if you can (see: https://github.com/DeterminateSystems/fh/pull/179.) However, I'll try and get the token prompt working correctly here too.

grahamc avatar Apr 29 '25 03:04 grahamc

Ack. It's a doozy. Basically, we'd need to switch from inquire to requestty + crossterm. This is the only library we've found that properly uses terminal raw mode in a race-free way for handling larger pastes. To level with you, this isn't a super near term priority at the moment, but I'd be very glad to have a PR!

grahamc avatar Apr 29 '25 03:04 grahamc

Thanks for your response! I agree that it doesn't ruin the experience since tokens have a long lifetime and the tokens ends up in the right place despite the broken terminal (if I remember correctly).

Regarding a potential PR, is the code for determinate-nixd available somewhere?

antoineco avatar Apr 29 '25 05:04 antoineco

Unfortunately not right now :/, but here's the function we use for prompting there:

// Prompts for a FlakeHub token without confirmation.
#[tracing::instrument]
fn maybe_token(login_url: &str) -> color_eyre::Result<Option<String>> {
    if !std::io::IsTerminal::is_terminal(&std::io::stdin()) {
        return Err(color_eyre::eyre::eyre!(
            "stdin was not a terminal, cannot prompt for token"
        ));
    }

    requestty::symbols::set(requestty::symbols::ASCII);

    let ret = requestty::prompt_one(
        requestty::Question::input("ignore")
            .message(format!("Get a FlakeHub token from {login_url}"))
            .default("Press Enter to continue")
            .transform(|_, _, backend| write!(backend, ""))
            .build(),
    );
    if ret.is_err() {
        crossterm::terminal::disable_raw_mode()?;
        ret?;
    }

    let answer = requestty::prompt_one(
        requestty::Question::password("token")
            .message("Paste your FlakeHub token:")
            .mask('*')
            .build(),
    );

    let token = match answer {
        Ok(answer) => answer
            .as_string()
            .expect("token should have been String")
            .to_string(),
        Err(e) => {
            crossterm::terminal::disable_raw_mode()?;
            return Err(e)?;
        }
    };

    if token.is_empty() {
        Ok(None)
    } else {
        Ok(Some(token))
    }
}

grahamc avatar Apr 29 '25 10:04 grahamc