sqlserver_mcp_catalog/auth/strategies/
windows.rs1use async_trait::async_trait;
26
27use super::super::auth_strategy::{AuthConfig, AuthStrategy, Credentials};
28use super::super::errors::AuthError;
29
30#[derive(Debug, Default)]
31pub struct WindowsAuthStrategy;
32
33#[async_trait]
34impl AuthStrategy for WindowsAuthStrategy {
35 async fn authenticate(&self, config: &AuthConfig) -> anyhow::Result<Credentials> {
36 let username = config.get("username").ok_or_else(|| {
37 AuthError::MissingCredentials(
38 "username (optionally DOMAIN\\user), password".to_string(),
39 )
40 })?;
41 let password = config
42 .get("password")
43 .ok_or_else(|| AuthError::MissingCredentials("username, password".to_string()))?;
44
45 let mut credentials = Credentials::new();
46 credentials.insert("username".to_string(), username.clone());
47 credentials.insert("password".to_string(), password.clone());
48 Ok(credentials)
49 }
50
51 fn validate_credentials(&self, credentials: &Credentials) -> bool {
52 credentials
53 .get("username")
54 .is_some_and(|username| !username.is_empty())
55 && credentials
56 .get("password")
57 .is_some_and(|password| !password.is_empty())
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[tokio::test]
66 async fn carries_a_domain_qualified_username_through_unchanged() {
67 let strategy = WindowsAuthStrategy;
68 let config = AuthConfig::from([
69 ("username".to_string(), "CORP\\alice".to_string()),
70 ("password".to_string(), "s3cr3t".to_string()),
71 ]);
72 let credentials = strategy.authenticate(&config).await.unwrap();
73 assert_eq!(credentials.get("username").unwrap(), "CORP\\alice");
74 }
75
76 #[tokio::test]
77 async fn rejects_a_config_missing_either_field() {
78 let strategy = WindowsAuthStrategy;
79 assert!(strategy.authenticate(&AuthConfig::new()).await.is_err());
80 }
81}