Skip to main content

auths_cli/commands/
reset.rs

1use anyhow::{Context, Result};
2use clap::Parser;
3
4use crate::adapters::git_config::SystemGitConfigProvider;
5use crate::commands::executable::ExecutableCommand;
6use crate::config::CliConfig;
7use crate::ux::format::Output;
8use auths_sdk::ports::git_config::GitConfigProvider;
9
10/// Git config keys that `auths init` sets for signing.
11const GIT_SIGNING_CONFIG_KEYS: &[&str] = &[
12    "gpg.format",
13    "gpg.ssh.program",
14    "user.signingkey",
15    "commit.gpgsign",
16    "tag.gpgsign",
17    "gpg.ssh.allowedSignersFile",
18];
19
20/// Remove Auths identity and git configuration, allowing a clean re-initialization.
21///
22/// This is a destructive operation that removes your local identity
23/// and signing configuration. Your identity can be recovered from
24/// a paired device if you have one.
25///
26/// Usage:
27/// ```ignore
28/// auths reset
29/// auths reset --force  # skip confirmation
30/// ```
31#[derive(Parser, Debug, Clone)]
32#[command(
33    name = "reset",
34    about = "Remove Auths identity and git signing configuration"
35)]
36pub struct ResetCommand {
37    /// Skip confirmation prompt
38    #[arg(long)]
39    force: bool,
40}
41
42impl ExecutableCommand for ResetCommand {
43    fn execute(&self, ctx: &CliConfig) -> Result<()> {
44        let out = Output::new();
45
46        if !self.force {
47            let confirmed = dialoguer::Confirm::new()
48                .with_prompt("This will remove ~/.auths and git signing config. Continue? [y/N]")
49                .default(false)
50                .interact()
51                .context("Failed to read confirmation")?;
52
53            if !confirmed {
54                out.println("Aborted.");
55                return Ok(());
56            }
57        }
58
59        out.newline();
60        out.print_heading("Resetting Auths...");
61        out.newline();
62
63        remove_auths_directory(&out, ctx.repo_path.clone())?;
64        clear_keychain_keys(&out, &ctx.env_config);
65        unset_git_signing_config(&out)?;
66
67        out.newline();
68        out.print_success("Reset complete. Run 'auths init' to set up again.");
69
70        Ok(())
71    }
72}
73
74/// Removes the resolved Auths storage directory, honoring `--repo`.
75///
76/// Args:
77/// * `out`: Output sink for progress messages.
78/// * `repo`: The optional `--repo` override; `None` selects the default
79///   `~/.auths` directory, matching the path used when `--repo` is absent.
80fn remove_auths_directory(out: &Output, repo: Option<std::path::PathBuf>) -> Result<()> {
81    let auths_dir = auths_sdk::storage_layout::resolve_repo_path(repo)
82        .context("Failed to resolve the repository path to reset")?;
83
84    if auths_dir.exists() {
85        std::fs::remove_dir_all(&auths_dir)
86            .with_context(|| format!("Failed to remove {}", auths_dir.display()))?;
87        out.print_success(&format!("Removed {}", auths_dir.display()));
88    } else {
89        out.print_info(&format!("{} does not exist, skipping", auths_dir.display()));
90    }
91
92    Ok(())
93}
94
95fn unset_git_signing_config(out: &Output) -> Result<()> {
96    let provider = SystemGitConfigProvider::global();
97
98    for key in GIT_SIGNING_CONFIG_KEYS {
99        match provider.unset(key) {
100            Ok(()) => out.print_success(&format!("Unset git config {key}")),
101            Err(e) => out.print_warn(&format!("Could not unset {key}: {e}")),
102        }
103    }
104
105    Ok(())
106}
107
108/// Clear all keys from the keychain backend.
109fn clear_keychain_keys(out: &Output, env_config: &auths_sdk::core_config::EnvironmentConfig) {
110    let keychain = match auths_sdk::keychain::get_platform_keychain_with_config(env_config) {
111        Ok(k) => k,
112        Err(e) => {
113            out.print_warn(&format!("Could not access keychain to clear keys: {e}"));
114            return;
115        }
116    };
117
118    let aliases = match keychain.list_aliases() {
119        Ok(a) => a,
120        Err(e) => {
121            out.print_warn(&format!("Could not list keychain keys: {e}"));
122            return;
123        }
124    };
125
126    if aliases.is_empty() {
127        return;
128    }
129
130    for alias in &aliases {
131        match keychain.delete_key(alias) {
132            Ok(()) => {}
133            Err(e) => {
134                out.print_warn(&format!("Could not delete key '{}': {e}", alias.as_str()));
135            }
136        }
137    }
138
139    out.print_success(&format!(
140        "Cleared {} key(s) from {} keychain",
141        aliases.len(),
142        keychain.backend_name()
143    ));
144}