Line data Source code
1 : // SPDX-License-Identifier: EUPL-1.2
2 : //! A value that must never reach a log line, an error message or a response:
3 : //! the `/q` admin token and the database URL with its password.
4 : //! `Debug` and `Display` print nothing of it, and it is not `Serialize`, so a
5 : //! `{:?}`, a `tracing` field or a state dump cannot carry it. The one way
6 : //! out is [`Secret::expose`], and `dev/security.cypher` (`secret-exposed`)
7 : //! lists every call with where the value goes.
8 :
9 : /// A secret string; see the module documentation.
10 : #[derive(Clone, PartialEq, Eq)]
11 : pub struct Secret(String);
12 :
13 : impl Secret {
14 : /// Wraps a value read from the environment or a mounted file.
15 59 : pub fn new(value: impl Into<String>) -> Self {
16 59 : Self(value.into())
17 59 : }
18 :
19 : /// The value itself, for the one call that needs it (a connect, a
20 : /// comparison). Never for a log line or an error.
21 155 : pub fn expose(&self) -> &str {
22 155 : &self.0
23 155 : }
24 : }
25 :
26 : impl std::fmt::Debug for Secret {
27 8 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 8 : f.write_str("Secret(..)")
29 8 : }
30 : }
31 :
32 : impl std::fmt::Display for Secret {
33 4 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 4 : f.write_str("[redacted]")
35 4 : }
36 : }
37 :
38 : #[cfg(test)]
39 : mod tests {
40 : use super::Secret;
41 :
42 : #[test]
43 4 : fn neither_debug_nor_display_prints_the_value() {
44 4 : let s = Secret::new("postgresql://antares:hunter2@db/antares");
45 12 : for shown in [format!("{s:?}"), format!("{s}"), format!("{:#?}", Some(&s))] {
46 12 : assert!(!shown.contains("hunter2"), "{shown}");
47 : }
48 4 : assert_eq!(s.expose(), "postgresql://antares:hunter2@db/antares");
49 4 : }
50 : }
|