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}