truthlinked_sdk/license.rs
1use zeroize::{Zeroize, ZeroizeOnDrop};
2
3/// Secure license key with automatic memory protection
4///
5/// This type provides secure storage and handling of Truthlinked license keys
6/// with the following security guarantees:
7///
8/// # Security Features
9/// - **Memory Protection**: Key data is automatically zeroized when dropped
10/// - **No Credential Leakage**: Debug and Display implementations show only redacted versions
11/// - **Safe Serialization**: Serialization outputs redacted version, never full key
12/// - **Constant-Time Operations**: Where applicable, operations are constant-time
13///
14/// # Thread Safety
15/// This type is `Send + Sync` and can be safely shared across threads.
16///
17/// # Example
18/// ```rust
19/// use truthlinked_sdk::LicenseKey;
20///
21/// let key = LicenseKey::new("tl_free_secret123".to_string());
22///
23/// // Safe to log - shows redacted version
24/// println!("Using key: {}", key); // "tl_...123"
25///
26/// // Key is automatically zeroized when dropped
27/// drop(key);
28/// ```
29#[derive(Clone, Zeroize, ZeroizeOnDrop)]
30pub struct LicenseKey {
31 /// The actual license key string (zeroized on drop)
32 #[zeroize(skip)]
33 key: String,
34}
35
36impl LicenseKey {
37 /// Creates a new license key with memory protection
38 ///
39 /// The provided key string will be stored securely and automatically
40 /// zeroized from memory when this instance is dropped.
41 ///
42 /// # Arguments
43 /// * `key` - The license key string (e.g., "tl_free_...")
44 ///
45 /// # Security
46 /// The key is immediately moved into secure storage and will be
47 /// zeroized when this instance goes out of scope.
48 pub fn new(key: String) -> Self {
49 Self { key }
50 }
51
52 /// Returns the license key as a string slice
53 ///
54 /// # Security Warning
55 /// This method provides access to the raw license key and should be used
56 /// sparingly. Prefer using the redacted version for logging and debugging.
57 ///
58 /// # Usage
59 /// This method is primarily intended for internal SDK use when making
60 /// authenticated API requests.
61 pub(crate) fn as_str(&self) -> &str {
62 &self.key
63 }
64
65 /// Returns a redacted version of the license key safe for logging
66 ///
67 /// The redacted version shows only the first 3 and last 3 characters
68 /// of the license key, with the middle portion replaced by "...".
69 ///
70 /// # Example
71 /// ```rust
72 /// # use truthlinked_sdk::LicenseKey;
73 /// let key = LicenseKey::new("tl_free_secret123456789".to_string());
74 /// assert_eq!(key.redacted(), "tl_...789");
75 /// ```
76 ///
77 /// # Security
78 /// This method is safe to use in logs, error messages, and debug output
79 /// as it does not reveal the full license key.
80 pub fn redacted(&self) -> String {
81 let len = self.key.len();
82 if len > 8 {
83 format!("{}...{}", &self.key[..3], &self.key[len-3..])
84 } else {
85 "***".to_string()
86 }
87 }
88}
89
90impl std::fmt::Debug for LicenseKey {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("LicenseKey")
93 .field("key", &self.redacted())
94 .finish()
95 }
96}
97
98impl std::fmt::Display for LicenseKey {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 write!(f, "{}", self.redacted())
101 }
102}
103
104// Prevent accidental serialization
105impl serde::Serialize for LicenseKey {
106 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107 where
108 S: serde::Serializer,
109 {
110 serializer.serialize_str(&self.redacted())
111 }
112}