bitwarden_sensitive_value/
sensitive_string.rs1use core::fmt;
2
3use crate::{Sensitive, SensitiveSlice, sensitive::ExposeSensitive};
4
5pub struct SensitiveString(Sensitive<String>);
10
11impl From<&str> for Sensitive<String> {
12 fn from(value: &str) -> Self {
13 Self(value.to_string())
14 }
15}
16
17impl SensitiveString {
18 pub fn as_bytes(&self) -> SensitiveSlice<'_> {
22 Sensitive::from(self.0.expose().as_bytes())
25 }
26}
27
28impl<T: fmt::Debug> fmt::Debug for Sensitive<T> {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 #[cfg(feature = "dangerous-crypto-debug")]
31 {
32 self.0.fmt(f)
33 }
34 #[cfg(not(feature = "dangerous-crypto-debug"))]
35 {
36 f.write_str("[REDACTED]")
37 }
38 }
39}
40
41impl<T: fmt::Display> fmt::Display for Sensitive<T> {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 #[cfg(feature = "dangerous-crypto-debug")]
44 {
45 self.0.fmt(f)
46 }
47 #[cfg(not(feature = "dangerous-crypto-debug"))]
48 {
49 f.write_str("[REDACTED]")
50 }
51 }
52}
53
54impl ExposeSensitive for SensitiveString {
55 type Exposed = String;
56
57 fn expose(&self) -> &Self::Exposed {
58 self.0.expose()
59 }
60
61 fn expose_owned(self) -> Self::Exposed {
62 self.0.expose_owned()
63 }
64}
65
66impl From<&str> for SensitiveString {
67 fn from(value: &str) -> Self {
68 Self(Sensitive::from(value.to_string()))
69 }
70}
71
72impl From<String> for SensitiveString {
73 fn from(value: String) -> Self {
74 Self(Sensitive::from(value))
75 }
76}
77
78impl PartialEq for SensitiveString {
79 fn eq(&self, other: &Self) -> bool {
80 self.0.eq(&other.0)
81 }
82}
83
84impl serde::Serialize for SensitiveString {
85 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
86 self.0.serialize(serializer)
87 }
88}
89
90impl<'de> serde::Deserialize<'de> for SensitiveString {
91 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
92 Ok(Self(Sensitive::<String>::deserialize(deserializer)?))
93 }
94}
95
96impl fmt::Debug for SensitiveString {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 self.0.fmt(f)
99 }
100}
101
102impl fmt::Display for SensitiveString {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 self.0.fmt(f)
105 }
106}
107
108#[cfg(feature = "uniffi")]
109uniffi::custom_type!(SensitiveString, String, {
110 try_lift: |val| Ok(SensitiveString::from(val)),
111 lower: |obj| obj.expose().to_string(),
112});
113
114bitwarden_ffi::impl_wire_string!(
115 SensitiveString,
116 parse = |wire: String| Ok::<_, std::convert::Infallible>(SensitiveString::from(wire)),
117 format = |value: SensitiveString| value.expose_owned(),
118);
119
120#[cfg(feature = "wasm")]
121const _: () = {
122 use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
123
124 #[wasm_bindgen(typescript_custom_section)]
125 const TS_CUSTOM_TYPES: &'static str = r#"
126export type SensitiveString = Tagged<string, "SensitiveString">;
127"#;
128
129 impl From<SensitiveString> for JsValue {
130 fn from(value: SensitiveString) -> Self {
131 JsValue::from(value.expose_owned())
132 }
133 }
134
135 impl TryFrom<JsValue> for SensitiveString {
136 type Error = &'static str;
137
138 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
139 value
140 .as_string()
141 .map(SensitiveString::from)
142 .ok_or("SensitiveString JsValue is not a string")
143 }
144 }
145};
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn from_str_and_from_string_are_equivalent() {
153 assert_eq!(
154 SensitiveString::from("secret"),
155 SensitiveString::from("secret".to_string())
156 );
157 }
158
159 #[test]
160 fn partial_eq_compares_inner_values() {
161 assert_eq!(
162 SensitiveString::from("secret"),
163 SensitiveString::from("secret")
164 );
165 assert_ne!(
166 SensitiveString::from("secret"),
167 SensitiveString::from("other")
168 );
169 }
170
171 #[test]
172 fn expose_returns_inner_string() {
173 let sensitive = SensitiveString::from("secret");
174 assert_eq!(sensitive.expose(), "secret");
175 assert_eq!(sensitive.expose_owned(), "secret");
176 }
177
178 #[test]
179 fn as_bytes_borrows_utf8_bytes() {
180 let sensitive = SensitiveString::from("secret");
181 let bytes = sensitive.as_bytes();
182 assert_eq!(bytes.expose_owned(), b"secret");
183 }
184
185 #[cfg(not(feature = "dangerous-crypto-debug"))]
186 #[test]
187 fn debug_is_redacted() {
188 let sensitive = SensitiveString::from("secret");
189 assert_eq!(format!("{sensitive:?}"), "[REDACTED]");
190 }
191
192 #[cfg(not(feature = "dangerous-crypto-debug"))]
193 #[test]
194 fn display_is_redacted() {
195 let sensitive = SensitiveString::from("secret");
196 assert_eq!(format!("{sensitive}"), "[REDACTED]");
197 }
198
199 #[test]
200 fn serde_json_round_trips_transparently() {
201 let sensitive = SensitiveString::from("secret");
202
203 let serialized = serde_json::to_string(&sensitive).unwrap();
204 assert_eq!(serialized, "\"secret\"");
205
206 let deserialized: SensitiveString = serde_json::from_str(&serialized).unwrap();
207 assert_eq!(deserialized, sensitive);
208 }
209}