Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions src/cortex-cli/src/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use cortex_login::{
safe_format_key, save_auth_with_fallback,
};
use std::collections::HashSet;
use std::io::{IsTerminal, Read};
use std::io::{BufRead, IsTerminal, Read};
use std::path::PathBuf;

/// Check for duplicate config override keys and warn the user.
Expand Down Expand Up @@ -41,6 +41,14 @@ fn get_cortex_home() -> PathBuf {
})
}

/// Read and parse a logout confirmation response.
pub fn read_logout_confirmation<R: BufRead>(reader: &mut R) -> std::io::Result<bool> {
let mut input = String::new();
reader.read_line(&mut input)?;
let input = input.trim().to_lowercase();
Ok(input == "y" || input == "yes")
}

/// Run login with API key.
pub async fn run_login_with_api_key(config_overrides: CliConfigOverrides, api_key: String) -> ! {
check_duplicate_config_overrides(&config_overrides);
Expand Down Expand Up @@ -205,13 +213,18 @@ pub async fn run_logout(config_overrides: CliConfigOverrides, skip_confirmation:
);
let _ = std::io::Write::flush(&mut std::io::stderr());

let mut input = String::new();
if std::io::stdin().read_line(&mut input).is_ok() {
let input = input.trim().to_lowercase();
if input != "y" && input != "yes" {
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
match read_logout_confirmation(&mut stdin) {
Ok(true) => {}
Ok(false) => {
print_info("Logout cancelled.");
std::process::exit(0);
}
Err(e) => {
print_error(&format!("Failed to read logout confirmation: {e}"));
std::process::exit(1);
}
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/cortex-cli/tests/logout_confirmation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::io::{self, BufRead, Cursor, Read};

use cortex_cli::login::read_logout_confirmation;

struct FailingReader;

impl Read for FailingReader {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::other("stdin unavailable"))
}
}

impl BufRead for FailingReader {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
Err(io::Error::other("stdin unavailable"))
}

fn consume(&mut self, _amt: usize) {}
}

#[test]
fn logout_confirmation_accepts_yes_values() {
let mut lowercase = Cursor::new(b"yes\n");
assert!(read_logout_confirmation(&mut lowercase).unwrap());

let mut uppercase = Cursor::new(b"Y\n");
assert!(read_logout_confirmation(&mut uppercase).unwrap());
}

#[test]
fn logout_confirmation_rejects_empty_or_negative_values() {
let mut empty = Cursor::new(b"\n");
assert!(!read_logout_confirmation(&mut empty).unwrap());

let mut no = Cursor::new(b"no\n");
assert!(!read_logout_confirmation(&mut no).unwrap());
}

#[test]
fn logout_confirmation_propagates_read_errors() {
let mut reader = FailingReader;
let error = read_logout_confirmation(&mut reader).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::Other);
}