Something went wrong. Try again.
⚘ use your pds as a git remote if you want to ⚘
Something went wrong. Try again.
6.7 kB · 213 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214//! git-remote-pds: PDS-backed git remote helper.//!//! When invoked by git as a remote helper (3 args, not "auth")://! git-remote-pds <remote-name> <url>//!//! When invoked directly for credential management://! git-remote-pds auth login --pds-url <url> --handle <handle>//! git-remote-pds auth status//! git-remote-pds auth logout --handle <handle>
mod remote_helper;
use clap::{Parser, Subcommand};
#[derive(Parser)]#[command(name = "git-remote-pds", about = "PDS-backed git remote helper")]struct Cli { #[command(subcommand)] command: CliCommand,}
#[derive(Subcommand)]enum CliCommand { /// Manage authentication credentials Auth { #[command(subcommand)] action: AuthAction, },}
#[derive(Subcommand)]enum AuthAction { /// Log in to a PDS using handle + password (createSession) Login { /// PDS server URL #[arg(long)] pds_url: String, /// AT Protocol handle #[arg(long)] handle: String, }, /// Log in via browser-based AT Protocol OAuth (DPoP-bound tokens) OauthLogin { /// AT Protocol handle #[arg(long)] handle: String, /// PDS server URL (optional, auto-discovered from handle) #[arg(long)] pds_url: Option<String>, /// Port for the localhost OAuth callback server #[arg(long, default_value = "8271")] port: u16, }, /// Show stored credentials Status, /// Remove stored credentials for a handle Logout { /// AT Protocol handle to log out #[arg(long)] handle: String, },}
#[tokio::main]async fn main() { // initialize logging to stderr (stdout is reserved for protocol) tracing_subscriber::fmt() .with_writer(std::io::stderr) .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init();
let args: Vec<String> = std::env::args().collect();
// detect remote helper mode: git invokes us as `git-remote-pds <name> <url>` // with exactly 3 args and the second arg is not "auth" if args.len() == 3 && args[1] != "auth" { let remote_name = &args[1]; let url = &args[2];
if let Err(e) = remote_helper::run(remote_name, url).await { eprintln!("git-remote-pds: error: {}", e); std::process::exit(1); } return; }
// CLI mode — parse with clap let cli = Cli::parse();
match cli.command { CliCommand::Auth { action } => match action { AuthAction::Login { pds_url, handle } => { handle_login(&pds_url, &handle).await; } AuthAction::OauthLogin { handle, pds_url, port, } => { handle_oauth_login(&handle, pds_url.as_deref(), port).await; } AuthAction::Status => { handle_status(); } AuthAction::Logout { handle } => { handle_logout(&handle); } }, }}
/// Handles `auth oauth-login` — browser-based OAuth login with DPoP.async fn handle_oauth_login(handle: &str, pds_url: Option<&str>, port: u16) { if let Err(e) = git_remote_pds::oauth_login::oauth_login(handle, pds_url, port).await { eprintln!("OAuth login failed: {}", e); std::process::exit(1); }}
/// Handles `auth login` — reads password from env or stdin, logs in, stores credential.async fn handle_login(pds_url: &str, handle: &str) { // read password from PDS_PASSWORD env var or stdin let password = match std::env::var("PDS_PASSWORD") { Ok(pw) => pw, Err(_) => { eprint!("Password: "); let mut pw = String::new(); std::io::stdin() .read_line(&mut pw) .expect("failed to read password"); pw.trim().to_string() } };
match git_remote_pds::auth::login_and_store(pds_url, handle, &password).await { Ok(cred) => { eprintln!("logged in as {} ({})", cred.handle, cred.did); } Err(e) => { eprintln!("login failed: {}", e); std::process::exit(1); } }}
/// Handles `auth status` — prints stored credentials with details.fn handle_status() { match git_remote_pds::auth::load_config() { Ok(config) => { if config.credentials.is_empty() { eprintln!("no stored credentials"); return; } let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_secs() as i64) .unwrap_or(0);
for (i, (handle, cred)) in config.credentials.iter().enumerate() { if i > 0 { eprintln!(); } eprintln!("{}", handle); eprintln!(" did: {}", cred.did); eprintln!(" pds: {}", cred.pds_url);
// auth method and token status if cred.dpop_key.is_some() { eprintln!(" auth: OAuth (DPoP)"); match cred.token_expiry { Some(expiry) => { let diff = expiry - now; if diff > 0 { let mins = diff / 60; let secs = diff % 60; eprintln!(" token: valid (expires in {}m {}s)", mins, secs); } else { let ago = -diff; let mins = ago / 60; eprintln!(" token: expired ({}m ago)", mins); } } None => { eprintln!(" token: unknown expiry"); } } } else { eprintln!(" auth: createSession (Bearer)"); } } } Err(e) => { eprintln!("failed to load config: {}", e); std::process::exit(1); } }}
/// Handles `auth logout` — removes stored credential.fn handle_logout(handle: &str) { match git_remote_pds::auth::logout(handle) { Ok(true) => { eprintln!("logged out {}", handle); } Ok(false) => { eprintln!("no stored credential for {}", handle); } Err(e) => { eprintln!("logout failed: {}", e); std::process::exit(1); } }}