1use std::fmt::Debug;
2use zeroize::Zeroizing;
3
4#[derive(Clone, PartialEq, Eq)]
5pub struct SqlServerAuth {
6 user: String,
7 password: Zeroizing<String>,
8}
9
10impl SqlServerAuth {
11 pub(crate) fn into_credentials(self) -> (String, Zeroizing<String>) {
12 (self.user, self.password)
13 }
14}
15
16impl Debug for SqlServerAuth {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 f.debug_struct("SqlServerAuth")
19 .field("user", &self.user)
20 .field("password", &"<HIDDEN>")
21 .finish()
22 }
23}
24
25#[derive(Clone, PartialEq, Eq)]
26#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
27#[cfg_attr(
28 docsrs,
29 doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
30)]
31pub struct WindowsAuth {
32 pub(crate) user: String,
33 pub(crate) password: Zeroizing<String>,
34 pub(crate) domain: Option<String>,
35}
36
37#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
38#[cfg_attr(
39 docsrs,
40 doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
41)]
42impl Debug for WindowsAuth {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("WindowsAuth")
45 .field("user", &self.user)
46 .field("password", &"<HIDDEN>")
47 .field("domain", &self.domain)
48 .finish()
49 }
50}
51
52#[derive(Clone, PartialEq, Eq)]
54pub enum AuthMethod {
55 SqlServer(SqlServerAuth),
57 #[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
61 #[cfg_attr(
62 docsrs,
63 doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
64 )]
65 Windows(WindowsAuth),
66 #[cfg(any(
69 all(windows, feature = "winauth"),
70 all(unix, feature = "integrated-auth-gssapi"),
71 doc
72 ))]
73 #[cfg_attr(
74 docsrs,
75 doc(cfg(any(windows, all(unix, feature = "integrated-auth-gssapi"))))
76 )]
77 Integrated,
78 AADToken(String),
81 #[doc(hidden)]
82 None,
83}
84
85impl Debug for AuthMethod {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 Self::SqlServer(a) => f.debug_tuple("SqlServer").field(a).finish(),
91 #[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
92 Self::Windows(a) => f.debug_tuple("Windows").field(a).finish(),
93 #[cfg(any(
94 all(windows, feature = "winauth"),
95 all(unix, feature = "integrated-auth-gssapi"),
96 doc
97 ))]
98 Self::Integrated => f.write_str("Integrated"),
99 Self::AADToken(_) => f.debug_tuple("AADToken").field(&"<HIDDEN>").finish(),
100 Self::None => f.write_str("None"),
101 }
102 }
103}
104
105impl AuthMethod {
106 pub fn sql_server(user: impl ToString, password: impl ToString) -> Self {
108 Self::SqlServer(SqlServerAuth {
109 user: user.to_string(),
110 password: Zeroizing::new(password.to_string()),
111 })
112 }
113
114 #[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
116 #[cfg_attr(
117 docsrs,
118 doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
119 )]
120 pub fn windows(user: impl AsRef<str>, password: impl ToString) -> Self {
121 let (domain, user) = match user.as_ref().find('\\') {
122 Some(idx) => (Some(&user.as_ref()[..idx]), &user.as_ref()[idx + 1..]),
123 _ => (None, user.as_ref()),
124 };
125
126 Self::Windows(WindowsAuth {
127 user: user.to_string(),
128 password: Zeroizing::new(password.to_string()),
129 domain: domain.map(|s| s.to_string()),
130 })
131 }
132
133 pub fn aad_token(token: impl ToString) -> Self {
135 Self::AADToken(token.to_string())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::AuthMethod;
142 use zeroize::Zeroize;
143
144 #[test]
145 fn sql_server_password_can_be_consumed_and_zeroized() {
146 let AuthMethod::SqlServer(auth) = AuthMethod::sql_server("sa", "secret") else {
147 unreachable!();
148 };
149
150 let (user, mut password) = auth.into_credentials();
151
152 assert_eq!("sa", user);
153 assert_eq!("secret", password.as_str());
154
155 password.zeroize();
156
157 assert!(password.is_empty());
158 }
159
160 #[test]
161 fn debug_redacts_credentials() {
162 let sql = format!("{:?}", AuthMethod::sql_server("sa", "sql-secret"));
163 assert!(!sql.contains("sql-secret"), "SQL password leaked: {sql}");
164
165 let aad = format!("{:?}", AuthMethod::aad_token("aad-secret-token"));
166 assert!(!aad.contains("aad-secret-token"), "AAD token leaked: {aad}");
167 assert!(aad.contains("HIDDEN"));
168 }
169
170 #[test]
171 fn debug_none_variant() {
172 assert_eq!(format!("{:?}", AuthMethod::None), "None");
173 }
174
175 #[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs")))]
176 #[test]
177 fn windows_auth_parses_domain_and_debug_redacts() {
178 let auth = AuthMethod::windows("DOMAIN\\user", "win-secret");
180 let dbg = format!("{:?}", auth);
181 assert!(dbg.contains("Windows"), "variant name missing: {dbg}");
182 assert!(dbg.contains("DOMAIN"), "domain not preserved: {dbg}");
183 assert!(dbg.contains("user"), "user not preserved: {dbg}");
184 assert!(!dbg.contains("win-secret"), "password leaked: {dbg}");
185
186 let plain = AuthMethod::windows("plainuser", "pw");
188 let dbg = format!("{:?}", plain);
189 assert!(dbg.contains("plainuser"), "user not preserved: {dbg}");
190 assert!(dbg.contains("None"), "domain should be None: {dbg}");
191 }
192
193 #[cfg(any(
194 all(windows, feature = "winauth"),
195 all(unix, feature = "integrated-auth-gssapi")
196 ))]
197 #[test]
198 fn integrated_debug() {
199 assert_eq!(format!("{:?}", AuthMethod::Integrated), "Integrated");
200 }
201}