1use std::{collections::HashMap, path::PathBuf};
2
3use anyhow::Context as _;
4use secrecy::SecretString;
5use serde::Deserialize;
6
7pub fn registry_token(registry: Option<&str>) -> anyhow::Result<Option<SecretString>> {
8 let mut token = registry_token_from_env(registry);
9 if token.is_none() {
10 token = registry_token_from_credential_file(registry).with_context(|| {
11 format!(
12 "can't retreive token from credential file for registry `{}`",
13 registry.unwrap_or("crates.io"),
14 )
15 })?;
16 }
17 Ok(token)
18}
19
20pub fn registry_token_from_env(registry: Option<&str>) -> Option<SecretString> {
23 let token = if let Some(r) = registry {
24 let env_var = format!("CARGO_REGISTRIES_{}_TOKEN", r.to_uppercase());
25 std::env::var(env_var)
26 } else {
27 std::env::var("CARGO_REGISTRY_TOKEN")
28 };
29 token.ok().map(|t| t.into())
30}
31
32pub fn registry_token_from_credential_file(
35 registry: Option<&str>,
36) -> anyhow::Result<Option<SecretString>> {
37 let credentials = read_cargo_credentials()?;
38 let token = credentials
39 .and_then(|c| {
40 let token: Option<RegistryToken> = if let Some(r) = registry {
41 c.registries.get(r).cloned()
42 } else {
43 c.registry.as_ref().cloned()
44 };
45 token
46 })
47 .and_then(|r| r.token.clone())
48 .map(|t| t.into());
49 Ok(token)
50}
51
52fn read_cargo_credentials() -> anyhow::Result<Option<CargoCredentials>> {
53 let credentials_path = credentials_path()?;
54 let credentials = if let Some(credentials_path) = credentials_path {
55 let content = fs_err::read_to_string(&credentials_path)
56 .context("failed to read cargo credentials file")?;
57 let credentials = toml::from_str::<CargoCredentials>(&content)
58 .context("Invalid cargo credentials file")?;
59 Some(credentials)
60 } else {
61 None
62 };
63 Ok(credentials)
64}
65
66fn credentials_path() -> anyhow::Result<Option<PathBuf>> {
67 let cargo_home = crate::cargo_home()?;
68 let mut path = cargo_home.join("credentials.toml");
69 if !path.exists() {
70 path = cargo_home.join("credentials");
71 }
72 if !path.exists() {
73 return Ok(None);
74 }
75 Ok(Some(path))
76}
77
78#[derive(Debug, Deserialize, Default, PartialEq)]
79struct CargoCredentials {
80 #[serde(default)]
81 registry: Option<RegistryToken>,
82 #[serde(default)]
83 registries: HashMap<String, RegistryToken>,
84}
85
86#[derive(Debug, Deserialize, Default, PartialEq, Clone)]
87struct RegistryToken {
88 token: Option<String>,
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn test_parse_cargo_credentials_both() {
97 let sample = r#"
98 [registry]
99 token = "aaaa" # Access token for crates.io
100
101 [registries.my]
102 token = "bbb" # Access token for the named registry
103 "#;
104 let creds = toml::from_str::<CargoCredentials>(sample).unwrap();
105 assert_eq!(
106 creds.registry.and_then(|r| r.token),
107 Some("aaaa".to_string())
108 );
109 assert_eq!(
110 creds.registries.get("my").and_then(|r| r.token.clone()),
111 Some("bbb".to_string())
112 );
113 assert_eq!(
114 creds.registries.get("foo").and_then(|r| r.token.clone()),
115 None
116 );
117 }
118
119 #[test]
120 fn test_parse_cargo_credentials_cratesio_only() {
121 let sample = r#"
122 [registry]
123 token = "aaaa" # Access token for crates.io
124 "#;
125 let creds = toml::from_str::<CargoCredentials>(sample).unwrap();
126 assert_eq!(
127 creds.registry.and_then(|r| r.token),
128 Some("aaaa".to_string())
129 );
130 assert_eq!(
131 creds.registries.get("my").and_then(|r| r.token.clone()),
132 None
133 );
134 assert_eq!(
135 creds.registries.get("foo").and_then(|r| r.token.clone()),
136 None
137 );
138 }
139
140 #[test]
141 fn test_parse_cargo_credentials_empty() {
142 let sample = "";
143 let creds = toml::from_str::<CargoCredentials>(sample).unwrap();
144 assert_eq!(creds.registry.and_then(|r| r.token), None);
145 assert_eq!(
146 creds.registries.get("my").and_then(|r| r.token.clone()),
147 None
148 );
149 assert_eq!(
150 creds.registries.get("foo").and_then(|r| r.token.clone()),
151 None
152 );
153 }
154}