nautilus_architect_ax/common/
credential.rs1use core::fmt::Debug;
19
20use zeroize::ZeroizeOnDrop;
21
22#[derive(Clone, ZeroizeOnDrop)]
27pub struct Credential {
28 api_key: Box<str>,
29 api_secret: Box<str>,
30}
31
32impl Debug for Credential {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct(stringify!(Credential))
35 .field("api_key", &self.masked_api_key())
36 .field("api_secret", &"<redacted>")
37 .finish()
38 }
39}
40
41impl Credential {
42 #[must_use]
44 pub fn new(api_key: impl Into<String>, api_secret: impl Into<String>) -> Self {
45 Self {
46 api_key: api_key.into().into_boxed_str(),
47 api_secret: api_secret.into().into_boxed_str(),
48 }
49 }
50
51 #[must_use]
53 pub fn api_key(&self) -> &str {
54 &self.api_key
55 }
56
57 #[must_use]
63 pub fn api_secret(&self) -> &str {
64 &self.api_secret
65 }
66
67 #[must_use]
72 pub fn masked_api_key(&self) -> String {
73 let key = self.api_key.as_ref();
74 let len = key.len();
75
76 if len <= 8 {
77 "*".repeat(len)
78 } else {
79 format!("{}...{}", &key[..4], &key[len - 4..])
80 }
81 }
82
83 #[must_use]
87 pub fn bearer_token(&self, session_token: &str) -> String {
88 format!("Bearer {session_token}")
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use rstest::rstest;
95
96 use super::*;
97
98 const API_KEY: &str = "test_api_key_123";
99 const API_SECRET: &str = "test_secret_456";
100
101 #[rstest]
102 fn test_credential_creation() {
103 let credential = Credential::new(API_KEY, API_SECRET);
104
105 assert_eq!(credential.api_key(), API_KEY);
106 assert_eq!(credential.api_secret(), API_SECRET);
107 }
108
109 #[rstest]
110 fn test_masked_api_key() {
111 let credential = Credential::new(API_KEY, API_SECRET);
112 let masked = credential.masked_api_key();
113
114 assert_eq!(masked, "test..._123");
115 assert!(!masked.contains("api_key"));
116 }
117
118 #[rstest]
119 fn test_masked_api_key_short() {
120 let credential = Credential::new("short", API_SECRET);
121 let masked = credential.masked_api_key();
122
123 assert_eq!(masked, "*****");
124 }
125
126 #[rstest]
127 fn test_bearer_token() {
128 let credential = Credential::new(API_KEY, API_SECRET);
129 let session_token = "abc123def456"; let auth_header = credential.bearer_token(session_token);
131
132 assert_eq!(auth_header, "Bearer abc123def456");
133 }
134
135 #[rstest]
136 fn test_debug_does_not_leak_secret() {
137 let credential = Credential::new(API_KEY, API_SECRET);
138 let debug_string = format!("{credential:?}");
139
140 assert!(!debug_string.contains(API_SECRET));
141 assert!(debug_string.contains("<redacted>"));
142 assert!(debug_string.contains("test..."));
143 }
144}