From b834701bec26fedbd2db7f85c055f001de434961 Mon Sep 17 00:00:00 2001 From: Anthony Lukach Date: Wed, 14 Oct 2026 09:29:40 -0700 Subject: [PATCH 1/5] feat: auth status, the cached login and a role's credentials at a glance `source-coop auth status` reports, for one environment and role, whether you're logged in (refresh token, ID and access token expiry) and when the role's cached credentials expire. It reads the cache only: nothing is refreshed and nothing goes over the network. --proxy-url and --role-arn pick the environment and role, as for creds. Co-Authored-By: Claude Opus 5.5 --- README.md | 16 +++++ src/main.rs | 181 ++++++++++++++++++++++++++++++++++++++++++++++++- src/session.rs | 2 +- 3 files changed, 197 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6dd87b..b5c8c88 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,22 @@ Credentials are cached on the server (see [File fallback](#file-fallback)), and source-coop --version ``` +### Checking login status + +`auth status` shows whether you're logged in, and when the cached tokens and a role's credentials expire. It works from the cache: nothing is refreshed and nothing is sent over the network. + +```bash +source-coop auth status +``` + +``` +Environment: production (https://data.source.coop) +Login session: logged in (refresh token: yes; ID token expires 2026-10-13T18:00:00Z; access token for https://source.coop expires 2026-10-13T18:00:00Z) +Role _default: credentials expire 2026-10-13T18:30:00Z +``` + +The keyring can't list its entries, so `auth status` reports on one environment and role. Pass `--proxy-url` and `--role-arn` (or set `SOURCE_PROXY_URL` and `SOURCE_ROLE_ARN`) to check others. + ### Setting credentials on the environment After logging in, you can export cached credentials as environment variables: diff --git a/src/main.rs b/src/main.rs index 7d355fe..eeb7193 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,7 @@ struct Cli { #[derive(Subcommand)] enum Commands { - /// Log in, and print tokens for the source.coop API + /// Log in, see whether you are, and print tokens for the source.coop API #[command(subcommand)] Auth(AuthCommand), /// Output cached credentials as credential_process JSON, shell env vars, @@ -114,11 +114,25 @@ enum AuthCommand { /// Log in through the browser, keeping the session and a role's S3 /// credentials for `creds` and the API Login(LoginArgs), + /// Show the cached login session and a role's credentials, without + /// refreshing either or going to the network + Status(StatusArgs), /// Print an access token for the source.coop API, refreshing it if expired /// (e.g. `curl -H "Authorization: Bearer $(source-coop auth token)" ...`) Token(TokenArgs), } +#[derive(Parser)] +struct StatusArgs { + /// S3 proxy URL: selects the environment to report on + #[arg(long, env = "SOURCE_PROXY_URL", default_value = defaults::PROXY_URL)] + proxy_url: String, + + /// Role ARN whose cached credentials to report on + #[arg(long, env = "SOURCE_ROLE_ARN", default_value = defaults::ROLE_ARN)] + role_arn: String, +} + #[derive(Parser)] struct TokenArgs { /// S3 proxy URL of the environment whose login session to use @@ -228,6 +242,11 @@ async fn main() { fail(e); } } + Commands::Auth(AuthCommand::Status(args)) => { + if let Err(e) = run_status(args) { + fail(e); + } + } Commands::Auth(AuthCommand::Token(args)) => { if let Err(e) = run_auth_token(args, verbose).await { fail(e); @@ -263,6 +282,100 @@ async fn main() { } } +fn run_status(args: StatusArgs) -> Result<(), String> { + let env = cache::env(&args.proxy_url)?; + let session = cache::read_session(env.as_deref())?; + let entry = cache::read_credentials(env.as_deref(), &args.role_arn)?; + print!( + "{}", + status_report( + &args.proxy_url, + env.as_deref(), + &args.role_arn, + session.as_ref(), + entry.as_ref().map(|e| &e.creds), + chrono::Utc::now().timestamp(), + ) + ); + Ok(()) +} + +/// `