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);
}
Modules
Structs
- Message sent by the credential helper on startup
- Message sent by Cargo to the credential helper after the hello
- A wrapper for values that should not be printed.
- Credential provider that doesn’t support any registries.
Enums
- Message sent by the credential helper
- Credential provider error type.
- A record of what kind of operation is happening that we should generate a token for.
Constants
- Credential process JSON protocol version. If the protocol needs to make a breaking change, a new protocol version should be defined (
PROTOCOL_VERSION_2
). This library should offer support for both protocols if possible, by signaling in theCredentialHello
message. Cargo will then choose which protocol to use, or it will error if there are no common protocol versions available.
Traits
Functions
- Deserialize a request from Cargo.
- doit 🔒
- Runs the credential interaction
- Read a line of text from stdin.
- Prompt the user for a token.