codeberg-cli 0.5.5

CLI Tool for codeberg similar to gh and glab
Documentation
use crate::actions::{GlobalArgs, text_manipulation::input_prompt_for};
use crate::types::context::BergContext;

use clap::Parser;
use forgejo_api::structs::VerifyGPGKeyOption;
use miette::{Context, IntoDiagnostic};

/// Verify a gpg key from your profile
#[derive(Parser, Debug)]
pub struct VerifyGpgArgs {
    /// The key id / fingerprint of the GPG key used to identify it
    key_id: String,
    /// optionally: for non-interactive workflows, you can specify the signature in a second
    /// invocation
    #[arg(short, long)]
    armored_signature: Option<String>,
}

impl VerifyGpgArgs {
    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
        let ctx = BergContext::new(self, global_args).await?;
        match ctx.global_args.non_interactive {
            true => verify_non_interactive(ctx).await,
            false => verify_interactive(ctx).await,
        }
    }
}

async fn verify_interactive(ctx: BergContext<VerifyGpgArgs>) -> miette::Result<()> {
    let signature = match ctx.args.armored_signature.clone() {
        Some(signature) => signature,
        None => {
            let token = ctx
                .client
                .get_verification_token()
                .await
                .into_diagnostic()
                .context("Could not get verification token from API!")?;
            let key_id = ctx.args.key_id.clone();
            let text = [
                "Please sign the following verification token and then send it back to the API".to_string(), 
                format!("Step 1: Sign with `echo \"{token}\" | gpg -a --default-key {key_id} --detach-sig`"), 
            ].join("\n");

            println!("{text}");

            inquire::Text::new(input_prompt_for("Step 2: Enter the signature here:").as_str())
                .prompt()
                .into_diagnostic()?
        }
    };

    send_signature(&ctx, signature).await?;

    Ok(())
}

async fn verify_non_interactive(ctx: BergContext<VerifyGpgArgs>) -> miette::Result<()> {
    match ctx.args.armored_signature.clone() {
        Some(signature) => send_signature(&ctx, signature).await,
        None => get_token(&ctx).await,
    }
}

async fn get_token(ctx: &BergContext<VerifyGpgArgs>) -> miette::Result<()> {
    let token = ctx
        .client
        .get_verification_token()
        .await
        .into_diagnostic()
        .context("Could not get verification token from API!")?;
    let key_id = ctx.args.key_id.clone();
    let text = [
        "Please sign the following verification token and then send it back to the API".to_string(),
        format!("Step 1: Sign with `echo \"{token}\" | gpg -a --default-key {key_id} --detach-sig`"),
        format!("Step 2: Send it back via `berg keys gpg verify {key_id} --armored-signature <RESULT OF STEP 1> --non-interactive`")
    ].join("\n");
    println!("{text}");
    Ok(())
}

async fn send_signature(
    ctx: &BergContext<VerifyGpgArgs>,
    signature: impl AsRef<str>,
) -> miette::Result<()> {
    let options = VerifyGPGKeyOption {
        armored_signature: Some(signature.as_ref().to_string()),
        key_id: ctx.args.key_id.clone(),
    };
    ctx.client
        .user_verify_gpg_key(options)
        .await
        .into_diagnostic()
        .map_err(|e| {
            miette::miette!(
                "{err}",
                err = textwrap::refill(
                    e.to_string().as_str(),
                    ctx.global_args.max_width.unwrap_or(80) as usize
                )
            )
        })
        .context("Could not send verification token to API!")
        .map(|_| ())
}