Skip to main content

ghast_auth/
secret.rs

1//! A string that does not print itself.
2
3use std::fmt;
4
5/// A secret string (password, access token, refresh token).
6///
7/// `Debug` and `Display` print `[redacted]` so secrets do not end up in logs by accident. Use
8/// [`SecretString::expose`] to get the actual value.
9#[derive(Clone, PartialEq, Eq, Default)]
10#[cfg_attr(feature = "microsoft", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "microsoft", serde(transparent))]
12pub struct SecretString(String);
13
14impl SecretString {
15    /// Wraps a secret value.
16    pub fn new(value: impl Into<String>) -> Self {
17        Self(value.into())
18    }
19
20    /// Returns the secret value.
21    pub fn expose(&self) -> &str {
22        &self.0
23    }
24
25    /// Returns `true` if the secret is the empty string.
26    pub fn is_empty(&self) -> bool {
27        self.0.is_empty()
28    }
29}
30
31impl From<String> for SecretString {
32    fn from(value: String) -> Self {
33        Self(value)
34    }
35}
36
37impl From<&str> for SecretString {
38    fn from(value: &str) -> Self {
39        Self(value.to_owned())
40    }
41}
42
43impl fmt::Debug for SecretString {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str("[redacted]")
46    }
47}
48
49impl fmt::Display for SecretString {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.write_str("[redacted]")
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn debug_and_display_are_redacted() {
61        let s = SecretString::new("hunter2");
62        assert_eq!(format!("{s:?}"), "[redacted]");
63        assert_eq!(format!("{s}"), "[redacted]");
64        assert_eq!(s.expose(), "hunter2");
65    }
66}