#
cargo/util/credential/
token.rs

    1//! Credential provider that uses plaintext tokens in Cargo's config.
    2
    3use anyhow::Context as _;
    4use cargo_credential::{Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo};
    5use url::Url;
    6
    7use crate::{
    8    GlobalContext,
    9    core::SourceId,
    10    ops::RegistryCredentialConfig,
    11    util::{auth::registry_credential_config_raw, context},
    12};
    13
    14pub struct TokenCredential<'a> {
    15    gctx: &'a GlobalContext,
    16}
    17
    18impl<'a> TokenCredential<'a> {
    19    pub fn new(gctx: &'a GlobalContext) -> Self {
    20        Self { gctx }
    21    }
    22}
    23
    24impl<'a> Credential for TokenCredential<'a> {
    25    fn perform(
    26        &self,
    27        registry: &RegistryInfo<'_>,
    28        action: &Action<'_>,
    29        _args: &[&str],
    30    ) -> Result<CredentialResponse, Error> {
    31        let index_url = Url::parse(registry.index_url).context("parsing index url")?;
    32        let sid = if let Some(name) = registry.name {
    33            SourceId::for_alt_registry(&index_url, name)
    34        } else {
    35            SourceId::for_registry(&index_url)
    36        }?;
    37        let previous_token = registry_credential_config_raw(self.gctx, &sid)?.and_then(|c| c.token);
    38
    39        match action {
    40            Action::Get(_) => {
    41                let token = previous_token.ok_or_else(|| Error::NotFound)?.val;
    42                Ok(CredentialResponse::Get {
    43                    token,
    44                    cache: CacheControl::Session,
    45                    operation_independent: true,
    46                })
    47            }
    48            Action::Login(options) => {
    49                // Automatically remove `cargo login` from an inputted token to
    50                // allow direct pastes from `registry.host()`/me.
    51                let new_token = cargo_credential::read_token(options, registry)?
    52                    .map(|line| line.replace("cargo login", "").trim().to_string());
    53
    54                crates_io::check_token(new_token.as_ref().expose()).map_err(Box::new)?;
    55                context::save_credentials(
    56                    self.gctx,
    57                    Some(RegistryCredentialConfig::Token(new_token)),
    58                    &sid,
    59                )?;
    60                let _ = self.gctx.shell().status(
    61                    "Login",
    62                    format!("token for `{}` saved", sid.display_registry_name()),
    63                );
    64                Ok(CredentialResponse::Login)
    65            }
    66            Action::Logout => {
    67                if previous_token.is_none() {
    68                    return Err(Error::NotFound);
    69                }
    70                let reg_name = sid.display_registry_name();
    71                context::save_credentials(self.gctx, None, &sid)?;
    72                let _ = self.gctx.shell().status(
    73                    "Logout",
    74                    format!("token for `{reg_name}` has been removed from local storage"),
    75                );
    76                let location = if sid.is_crates_io() {
    77                    "<https://crates.io/me>".to_string()
    78                } else {
    79                    // The URL for the source requires network access to load the config.
    80                    // That could be a fairly heavy operation to perform just to provide a
    81                    // help message, so for now this just provides some generic text.
    82                    // Perhaps in the future this could have an API to fetch the config if
    83                    // it is cached, but avoid network access otherwise?
    84                    format!("the `{reg_name}` website")
    85                };
    86                eprintln!(
    87                    "note: This does not revoke the token on the registry server.\n    \
    88                    If you need to revoke the token, visit {location} and follow the instructions there."
    89                );
    90                Ok(CredentialResponse::Logout)
    91            }
    92            _ => Err(Error::OperationNotSupported),
    93        }
    94    }
    95}