| 1 | //! Credential resolution from the environment. |
| 2 | |
| 3 | use smbcloud_ascapi_core::ApiKey; |
| 4 | use smbcloud_ascapi_core::Error; |
| 5 | |
| 6 | /// Resolve an App Store Connect API key from the environment. |
| 7 | /// |
| 8 | /// Deliberately per-call rather than at startup: an MCP server that |
| 9 | /// refuses to start without credentials cannot answer `tools/list`, which |
| 10 | /// is the first thing every client asks and the only way a user discovers |
| 11 | /// what configuration is missing. Unconfigured servers should list their |
| 12 | /// tools and fail with a useful message when one is called. |
| 13 | pub fn api_key_from_env() -> Result<ApiKey, String> { |
| 14 | let key_id = std::env::var("ASC_API_KEY") |
| 15 | .map_err(|_| "ASC_API_KEY is not set (App Store Connect API key ID)".to_string())?; |
| 16 | let issuer_id = std::env::var("ASC_ISSUER_ID") |
| 17 | .map_err(|_| "ASC_ISSUER_ID is not set (App Store Connect issuer ID)".to_string())?; |
| 18 | |
| 19 | let path = match std::env::var("ASC_PRIVATE_KEY_PATH") { |
| 20 | Ok(path) => std::path::PathBuf::from(path), |
| 21 | Err(_) => { |
| 22 | let home = std::env::var("HOME").map_err(|_| { |
| 23 | "neither ASC_PRIVATE_KEY_PATH nor HOME is set, so the .p8 key cannot be located" |
| 24 | .to_string() |
| 25 | })?; |
| 26 | std::path::PathBuf::from(home) |
| 27 | .join(".appstoreconnect") |
| 28 | .join("private_keys") |
| 29 | .join(format!("AuthKey_{key_id}.p8")) |
| 30 | } |
| 31 | }; |
| 32 | |
| 33 | ApiKey::from_p8_file(&key_id, &issuer_id, &path).map_err(|error| match error { |
| 34 | // The most common misconfiguration by a wide margin, and the |
| 35 | // default-path message needs to name the path it guessed. |
| 36 | Error::PrivateKeyRead { .. } => { |
| 37 | format!( |
| 38 | "could not read the App Store Connect private key at {}", |
| 39 | path.display() |
| 40 | ) |
| 41 | } |
| 42 | other => other.to_string(), |
| 43 | }) |
| 44 | } |