mssql_auth/provider.rs
1//! Authentication provider traits.
2//!
3//! This module defines the `AuthProvider` trait for implementing
4//! authentication strategies, as specified in ARCHITECTURE.md.
5
6use bytes::Bytes;
7
8use crate::error::AuthError;
9
10/// Authentication method enumeration.
11///
12/// This indicates which authentication flow to use during connection.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AuthMethod {
15 /// SQL Server authentication (username/password in Login7).
16 SqlServer,
17 /// Azure AD / Entra ID federated authentication.
18 AzureAd,
19 /// Integrated Windows authentication (SSPI/Kerberos).
20 Integrated,
21 /// Certificate-based authentication.
22 Certificate,
23}
24
25impl AuthMethod {
26 /// Check if this method uses federated authentication.
27 #[must_use]
28 pub fn is_federated(&self) -> bool {
29 matches!(self, Self::AzureAd)
30 }
31
32 /// Check if this method uses SSPI.
33 #[must_use]
34 pub fn is_sspi(&self) -> bool {
35 matches!(self, Self::Integrated)
36 }
37
38 /// Check if this method uses Login7 credentials.
39 #[must_use]
40 pub fn uses_login7_credentials(&self) -> bool {
41 matches!(self, Self::SqlServer)
42 }
43}
44
45/// Authentication data produced by an auth provider.
46///
47/// This contains the data needed to authenticate with SQL Server,
48/// depending on the authentication method being used.
49///
50/// Sensitive fields (password bytes, tokens, SSPI blobs) are securely zeroized
51/// on drop when the `zeroize` feature is enabled.
52#[derive(Debug, Clone)]
53pub enum AuthData {
54 /// SQL Server credentials for Login7 packet.
55 SqlServer {
56 /// Username.
57 username: String,
58 /// Obfuscated password bytes (XOR + bit rotation).
59 password_bytes: Vec<u8>,
60 },
61 /// Federated authentication token for FEDAUTH feature.
62 FedAuth {
63 /// The access token.
64 token: String,
65 /// Token nonce (optional, for certain flows).
66 nonce: Option<Bytes>,
67 },
68 /// SSPI blob for integrated authentication.
69 Sspi {
70 /// The SSPI authentication blob.
71 blob: Vec<u8>,
72 },
73 /// No additional authentication data needed.
74 None,
75}
76
77/// Trait for authentication providers.
78///
79/// Authentication providers are responsible for producing the authentication
80/// data needed for the TDS connection. Different providers support different
81/// authentication methods (SQL auth, Azure AD, integrated, etc.).
82///
83/// # Example
84///
85/// ```rust,ignore
86/// use mssql_auth::{AuthProvider, SqlServerAuth};
87///
88/// let provider = SqlServerAuth::new("username", "password");
89/// let auth_data = provider.authenticate().await?;
90/// ```
91pub trait AuthProvider: Send + Sync {
92 /// Get the authentication method this provider uses.
93 fn method(&self) -> AuthMethod;
94
95 /// Authenticate and produce authentication data.
96 ///
97 /// This may involve network calls (e.g., for Azure AD token acquisition)
98 /// so it returns a future in async implementations.
99 fn authenticate(&self) -> Result<AuthData, AuthError>;
100
101 /// Get additional feature extension data for Login7.
102 ///
103 /// Some authentication methods (like Azure AD) require feature extensions
104 /// in the Login7 packet. This returns the raw feature data if needed.
105 fn feature_extension_data(&self) -> Option<Bytes> {
106 None
107 }
108
109 /// Check if this provider needs to refresh its authentication.
110 ///
111 /// For token-based authentication, this can check if the token is expired
112 /// or about to expire.
113 fn needs_refresh(&self) -> bool {
114 false
115 }
116}
117
118/// Async authentication provider trait.
119///
120/// This is for authentication methods that require async operations,
121/// such as acquiring tokens from Azure AD endpoints.
122#[allow(async_fn_in_trait)]
123pub trait AsyncAuthProvider: Send + Sync {
124 /// Get the authentication method this provider uses.
125 fn method(&self) -> AuthMethod;
126
127 /// Authenticate asynchronously and produce authentication data.
128 async fn authenticate_async(&self) -> Result<AuthData, AuthError>;
129
130 /// Get additional feature extension data for Login7.
131 fn feature_extension_data(&self) -> Option<Bytes> {
132 None
133 }
134
135 /// Check if this provider needs to refresh its authentication.
136 fn needs_refresh(&self) -> bool {
137 false
138 }
139}
140
141// Implement AuthProvider for any AsyncAuthProvider by blocking
142// (for use in synchronous contexts when needed)
143impl<T: AsyncAuthProvider> AuthProvider for T {
144 fn method(&self) -> AuthMethod {
145 <T as AsyncAuthProvider>::method(self)
146 }
147
148 fn authenticate(&self) -> Result<AuthData, AuthError> {
149 // This is a fallback - in practice, async providers should be used
150 // with authenticate_async(). This implementation is for compatibility.
151 Err(AuthError::Configuration(
152 "Async auth provider must use authenticate_async()".into(),
153 ))
154 }
155
156 fn feature_extension_data(&self) -> Option<Bytes> {
157 <T as AsyncAuthProvider>::feature_extension_data(self)
158 }
159
160 fn needs_refresh(&self) -> bool {
161 <T as AsyncAuthProvider>::needs_refresh(self)
162 }
163}
164
165// Secure zeroization of sensitive authentication data when `zeroize` feature is enabled.
166#[cfg(feature = "zeroize")]
167impl Drop for AuthData {
168 fn drop(&mut self) {
169 use zeroize::Zeroize;
170
171 match self {
172 AuthData::SqlServer { password_bytes, .. } => {
173 password_bytes.zeroize();
174 }
175 AuthData::FedAuth { token, .. } => {
176 token.zeroize();
177 }
178 AuthData::Sspi { blob } => {
179 blob.zeroize();
180 }
181 AuthData::None => {}
182 }
183 }
184}
185
186#[cfg(test)]
187#[allow(clippy::unwrap_used)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_auth_method_properties() {
193 assert!(AuthMethod::AzureAd.is_federated());
194 assert!(!AuthMethod::SqlServer.is_federated());
195
196 assert!(AuthMethod::Integrated.is_sspi());
197 assert!(!AuthMethod::SqlServer.is_sspi());
198
199 assert!(AuthMethod::SqlServer.uses_login7_credentials());
200 assert!(!AuthMethod::AzureAd.uses_login7_credentials());
201 }
202}