Skip to main content

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}