use std::process::ExitCode;
use colored::Colorize as _;
use s2_sdk::{S2, error::ErrorCode, types::ListBasinsInput};
use crate::{
config::{CliConfig, CredentialStore, DEFAULT_ACCOUNT_ENDPOINT, sdk_config},
error::{CliConfigError, CliError, TokenSource},
login::{LoginError, effective_endpoints, resolve_access_token, uses_loopback_endpoints},
update,
};
pub async fn status() -> Result<ExitCode, CliError> {
let (config, credential) = match resolve_access_token().await {
Ok(resolved) => resolved,
Err(LoginError::Config(CliConfigError::MissingAccessToken)) => {
let config = crate::config::load_cli_config()?;
print_heading(&config);
eprintln!(" {}", "✗ Not logged in".red().bold());
eprintln!(" - Run `s2 login`.");
print_configured_credentials(&config, None);
return Ok(ExitCode::FAILURE);
}
Err(error) => {
let config = crate::config::load_cli_config()?;
print_heading(&config);
print_unverified(&configured_credential_label(&config), &error);
print_configured_credentials(&config, configured_source(&config));
return Ok(ExitCode::FAILURE);
}
};
print_heading(&config);
let label = credential_label(&config, credential.source());
if let Err(error) = credential.validate_destination(&config) {
print_unverified(&label, &error);
print_configured_credentials(&config, Some(credential.source()));
return Ok(ExitCode::FAILURE);
}
let sdk = match sdk_config(&config, credential.access_token(), update::user_agent()) {
Ok(sdk) => credential.configure_sdk(sdk),
Err(error) => {
print_unverified(&label, &error);
print_configured_credentials(&config, Some(credential.source()));
return Ok(ExitCode::FAILURE);
}
};
let s2 = match S2::new(sdk) {
Ok(s2) => s2,
Err(error) => {
print_unverified(&label, &error);
print_configured_credentials(&config, Some(credential.source()));
return Ok(ExitCode::FAILURE);
}
};
let result = s2.list_basins(ListBasinsInput::new().with_limit(1)).await;
let exit_code = match result {
Ok(_) => {
if uses_loopback_endpoints(&config) {
print_local_connection();
} else {
print_authenticated(&label);
}
ExitCode::SUCCESS
}
Err(error)
if error.server_error().and_then(|error| error.known_code())
== Some(ErrorCode::PermissionDenied) =>
{
print_authenticated(&label);
ExitCode::SUCCESS
}
Err(error)
if matches!(
error.server_error().and_then(|error| error.known_code()),
Some(ErrorCode::Authn | ErrorCode::AccessTokenNotFound)
) =>
{
eprintln!(
" {}",
format!("✗ Authentication failed for {label}").red().bold()
);
eprintln!(" - {}", recovery_command(credential.source()));
ExitCode::FAILURE
}
Err(error) => {
print_unverified(&label, &error);
ExitCode::FAILURE
}
};
print_configured_credentials(&config, Some(credential.source()));
Ok(exit_code)
}
fn print_authenticated(label: &str) {
eprintln!(" {}", format!("✓ Logged in with {label}").green().bold());
}
fn print_local_connection() {
eprintln!(" {}", "✓ Connected to local S2 endpoint".green().bold());
eprintln!(" - Credentials are not verified for local endpoints.");
}
fn print_unverified(label: &str, error: &impl std::fmt::Display) {
eprintln!(
" {}",
format!("! Could not verify {label}").yellow().bold()
);
eprintln!(" - {error}");
}
fn print_heading(config: &CliConfig) {
let (account_endpoint, _) = effective_endpoints(config);
let heading = if account_endpoint == DEFAULT_ACCOUNT_ENDPOINT {
"s2.dev".to_owned()
} else {
reqwest::Url::parse(&account_endpoint)
.ok()
.and_then(|url| {
let host = url.host_str()?;
Some(match url.port() {
Some(port) => format!("{host}:{port}"),
None => host.to_owned(),
})
})
.unwrap_or_else(|| account_endpoint.clone())
};
eprintln!("{}", heading.bold());
}
fn configured_credential_label(config: &CliConfig) -> String {
configured_source(config).map_or_else(
|| "authentication".to_owned(),
|source| credential_label(config, source),
)
}
fn configured_source(config: &CliConfig) -> Option<TokenSource> {
if std::env::var_os("S2_ACCESS_TOKEN").is_some_and(|value| !value.is_empty()) {
return Some(TokenSource::Environment);
}
match config.auth_method {
Some(crate::config::AuthMethod::BrowserLogin) if config.oauth.is_some() => {
Some(TokenSource::BrowserLogin)
}
Some(crate::config::AuthMethod::AccessToken) if config.has_stored_access_token() => {
Some(stored_access_token_source(config))
}
Some(_) => None,
None if config.has_stored_access_token() => Some(stored_access_token_source(config)),
None if config.oauth.is_some() => Some(TokenSource::BrowserLogin),
None => None,
}
}
fn stored_access_token_source(config: &CliConfig) -> TokenSource {
if config.stored_access_token.is_some() {
TokenSource::StoredAccessToken
} else {
TokenSource::ConfigFile
}
}
fn credential_label(config: &CliConfig, source: TokenSource) -> String {
match source {
TokenSource::Environment => "S2_ACCESS_TOKEN".to_owned(),
TokenSource::BrowserLogin => browser_login_label(config),
TokenSource::StoredAccessToken | TokenSource::ConfigFile => access_token_label(config),
}
}
fn browser_login_label(config: &CliConfig) -> String {
let storage = config
.oauth
.as_ref()
.map(|session| storage_label(session.credential_store));
storage.map_or_else(
|| "browser login".to_owned(),
|storage| format!("browser login ({storage})"),
)
}
fn access_token_label(config: &CliConfig) -> String {
if let Some(reference) = config.stored_access_token.as_ref() {
format!(
"access token ({})",
storage_label(reference.credential_store)
)
} else if config.has_legacy_access_token() {
"access token (config.toml)".to_owned()
} else {
"access token".to_owned()
}
}
fn storage_label(store: CredentialStore) -> &'static str {
match store {
CredentialStore::Keyring => "keyring",
CredentialStore::File => "private file",
}
}
fn print_configured_credentials(config: &CliConfig, active: Option<TokenSource>) {
let browser_active = matches!(active, Some(TokenSource::BrowserLogin));
let access_token_active = matches!(
active,
Some(TokenSource::StoredAccessToken | TokenSource::ConfigFile)
);
if !browser_active && config.oauth.is_some() {
eprintln!(" - Also configured: {}", browser_login_label(config));
}
if !access_token_active && config.has_stored_access_token() {
eprintln!(" - Also configured: {}", access_token_label(config));
}
if config.has_legacy_access_token() {
eprintln!(
"{}",
" ! The access token is stored in plaintext in config.toml.\n Run `s2 auth access-token migrate` to secure it."
.yellow()
);
}
let has_file_storage = config
.oauth
.as_ref()
.is_some_and(|session| session.credential_store == CredentialStore::File)
|| config
.stored_access_token
.as_ref()
.is_some_and(|reference| reference.credential_store == CredentialStore::File);
if has_file_storage {
eprintln!(
"{}",
" ! Credentials are stored in a private plaintext file.".yellow()
);
}
}
fn recovery_command(source: TokenSource) -> &'static str {
match source {
TokenSource::BrowserLogin => "Run `s2 login` again.",
TokenSource::Environment => "Set S2_ACCESS_TOKEN to a valid access token.",
TokenSource::StoredAccessToken | TokenSource::ConfigFile => {
"Run `s2 auth access-token set` to replace it."
}
}
}