use is_terminal::IsTerminal;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthTransport {
Browser,
DeviceCode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AutoAuth {
Proceed(AuthTransport),
FailFast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExecutionContext {
pub json: bool,
pub ci: bool,
pub stdout_tty: bool,
pub stdin_tty: bool,
pub agent_harness: bool,
pub browser_reachable: bool,
}
impl ExecutionContext {
pub fn detect(json: bool, ci: bool) -> Self {
Self {
json,
ci: ci || crate::config::Configs::env_is_ci(),
stdout_tty: std::io::stdout().is_terminal(),
stdin_tty: std::io::stdin().is_terminal(),
agent_harness: crate::telemetry::is_agent_harness(),
browser_reachable: !crate::commands::login::is_likely_headless(),
}
}
pub fn agent_implicit_consent(&self) -> bool {
self.agent_harness && !self.stdin_tty
}
pub fn login_transport(&self, browserless: bool) -> AuthTransport {
if browserless || !self.browser_reachable {
AuthTransport::DeviceCode
} else {
AuthTransport::Browser
}
}
pub fn auto_auth(&self, browserless: bool) -> AutoAuth {
if self.json || self.ci {
return AutoAuth::FailFast;
}
if !self.stdout_tty && !self.agent_implicit_consent() {
return AutoAuth::FailFast;
}
AutoAuth::Proceed(self.login_transport(browserless))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx(
json: bool,
ci: bool,
stdout_tty: bool,
stdin_tty: bool,
agent_harness: bool,
browser_reachable: bool,
) -> ExecutionContext {
ExecutionContext {
json,
ci,
stdout_tty,
stdin_tty,
agent_harness,
browser_reachable,
}
}
#[test]
fn login_uses_browser_when_reachable_and_not_browserless() {
let c = ctx(false, false, true, true, false, true);
assert_eq!(c.login_transport(false), AuthTransport::Browser);
}
#[test]
fn login_uses_device_code_when_browserless_requested() {
let c = ctx(false, false, true, true, false, true);
assert_eq!(c.login_transport(true), AuthTransport::DeviceCode);
}
#[test]
fn login_uses_device_code_when_no_browser_reachable() {
let c = ctx(false, false, true, true, false, false);
assert_eq!(c.login_transport(false), AuthTransport::DeviceCode);
}
#[test]
fn auto_auth_proceeds_in_a_plain_interactive_terminal() {
let c = ctx(false, false, true, true, false, true);
assert_eq!(
c.auto_auth(false),
AutoAuth::Proceed(AuthTransport::Browser)
);
}
#[test]
fn auto_auth_proceeds_under_an_agent_harness_with_piped_stdio() {
let c = ctx(false, false, false, false, true, true);
assert_eq!(
c.auto_auth(false),
AutoAuth::Proceed(AuthTransport::Browser)
);
}
#[test]
fn auto_auth_fails_fast_in_json_mode() {
let c = ctx(true, false, true, true, false, true);
assert_eq!(c.auto_auth(false), AutoAuth::FailFast);
}
#[test]
fn auto_auth_fails_fast_in_ci_mode() {
let c = ctx(false, true, true, true, false, true);
assert_eq!(c.auto_auth(false), AutoAuth::FailFast);
}
#[test]
fn auto_auth_uses_device_code_on_ssh_with_an_interactive_human() {
let c = ctx(false, false, true, true, false, false);
assert_eq!(
c.auto_auth(false),
AutoAuth::Proceed(AuthTransport::DeviceCode)
);
}
#[test]
fn auto_auth_uses_device_code_on_ssh_under_a_watching_agent() {
let c = ctx(false, false, false, false, true, false);
assert_eq!(
c.auto_auth(false),
AutoAuth::Proceed(AuthTransport::DeviceCode)
);
}
#[test]
fn auto_auth_fails_fast_on_ssh_with_no_human() {
let c = ctx(false, false, false, false, false, false);
assert_eq!(c.auto_auth(false), AutoAuth::FailFast);
}
#[test]
fn auto_auth_fails_fast_when_stdout_piped_and_no_agent() {
let c = ctx(false, false, false, false, false, true);
assert_eq!(c.auto_auth(false), AutoAuth::FailFast);
}
#[test]
fn auto_auth_does_not_treat_stale_agent_export_in_a_tty_as_consent() {
let c = ctx(false, false, true, true, true, true);
assert!(!c.agent_implicit_consent());
assert_eq!(
c.auto_auth(false),
AutoAuth::Proceed(AuthTransport::Browser)
);
}
}