diff --git a/Cargo.lock b/Cargo.lock index 5e310bf17..ea7482aed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,7 +498,7 @@ dependencies = [ [[package]] name = "butterflow-core" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "async-trait", "butterflow-models", @@ -525,7 +525,7 @@ dependencies = [ [[package]] name = "butterflow-models" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "chrono", "regex", @@ -541,7 +541,7 @@ dependencies = [ [[package]] name = "butterflow-runners" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "async-trait", "butterflow-models", @@ -554,7 +554,7 @@ dependencies = [ [[package]] name = "butterflow-scheduler" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "butterflow-models", "chrono", @@ -571,7 +571,7 @@ dependencies = [ [[package]] name = "butterflow-state" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "async-trait", "butterflow-models", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "codemod" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "anyhow", "ast-grep-language", @@ -818,7 +818,7 @@ dependencies = [ [[package]] name = "codemod-sandbox" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "ast-grep-config", "ast-grep-core", @@ -861,7 +861,7 @@ dependencies = [ [[package]] name = "codemod-telemetry" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "async-trait", "posthog-rs", @@ -7320,7 +7320,7 @@ dependencies = [ [[package]] name = "xtask" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" dependencies = [ "butterflow-models", "clap", diff --git a/Cargo.toml b/Cargo.toml index 1bf7c00d8..740a0bda7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ documentation = "https://docs.rs/butterflow" repository = "https://github.com/codemod-com/butterflow" license = "MIT" rust-version = "1.70" -version = "1.0.0-rc.16" +version = "1.0.0-rc.17" [workspace.dependencies] butterflow-models = { path = "crates/models" } diff --git a/crates/cli/src/commands/login.rs b/crates/cli/src/commands/login.rs index 2e625bdb5..00302df5b 100644 --- a/crates/cli/src/commands/login.rs +++ b/crates/cli/src/commands/login.rs @@ -1,8 +1,11 @@ use anyhow::{anyhow, Result}; use clap::Args; use log::{info, warn}; +use reqwest::Client; use crate::ascii_art::print_ascii_art; +use crate::auth::storage::StoredAuth; +use crate::auth::types::{AuthTokens, UserInfo}; use crate::auth::{OidcClient, TokenStorage}; #[derive(Args, Debug)] @@ -13,6 +16,35 @@ pub struct Command { /// Organization or user scope for publishing #[arg(long)] scope: Option, + /// API key for authentication (alternative to OAuth flow) + #[arg(long)] + api_key: Option, +} + +async fn validate_api_key_and_get_user_info(registry_url: &str, api_key: &str) -> Result { + let client = Client::new(); + let user_info_url = format!("{registry_url}/api/auth/oauth2/userinfo"); + + let response = client + .get(&user_info_url) + .bearer_auth(api_key) + .send() + .await + .map_err(|e| anyhow!("Failed to validate API key: {}", e))?; + + if !response.status().is_success() { + return Err(anyhow!( + "API key validation failed: HTTP {} - Please check your API key", + response.status() + )); + } + + let user_info: UserInfo = response + .json() + .await + .map_err(|e| anyhow!("Failed to parse user information: {}", e))?; + + Ok(user_info) } pub async fn handler(args: &Command) -> Result<()> { @@ -33,6 +65,59 @@ pub async fn handler(args: &Command) -> Result<()> { info!("Authenticating with registry: {registry_url}"); + // Handle API key authentication + if let Some(api_key) = &args.api_key { + info!("Using API key authentication"); + + // Validate API key and get user info + let user_info = validate_api_key_and_get_user_info(®istry_url, api_key).await?; + + // Create auth tokens for API key + let auth_tokens = AuthTokens { + access_token: api_key.clone(), + refresh_token: None, // API keys don't have refresh tokens + expires_at: None, // API keys typically don't expire + scope: vec![ + "read".to_string(), + "write".to_string(), + "publish".to_string(), + ], // Default scopes + token_type: "Bearer".to_string(), + }; + + // Create stored auth + let stored_auth = StoredAuth { + tokens: auth_tokens, + user: user_info.clone(), + registry: registry_url.clone(), + }; + + // Save authentication + storage.save_auth(&stored_auth)?; + + println!( + "✓ Successfully logged in with API key as {}", + user_info.username + ); + + if let Some(scope) = &args.scope { + info!("Setting default publish scope to {scope}"); + // TODO: Save scope preference to config + } + + if let Some(organizations) = &user_info.organizations { + println!("This API key can only publish to the following organizations:"); + for org in organizations { + println!(" - {} ({})", org.name, org.role); + } + } + + println!("\nYou can now publish packages using 'codemod publish'"); + print_ascii_art(); + return Ok(()); + } + + // Continue with OIDC flow if no API key provided let oidc_client = OidcClient::new(registry_url.clone(), registry_config)?; // Check current auth status pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy