Crate cargo_credential

source ·
Expand description

Helper library for writing Cargo credential providers.

A credential process should have a struct that implements the Credential trait. The main function should be called with an instance of that struct, such as:

fn main() {
    cargo_credential::main(MyCredential);
}

While in the perform function, stdin and stdout will be re-attached to the active console. This allows credential providers to be interactive if necessary.

Error handling

Error::UrlNotSupported

A credential provider may only support some registry URLs. If this is the case and an unsupported index URL is passed to the provider, it should respond with Error::UrlNotSupported. Other credential providers may be attempted by Cargo.

Error::NotFound

When attempting an Action::Get or Action::Logout, if a credential can not be found, the provider should respond with Error::NotFound. Other credential providers may be attempted by Cargo.

Error::OperationNotSupported

A credential provider might not support all operations. For example if the provider only supports Action::Get, Error::OperationNotSupported should be returned for all other requests.

Error::Other

All other errors go here. The error will be shown to the user in Cargo, including the full error chain using std::error::Error::source.

Example

//! Example credential provider that stores credentials in a JSON file.
//! This is not secure

use cargo_credential::{
    Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret,
};
use std::{collections::HashMap, fs::File, io::ErrorKind};
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;

struct FileCredential;

impl Credential for FileCredential {
    fn perform(
        &self,
        registry: &RegistryInfo,
        action: &Action,
        _args: &[&str],
    ) -> Result<CredentialResponse, cargo_credential::Error> {
        if registry.index_url != "https://github.com/rust-lang/crates.io-index" {
            // Restrict this provider to only work for crates.io. Cargo will skip it and attempt
            // another provider for any other registry.
            //
            // If a provider supports any registry, then this check should be omitted.
            return Err(cargo_credential::Error::UrlNotSupported);
        }

        // `Error::Other` takes a boxed `std::error::Error` type that causes Cargo to show the error.
        let mut creds = FileCredential::read().map_err(cargo_credential::Error::Other)?;

        match action {
            Action::Get(_) => {
                // Cargo requested a token, look it up.
                if let Some(token) = creds.get(registry.index_url) {
                    Ok(CredentialResponse::Get {
                        token: token.clone(),
                        cache: CacheControl::Session,
                        operation_independent: true,
                    })
                } else {
                    // Credential providers should respond with `NotFound` when a credential can not be
                    // found, allowing Cargo to attempt another provider.
                    Err(cargo_credential::Error::NotFound)
                }
            }
            Action::Login(login_options) => {
                // The token for `cargo login` can come from the `login_options` parameter or i
                // interactively reading from stdin.
                //
                // `cargo_credential::read_token` automatically handles this.
                let token = cargo_credential::read_token(login_options, registry)?;
                creds.insert(registry.index_url.to_string(), token);

                FileCredential::write(&creds).map_err(cargo_credential::Error::Other)?;

                // Credentials were successfully stored.
                Ok(CredentialResponse::Login)
            }
            Action::Logout => {
                if creds.remove(registry.index_url).is_none() {
                    // If the user attempts to log out from a registry that has no credentials
                    // stored, then NotFound is the appropriate error.
                    Err(cargo_credential::Error::NotFound)
                } else {
                    // Credentials were successfully erased.
                    Ok(CredentialResponse::Logout)
                }
            }
            // If a credential provider doesn't support a given operation, it should respond with `OperationNotSupported`.
            _ => Err(cargo_credential::Error::OperationNotSupported),
        }
    }
}

impl FileCredential {
    fn read() -> Result<HashMap<String, Secret<String>>, Error> {
        match File::open("cargo-credentials.json") {
            Ok(f) => Ok(serde_json::from_reader(f)?),
            Err(e) if e.kind() == ErrorKind::NotFound => Ok(HashMap::new()),
            Err(e) => Err(e)?,
        }
    }
    fn write(value: &HashMap<String, Secret<String>>) -> Result<(), Error> {
        let file = File::create("cargo-credentials.json")?;
        Ok(serde_json::to_writer_pretty(file, value)?)
    }
}

fn main() {
    cargo_credential::main(FileCredential);
}

Structs

Enums

Constants

  • Credential process JSON protocol version. Incrementing this version will prevent new credential providers from working with older versions of Cargo.

Traits

Functions