Skip to main content

sofie_values/
secret.rs

1use std::fmt;
2
3use serde::{Deserialize, Deserializer};
4use sofie_core::{Cast, ColumnReader, ColumnSpec, Result, SqlValue};
5
6/// No `Display` and no `Serialize`, so no reply, log field or event payload can carry it.
7#[derive(Clone)]
8pub struct Secret<T> {
9    value: T,
10}
11
12impl<T> Secret<T> {
13    pub fn new(value: T) -> Self {
14        Self { value }
15    }
16
17    pub fn expose(&self) -> &T {
18        &self.value
19    }
20}
21
22/// Constant time for equal lengths, so timing does not tell how much of a guess was right.
23impl<T: AsRef<[u8]>> PartialEq for Secret<T> {
24    fn eq(&self, other: &Self) -> bool {
25        let (own, other) = (self.value.as_ref(), other.value.as_ref());
26        let difference = own
27            .iter()
28            .zip(other)
29            .fold(0, |difference, (own, other)| difference | (own ^ other));
30        own.len() == other.len() && std::hint::black_box(difference) == 0
31    }
32}
33
34impl<T: AsRef<[u8]>> Eq for Secret<T> {}
35
36impl<T> fmt::Debug for Secret<T> {
37    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38        sofie_core::value::write_masked(formatter, "Secret")
39    }
40}
41
42impl<T: Cast> Cast for Secret<T> {
43    const COLUMNS: &'static [ColumnSpec] = T::COLUMNS;
44    const NULLABLE: bool = T::NULLABLE;
45    const SENSITIVE: bool = true;
46
47    fn encode(&self, out: &mut Vec<SqlValue>) {
48        self.value.encode(out);
49    }
50
51    fn decode(row: &mut dyn ColumnReader) -> Result<Self> {
52        T::decode(row).map(Self::new)
53    }
54}
55
56impl<'de, T: Deserialize<'de>> Deserialize<'de> for Secret<T> {
57    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
58        T::deserialize(deserializer).map(Self::new)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::testing::round_trip;
66
67    #[test]
68    fn never_prints_its_value() {
69        let key = Secret::new("sk_live_123".to_owned());
70
71        assert_eq!(format!("{key:?}"), "Secret(***)");
72        assert_eq!(format!("{:?}", Some(&key)), "Some(Secret(***))");
73        assert_eq!(key.expose(), "sk_live_123");
74    }
75
76    #[test]
77    fn equals_only_the_same_bytes() {
78        let key = Secret::new("sk_live_123".to_owned());
79
80        assert_eq!(key, Secret::new("sk_live_123".to_owned()));
81        assert_ne!(key, Secret::new("sk_live_124".to_owned()));
82        assert_ne!(key, Secret::new("sk_live_12".to_owned()));
83    }
84
85    #[test]
86    fn is_read_from_input_and_stored_like_its_value() {
87        let key: Secret<String> = serde_json::from_value(serde_json::json!("sk_123")).unwrap();
88        assert_eq!(round_trip(&key), vec![SqlValue::Text("sk_123".into())]);
89    }
90}