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
114#[cfg(feature = "wasm")]
115const _: () = {
116 use wasm_bindgen::{
117 JsValue,
118 convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi},
119 describe::WasmDescribe,
120 prelude::wasm_bindgen,
121 };
122
123 #[wasm_bindgen(typescript_custom_section)]
124 const TS_CUSTOM_TYPES: &'static str = r#"
125export type SensitiveString = Tagged<string, "SensitiveString">;
126"#;
127
128 impl WasmDescribe for SensitiveString {
129 fn describe() {
130 <String as WasmDescribe>::describe();
131 }
132 }
133
134 impl FromWasmAbi for SensitiveString {
135 type Abi = <String as FromWasmAbi>::Abi;
136
137 unsafe fn from_abi(abi: Self::Abi) -> Self {
138 let string = unsafe { String::from_abi(abi) };
139 SensitiveString::from(string)
140 }
141 }
142
143 impl OptionFromWasmAbi for SensitiveString {
144 fn is_none(abi: &Self::Abi) -> bool {
145 <String as OptionFromWasmAbi>::is_none(abi)
146 }
147 }
148
149 impl IntoWasmAbi for SensitiveString {
150 type Abi = <String as IntoWasmAbi>::Abi;
151
152 fn into_abi(self) -> Self::Abi {
153 self.expose_owned().into_abi()
154 }
155 }
156
157 impl OptionIntoWasmAbi for SensitiveString {
158 fn none() -> Self::Abi {
159 <String as OptionIntoWasmAbi>::none()
160 }
161 }
162
163 impl From<SensitiveString> for JsValue {
164 fn from(value: SensitiveString) -> Self {
165 JsValue::from(value.expose_owned())
166 }
167 }
168
169 impl TryFrom<JsValue> for SensitiveString {
170 type Error = &'static str;
171
172 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
173 value
174 .as_string()
175 .map(SensitiveString::from)
176 .ok_or("SensitiveString JsValue is not a string")
177 }
178 }
179};
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn from_str_and_from_string_are_equivalent() {
187 assert_eq!(
188 SensitiveString::from("secret"),
189 SensitiveString::from("secret".to_string())
190 );
191 }
192
193 #[test]
194 fn partial_eq_compares_inner_values() {
195 assert_eq!(
196 SensitiveString::from("secret"),
197 SensitiveString::from("secret")
198 );
199 assert_ne!(
200 SensitiveString::from("secret"),
201 SensitiveString::from("other")
202 );
203 }
204
205 #[test]
206 fn expose_returns_inner_string() {
207 let sensitive = SensitiveString::from("secret");
208 assert_eq!(sensitive.expose(), "secret");
209 assert_eq!(sensitive.expose_owned(), "secret");
210 }
211
212 #[test]
213 fn as_bytes_borrows_utf8_bytes() {
214 let sensitive = SensitiveString::from("secret");
215 let bytes = sensitive.as_bytes();
216 assert_eq!(bytes.expose_owned(), b"secret");
217 }
218
219 #[cfg(not(feature = "dangerous-crypto-debug"))]
220 #[test]
221 fn debug_is_redacted() {
222 let sensitive = SensitiveString::from("secret");
223 assert_eq!(format!("{sensitive:?}"), "[REDACTED]");
224 }
225
226 #[cfg(not(feature = "dangerous-crypto-debug"))]
227 #[test]
228 fn display_is_redacted() {
229 let sensitive = SensitiveString::from("secret");
230 assert_eq!(format!("{sensitive}"), "[REDACTED]");
231 }
232
233 #[test]
234 fn serde_json_round_trips_transparently() {
235 let sensitive = SensitiveString::from("secret");
236
237 let serialized = serde_json::to_string(&sensitive).unwrap();
238 assert_eq!(serialized, "\"secret\"");
239
240 let deserialized: SensitiveString = serde_json::from_str(&serialized).unwrap();
241 assert_eq!(deserialized, sensitive);
242 }
243}