Skip to content

Commit 2bc6e43

Browse files
authored
feat(cli): add API key authentication to login command (#1642)
1 parent b8c23b8 commit 2bc6e43

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

crates/cli/src/commands/login.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use anyhow::{anyhow, Result};
22
use clap::Args;
33
use log::{info, warn};
4+
use reqwest::Client;
45

56
use crate::ascii_art::print_ascii_art;
7+
use crate::auth::storage::StoredAuth;
8+
use crate::auth::types::{AuthTokens, UserInfo};
69
use crate::auth::{OidcClient, TokenStorage};
710

811
#[derive(Args, Debug)]
@@ -13,6 +16,35 @@ pub struct Command {
1316
/// Organization or user scope for publishing
1417
#[arg(long)]
1518
scope: Option<String>,
19+
/// API key for authentication (alternative to OAuth flow)
20+
#[arg(long)]
21+
api_key: Option<String>,
22+
}
23+
24+
async fn validate_api_key_and_get_user_info(registry_url: &str, api_key: &str) -> Result<UserInfo> {
25+
let client = Client::new();
26+
let user_info_url = format!("{registry_url}/api/auth/oauth2/userinfo");
27+
28+
let response = client
29+
.get(&user_info_url)
30+
.bearer_auth(api_key)
31+
.send()
32+
.await
33+
.map_err(|e| anyhow!("Failed to validate API key: {}", e))?;
34+
35+
if !response.status().is_success() {
36+
return Err(anyhow!(
37+
"API key validation failed: HTTP {} - Please check your API key",
38+
response.status()
39+
));
40+
}
41+
42+
let user_info: UserInfo = response
43+
.json()
44+
.await
45+
.map_err(|e| anyhow!("Failed to parse user information: {}", e))?;
46+
47+
Ok(user_info)
1648
}
1749

1850
pub async fn handler(args: &Command) -> Result<()> {
@@ -33,6 +65,59 @@ pub async fn handler(args: &Command) -> Result<()> {
3365

3466
info!("Authenticating with registry: {registry_url}");
3567

68+
// Handle API key authentication
69+
if let Some(api_key) = &args.api_key {
70+
info!("Using API key authentication");
71+
72+
// Validate API key and get user info
73+
let user_info = validate_api_key_and_get_user_info(&registry_url, api_key).await?;
74+
75+
// Create auth tokens for API key
76+
let auth_tokens = AuthTokens {
77+
access_token: api_key.clone(),
78+
refresh_token: None, // API keys don't have refresh tokens
79+
expires_at: None, // API keys typically don't expire
80+
scope: vec![
81+
"read".to_string(),
82+
"write".to_string(),
83+
"publish".to_string(),
84+
], // Default scopes
85+
token_type: "Bearer".to_string(),
86+
};
87+
88+
// Create stored auth
89+
let stored_auth = StoredAuth {
90+
tokens: auth_tokens,
91+
user: user_info.clone(),
92+
registry: registry_url.clone(),
93+
};
94+
95+
// Save authentication
96+
storage.save_auth(&stored_auth)?;
97+
98+
println!(
99+
"✓ Successfully logged in with API key as {}",
100+
user_info.username
101+
);
102+
103+
if let Some(scope) = &args.scope {
104+
info!("Setting default publish scope to {scope}");
105+
// TODO: Save scope preference to config
106+
}
107+
108+
if let Some(organizations) = &user_info.organizations {
109+
println!("This API key can only publish to the following organizations:");
110+
for org in organizations {
111+
println!(" - {} ({})", org.name, org.role);
112+
}
113+
}
114+
115+
println!("\nYou can now publish packages using 'codemod publish'");
116+
print_ascii_art();
117+
return Ok(());
118+
}
119+
120+
// Continue with OIDC flow if no API key provided
36121
let oidc_client = OidcClient::new(registry_url.clone(), registry_config)?;
37122

38123
// Check current auth status

0 commit comments

Comments
 (0)
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