form_net/extract/secret.rs
1//! `Secret`: a text field that is wiped on drop and never printed.
2
3use alloc::string::String;
4use core::fmt;
5
6use super::{FromField, ValueError};
7
8/// A text field that holds a secret (a password, a token).
9///
10/// No `Display`, no `Clone` (a clone is a second copy to wipe), no way to
11/// take the `String` out: [`expose`](Self::expose) is the only way to read
12/// it, which makes every read easy to find in review. The bytes are wiped
13/// on drop with crypt-io's volatile wipe; `Debug` prints `Secret(..)`.
14///
15/// # Examples
16///
17/// ```
18/// use form_net::Secret;
19///
20/// let s = Secret::from(String::from("hunter2"));
21/// assert_eq!(s.expose(), "hunter2");
22/// assert_eq!(format!("{s:?}"), "Secret(..)");
23/// ```
24pub struct Secret(String);
25
26impl Secret {
27 /// The secret text.
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// use form_net::Secret;
33 ///
34 /// let password = Secret::from(String::from("hunter2"));
35 /// assert_eq!(password.expose(), "hunter2");
36 /// ```
37 #[must_use]
38 pub fn expose(&self) -> &str {
39 &self.0
40 }
41
42 /// Its length in bytes.
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// use form_net::Secret;
48 ///
49 /// let token = Secret::from(String::from("abc123"));
50 /// assert_eq!(token.len(), 6);
51 /// ```
52 #[must_use]
53 pub fn len(&self) -> usize {
54 self.0.len()
55 }
56
57 /// Whether it is empty.
58 ///
59 /// # Examples
60 ///
61 /// ```
62 /// use form_net::Secret;
63 ///
64 /// assert!(Secret::from(String::new()).is_empty());
65 /// assert!(!Secret::from(String::from("x")).is_empty());
66 /// ```
67 #[must_use]
68 pub fn is_empty(&self) -> bool {
69 self.0.is_empty()
70 }
71}
72
73impl From<String> for Secret {
74 fn from(s: String) -> Secret {
75 Secret(s)
76 }
77}
78
79impl fmt::Debug for Secret {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.write_str("Secret(..)")
82 }
83}
84
85impl Drop for Secret {
86 fn drop(&mut self) {
87 crate::wipe::wipe_string(&mut self.0);
88 }
89}
90
91impl FromField for Secret {
92 type Slot = Option<Secret>;
93
94 fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
95 if slot.is_some() {
96 return Err(ValueError::Duplicate);
97 }
98 *slot = Some(Secret(String::from(value)));
99 Ok(())
100 }
101
102 fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
103 slot.ok_or(ValueError::Missing)
104 }
105}