coinbase_advanced/
credentials.rs1use secrecy::{ExposeSecret, SecretString};
2use std::env;
3
4use crate::error::{Error, Result};
5
6#[derive(Clone)]
8pub struct Credentials {
9 api_key: String,
11 private_key: SecretString,
13}
14
15impl std::fmt::Debug for Credentials {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 f.debug_struct("Credentials")
18 .field("api_key", &self.api_key)
19 .field("private_key", &"[REDACTED]")
20 .finish()
21 }
22}
23
24impl Credentials {
25 pub fn new(api_key: impl Into<String>, private_key: impl Into<String>) -> Result<Self> {
41 let api_key = api_key.into();
42 let private_key = private_key.into();
43
44 if api_key.is_empty() {
46 return Err(Error::config("API key cannot be empty"));
47 }
48 if private_key.is_empty() {
49 return Err(Error::config("Private key cannot be empty"));
50 }
51 if !private_key.contains("BEGIN EC PRIVATE KEY") {
52 return Err(Error::config(
53 "Private key must be in PEM format (EC PRIVATE KEY)",
54 ));
55 }
56
57 Ok(Self {
58 api_key,
59 private_key: SecretString::from(private_key),
60 })
61 }
62
63 pub fn from_env() -> Result<Self> {
72 let api_key = env::var("COINBASE_API_KEY")
73 .map_err(|_| Error::config("COINBASE_API_KEY environment variable not set"))?;
74
75 let private_key = env::var("COINBASE_PRIVATE_KEY")
76 .map_err(|_| Error::config("COINBASE_PRIVATE_KEY environment variable not set"))?;
77
78 let private_key = private_key.replace("\\n", "\n");
80
81 Self::new(api_key, private_key)
82 }
83
84 pub fn api_key(&self) -> &str {
86 &self.api_key
87 }
88
89 pub(crate) fn private_key(&self) -> &str {
91 self.private_key.expose_secret()
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 const TEST_KEY: &str = "organizations/test-org/apiKeys/test-key";
100 const TEST_PRIVATE_KEY: &str = "-----BEGIN EC PRIVATE KEY-----
101MHQCAQEEIBkg4LVWM9nuwNKXPgFvbVwUxYdLlpfazMKfqTgs1RwQoAcGBSuBBAAK
102oUQDQgAEm8+paLliHKY9RI5gZ8SBOHwAFcPf27pePzVTaWLSmzxanOT/MO6DPqMW
1031pNcpaLerRLCPCchK31waXYjKEf3Dw==
104-----END EC PRIVATE KEY-----
105";
106
107 #[test]
108 fn test_new_credentials() {
109 let creds = Credentials::new(TEST_KEY, TEST_PRIVATE_KEY).unwrap();
110 assert_eq!(creds.api_key(), TEST_KEY);
111 }
112
113 #[test]
114 fn test_empty_api_key() {
115 let result = Credentials::new("", TEST_PRIVATE_KEY);
116 assert!(result.is_err());
117 }
118
119 #[test]
120 fn test_empty_private_key() {
121 let result = Credentials::new(TEST_KEY, "");
122 assert!(result.is_err());
123 }
124
125 #[test]
126 fn test_invalid_private_key_format() {
127 let result = Credentials::new(TEST_KEY, "not a pem key");
128 assert!(result.is_err());
129 }
130
131 #[test]
132 fn test_debug_redacts_private_key() {
133 let creds = Credentials::new(TEST_KEY, TEST_PRIVATE_KEY).unwrap();
134 let debug = format!("{:?}", creds);
135 assert!(debug.contains("[REDACTED]"));
136 assert!(!debug.contains("BEGIN EC PRIVATE KEY"));
137 }
138}