signstar_crypto/
passphrase.rs1use std::{fmt::Display, str::FromStr};
4
5use rand::{Rng, distributions::Alphanumeric, thread_rng};
6use secrecy::{ExposeSecret, SecretString};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, thiserror::Error)]
11pub enum Error {
12 #[error("Unable to convert string to passphrase")]
14 Passphrase,
15}
16
17#[derive(Clone, Debug, Default, Deserialize)]
22pub struct Passphrase(SecretString);
23
24impl Passphrase {
25 pub const DEFAULT_LENGTH: usize = 30;
27
28 pub fn new(passphrase: String) -> Self {
37 Self(SecretString::new(passphrase.into()))
38 }
39
40 pub fn generate(length: Option<usize>) -> Self {
55 let length = {
56 let mut length = length.unwrap_or(Self::DEFAULT_LENGTH);
57 if length < Self::DEFAULT_LENGTH {
58 length = Self::DEFAULT_LENGTH
59 }
60 length
61 };
62
63 Self::new(
64 thread_rng()
65 .sample_iter(&Alphanumeric)
66 .take(length)
67 .map(char::from)
68 .collect(),
69 )
70 }
71
72 pub fn expose_owned(&self) -> String {
74 self.0.expose_secret().to_owned()
75 }
76
77 pub fn expose_borrowed(&self) -> &str {
79 self.0.expose_secret()
80 }
81}
82
83impl Display for Passphrase {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "[REDACTED]")
86 }
87}
88
89impl FromStr for Passphrase {
90 type Err = Error;
91
92 fn from_str(s: &str) -> Result<Self, Self::Err> {
93 Ok(Self(SecretString::from(s.to_string())))
94 }
95}
96
97impl Serialize for Passphrase {
98 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
105 where
106 S: serde::Serializer,
107 {
108 self.0.expose_secret().serialize(serializer)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use rstest::rstest;
115 use testresult::TestResult;
116
117 use super::*;
118
119 #[test]
120 fn passphrase_display() -> TestResult {
121 let passphrase = Passphrase::new("a-secret-passphrase".to_string());
122 assert_eq!(format!("{passphrase}"), "[REDACTED]");
123 Ok(())
124 }
125
126 #[rstest]
127 #[case::too_short_use_default(Some(20), 30)]
128 #[case::none_use_default(None, 30)]
129 #[case::longer_than_default(Some(31), 31)]
130 fn passphrase_generate(#[case] input_length: Option<usize>, #[case] output_length: usize) {
131 let passphrase = Passphrase::generate(input_length);
132 assert_eq!(passphrase.expose_borrowed().len(), output_length);
133 }
134}