warg_cli/commands/
login.rs

1use crate::commands::config::{keyring_backend_help, keyring_backend_parser};
2use anyhow::{bail, Context, Result};
3use clap::Args;
4use dialoguer::{theme::ColorfulTheme, Confirm, Password};
5use p256::ecdsa::SigningKey;
6use rand_core::OsRng;
7use warg_client::keyring::Keyring;
8use warg_client::{Config, RegistryUrl};
9
10use super::CommonOptions;
11
12/// Manage auth tokens for interacting with a registry.
13#[derive(Args)]
14pub struct LoginCommand {
15    /// The common command options.
16    #[clap(flatten)]
17    pub common: CommonOptions,
18
19    /// The URL of the registry to use.
20    #[clap(value_name = "URL")]
21    #[arg(hide = true)]
22    pub registry_url: Option<String>,
23
24    /// Ignore federation hints.
25    #[clap(long)]
26    pub ignore_federation_hints: bool,
27
28    /// Disable auto accept federation hints.
29    #[clap(long)]
30    pub disable_auto_accept_federation_hints: bool,
31
32    /// Automatically attempt package initialize if does not exist
33    /// or ask the user to confirm first.
34    #[clap(long)]
35    pub disable_auto_package_init: bool,
36
37    /// The backend to use for keyring access
38    #[clap(long, value_name = "KEYRING_BACKEND", value_parser = keyring_backend_parser, long_help = keyring_backend_help())]
39    pub keyring_backend: Option<String>,
40}
41
42impl LoginCommand {
43    /// Executes the command.
44    pub async fn exec(mut self) -> Result<()> {
45        if self.registry_url.is_some() {
46            if self.common.registry.is_some() {
47                bail!("Registry URL provided in two different arguments. Use only one.");
48            }
49            self.common.registry = self.registry_url;
50        }
51        let mut config = self.common.read_config()?;
52        let mut registry_url = &self
53            .common
54            .registry
55            .as_ref()
56            .map(RegistryUrl::new)
57            .transpose()?
58            .map(|u| u.to_string());
59        config.ignore_federation_hints = self.ignore_federation_hints;
60        config.disable_auto_accept_federation_hints = self.disable_auto_accept_federation_hints;
61        config.disable_auto_package_init = self.disable_auto_package_init;
62
63        // set keyring backend, if specified
64        if self.keyring_backend.is_some() {
65            config.keyring_backend = self.keyring_backend;
66        }
67
68        if registry_url.is_none() && config.home_url.is_none() {
69            bail!("Please set your registry: warg login --registry <registry-url>");
70        }
71
72        let mut changing_home_registry = false;
73
74        if registry_url.is_some()
75            && registry_url != &config.home_url
76            && Confirm::with_theme(&ColorfulTheme::default())
77                .with_prompt(format!(
78                    "Set `{registry}` as your home (or default) registry?",
79                    registry = registry_url.as_deref().unwrap(),
80                ))
81                .default(true)
82                .interact()?
83        {
84            config.home_url.clone_from(registry_url);
85            config.write_to_file(&Config::default_config_path()?)?;
86
87            // reset if changing home registry
88            changing_home_registry = true;
89        } else if registry_url.is_none() {
90            registry_url = &config.home_url;
91        }
92
93        let keyring = Keyring::from_config(&config)?;
94        config.keyring_auth = true;
95
96        let client = if *registry_url == config.home_url {
97            self.common.create_client(&config).await?
98        } else {
99            let mut config = config.clone();
100            config.home_url.clone_from(registry_url);
101            self.common.create_client(&config).await?
102        };
103
104        // the client may resolve the registry to well-known on a different registry host,
105        // so replace the `registry_url` with that host
106        let registry_url = Some(client.url().to_string());
107
108        if changing_home_registry {
109            client.reset_namespaces().await?;
110            client.reset_registry().await?;
111        }
112
113        let prompt = format!(
114            "Enter auth token for registry: {registry}",
115            registry = client.url().registry_domain(),
116        );
117
118        if config.keys.is_empty() {
119            config.keys.insert("default".to_string());
120            let key = SigningKey::random(&mut OsRng).into();
121            keyring.set_signing_key(None, &key, &mut config.keys, registry_url.as_deref())?;
122            let public_key = key.public_key();
123            let token = Password::with_theme(&ColorfulTheme::default())
124                .with_prompt(prompt)
125                .interact()
126                .context("failed to read token")?;
127            keyring.set_auth_token(&RegistryUrl::new(registry_url.as_deref().unwrap())?, &token)?;
128            config.write_to_file(&Config::default_config_path()?)?;
129            println!("Auth token was set successfully, and generated default key.");
130            println!("Public Key: {public_key}");
131            return Ok(());
132        }
133
134        let token = Password::with_theme(&ColorfulTheme::default())
135            .with_prompt(prompt)
136            .interact()
137            .context("failed to read token")?;
138        keyring.set_auth_token(&RegistryUrl::new(registry_url.as_deref().unwrap())?, &token)?;
139        config.write_to_file(&Config::default_config_path()?)?;
140        println!("Auth token was set successfully.");
141
142        if let Ok(private_key) = keyring.get_signing_key(
143            self.common.registry.as_deref(),
144            &config.keys,
145            registry_url.as_deref(),
146        ) {
147            println!("\nSigning key is still available:");
148            let public_key = private_key.public_key();
149            println!("Key ID: {}", public_key.fingerprint());
150            println!("Public Key: {public_key}");
151        }
152
153        Ok(())
154    }
155}