github_mcp/auth/strategies/
basic.rs1use async_trait::async_trait;
4use base64::Engine;
5use base64::engine::general_purpose::STANDARD;
6
7use super::super::auth_strategy::{AuthConfig, AuthStrategy, Credentials};
8use super::super::errors::AuthError;
9
10#[derive(Debug, Default)]
11pub struct BasicAuthStrategy;
12
13#[async_trait]
14impl AuthStrategy for BasicAuthStrategy {
15 async fn authenticate(&self, config: &AuthConfig) -> anyhow::Result<Credentials> {
16 let username = config
17 .get("username")
18 .ok_or_else(|| AuthError::MissingCredentials("username, password".to_string()))?;
19 let password = config
20 .get("password")
21 .ok_or_else(|| AuthError::MissingCredentials("username, password".to_string()))?;
22
23 let token = STANDARD.encode(format!("{username}:{password}"));
24 let mut credentials = Credentials::new();
25 credentials.insert("username".to_string(), username.clone());
26 credentials.insert("password".to_string(), password.clone());
27 credentials.insert("authorization_header".to_string(), format!("Basic {token}"));
28 Ok(credentials)
29 }
30
31 fn validate_credentials(&self, credentials: &Credentials) -> bool {
32 credentials.contains_key("authorization_header")
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 fn config(username: &str, password: &str) -> AuthConfig {
41 AuthConfig::from([
42 ("username".to_string(), username.to_string()),
43 ("password".to_string(), password.to_string()),
44 ])
45 }
46
47 #[tokio::test]
48 async fn encodes_username_and_password_as_a_basic_authorization_header() {
49 let strategy = BasicAuthStrategy;
50 let credentials = strategy
51 .authenticate(&config("alice", "s3cr3t"))
52 .await
53 .unwrap();
54 assert_eq!(
55 credentials.get("authorization_header").unwrap(),
56 "Basic YWxpY2U6czNjcjN0"
57 );
58 }
59
60 #[tokio::test]
61 async fn rejects_a_config_missing_either_field() {
62 let strategy = BasicAuthStrategy;
63 assert!(strategy.authenticate(&AuthConfig::new()).await.is_err());
64 }
65
66 #[test]
67 fn validates_credentials_carrying_an_authorization_header() {
68 let strategy = BasicAuthStrategy;
69 let credentials =
70 Credentials::from([("authorization_header".to_string(), "Basic abc".to_string())]);
71 assert!(strategy.validate_credentials(&credentials));
72 assert!(!strategy.validate_credentials(&Credentials::new()));
73 }
74}