signstar_config/config/
credentials.rs1use std::{fmt::Display, str::FromStr};
4
5use serde::{Deserialize, Serialize};
6use ssh_key::authorized_keys::Entry;
7use zeroize::Zeroize;
8
9use crate::ConfigError;
10#[cfg(doc)]
11use crate::SignstarConfig;
12
13#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Zeroize)]
18#[serde(into = "String", try_from = "String")]
19pub struct SystemUserId(String);
20
21impl SystemUserId {
22 pub fn new(user: String) -> Result<Self, crate::Error> {
41 if user.is_empty()
42 || !(user
43 .chars()
44 .all(|char| char.is_ascii_alphanumeric() || char == '_' || char == '-'))
45 {
46 return Err(ConfigError::InvalidSystemUserName { name: user }.into());
47 }
48 Ok(Self(user))
49 }
50}
51
52impl AsRef<str> for SystemUserId {
53 fn as_ref(&self) -> &str {
54 &self.0
55 }
56}
57
58impl Display for SystemUserId {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 self.0.fmt(f)
61 }
62}
63
64impl From<SystemUserId> for String {
65 fn from(value: SystemUserId) -> Self {
66 value.0
67 }
68}
69
70impl FromStr for SystemUserId {
71 type Err = crate::Error;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 Self::new(s.to_string())
75 }
76}
77
78impl TryFrom<String> for SystemUserId {
79 type Error = crate::Error;
80
81 fn try_from(value: String) -> Result<Self, Self::Error> {
82 Self::new(value)
83 }
84}
85
86#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Zeroize)]
92#[serde(into = "String", try_from = "String")]
93pub struct AuthorizedKeyEntry(String);
94
95impl AuthorizedKeyEntry {
96 pub fn new(entry: String) -> Result<Self, crate::Error> {
117 if Entry::from_str(&entry).is_err() {
118 return Err(ConfigError::InvalidAuthorizedKeyEntry { entry }.into());
119 }
120
121 Ok(Self(entry))
122 }
123}
124
125impl AsRef<str> for AuthorizedKeyEntry {
126 fn as_ref(&self) -> &str {
127 &self.0
128 }
129}
130
131impl Display for AuthorizedKeyEntry {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 self.0.fmt(f)
134 }
135}
136
137impl From<AuthorizedKeyEntry> for String {
138 fn from(value: AuthorizedKeyEntry) -> Self {
139 value.to_string()
140 }
141}
142
143impl FromStr for AuthorizedKeyEntry {
144 type Err = crate::Error;
145
146 fn from_str(s: &str) -> Result<Self, Self::Err> {
147 Self::new(s.to_string())
148 }
149}
150
151impl TryFrom<&AuthorizedKeyEntry> for Entry {
152 type Error = crate::Error;
153
154 fn try_from(value: &AuthorizedKeyEntry) -> Result<Self, crate::Error> {
155 Entry::from_str(&value.0)
156 .map_err(|source| crate::Error::Config(ConfigError::SshKey(source)))
157 }
158}
159
160impl TryFrom<String> for AuthorizedKeyEntry {
161 type Error = crate::Error;
162
163 fn try_from(value: String) -> Result<Self, crate::Error> {
164 Self::new(value)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use testresult::TestResult;
171
172 use super::*;
173
174 #[test]
175 fn system_user_id_new_fails() {
176 assert!(SystemUserId::new("üser".to_string()).is_err());
177 }
178
179 #[test]
180 fn authorized_key_entry_new_fails() {
181 assert!(AuthorizedKeyEntry::new("foo".to_string()).is_err());
182 }
183
184 #[test]
185 fn authorized_key_as_ref() -> TestResult {
186 let entry = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host";
187 let authorized_key = AuthorizedKeyEntry::new(entry.to_string())?;
188
189 assert_eq!(authorized_key.as_ref(), entry);
190 Ok(())
191 }
192}