pakker/src/cli/commands/credentials_set.rs
NotAShelf 7ed425683d
cli: flush credential verification prompts
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icbca98ba56bc30e85d0f3972af616ca96a6a6964
2026-05-03 03:44:48 +03:00

219 lines
5.1 KiB
Rust

use std::{io::Write, time::Duration};
use crate::{
error::{PakkerError, Result},
http,
model::{PakkerCredentialsFile, set_keyring_secret},
ui_utils::{prompt_secret, prompt_yes_no},
};
pub async fn execute(
curseforge_api_key: Option<String>,
modrinth_token: Option<String>,
github_access_token: Option<String>,
) -> Result<()> {
let mut cf_key = curseforge_api_key;
let mut mr_token = modrinth_token;
let mut gh_token = github_access_token;
let any_cli_args =
cf_key.is_some() || mr_token.is_some() || gh_token.is_some();
// Enter interactive mode when no CLI args provided
if !any_cli_args {
println!("No credentials provided via command line.");
println!();
if let Some(key) =
prompt_secret("CurseForge API key (press Enter to skip)")?
{
cf_key = Some(key);
}
if let Some(token) = prompt_secret("Modrinth token (press Enter to skip)")?
{
mr_token = Some(token);
}
if let Some(token) =
prompt_secret("GitHub access token (press Enter to skip)")?
{
gh_token = Some(token);
}
}
let updated_any =
cf_key.is_some() || mr_token.is_some() || gh_token.is_some();
if !updated_any {
println!("No credentials to save.");
return Ok(());
}
// Verify credentials before saving
let client = http::create_http_client();
let mut verified = Vec::new();
if let Some(ref key) = cf_key {
print!("Verifying CurseForge API key... ");
std::io::stdout().flush().ok();
match verify_curseforge(&client, key).await {
Ok(()) => {
println!("valid");
verified.push("CurseForge");
},
Err(e) => {
println!("failed ({e})");
if !prompt_yes_no(
"CurseForge key appears invalid. Save anyway?",
false,
false,
)? {
cf_key = None;
}
},
}
}
if let Some(ref token) = mr_token {
print!("Verifying Modrinth token... ");
std::io::stdout().flush().ok();
match verify_modrinth(&client, token).await {
Ok(()) => {
println!("valid");
verified.push("Modrinth");
},
Err(e) => {
println!("failed ({e})");
if !prompt_yes_no(
"Modrinth token appears invalid. Save anyway?",
false,
false,
)? {
mr_token = None;
}
},
}
}
if let Some(ref token) = gh_token {
print!("Verifying GitHub access token... ");
std::io::stdout().flush().ok();
match verify_github(&client, token).await {
Ok(()) => {
println!("valid");
verified.push("GitHub");
},
Err(e) => {
println!("failed ({e})");
if !prompt_yes_no(
"GitHub token appears invalid. Save anyway?",
false,
false,
)? {
gh_token = None;
}
},
}
}
let mut creds = PakkerCredentialsFile::load()?;
if let Some(key) = cf_key {
let key = key.trim().to_string();
if !key.is_empty() {
set_keyring_secret("curseforge_api_key", &key)?;
creds.curseforge_api_key = Some(key);
}
}
if let Some(token) = mr_token {
let token = token.trim().to_string();
if !token.is_empty() {
set_keyring_secret("modrinth_token", &token)?;
creds.modrinth_token = Some(token);
}
}
if let Some(token) = gh_token {
let token = token.trim().to_string();
if !token.is_empty() {
set_keyring_secret("github_access_token", &token)?;
creds.github_access_token = Some(token);
}
}
creds.save()?;
println!();
if verified.is_empty() {
println!("Credentials saved (unverified).");
} else {
println!("Credentials saved and verified: {}", verified.join(", "));
}
println!(
"Credentials file: {}",
PakkerCredentialsFile::get_path()?.display()
);
println!("Keyring service: pakker");
Ok(())
}
async fn verify_curseforge(
client: &reqwest::Client,
api_key: &str,
) -> Result<()> {
let response = client
.get("https://api.curseforge.com/v1/mods/238222")
.header("x-api-key", api_key)
.timeout(Duration::from_secs(10))
.send()
.await?;
if response.status().is_success() {
Ok(())
} else {
Err(PakkerError::PlatformApiError(format!(
"HTTP {}",
response.status()
)))
}
}
async fn verify_modrinth(client: &reqwest::Client, token: &str) -> Result<()> {
let response = client
.get("https://api.modrinth.com/v2/user")
.header("Authorization", token)
.timeout(Duration::from_secs(10))
.send()
.await?;
if response.status().is_success() {
Ok(())
} else {
Err(PakkerError::PlatformApiError(format!(
"HTTP {}",
response.status()
)))
}
}
async fn verify_github(client: &reqwest::Client, token: &str) -> Result<()> {
let response = client
.get("https://api.github.com/user")
.header("Authorization", format!("Bearer {token}"))
.header("User-Agent", "pakker")
.timeout(Duration::from_secs(10))
.send()
.await?;
if response.status().is_success() {
Ok(())
} else {
Err(PakkerError::PlatformApiError(format!(
"HTTP {}",
response.status()
)))
}
}