Skip to main content

github_mcp/auth/strategies/
basic.rs

1// GitHub v3 REST API MCP server — generated by mcpify. Do not hand-edit.
2
3use 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            || (credentials
34                .get("username")
35                .is_some_and(|username| !username.is_empty())
36                && credentials
37                    .get("password")
38                    .is_some_and(|password| !password.is_empty()))
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    fn config(username: &str, password: &str) -> AuthConfig {
47        AuthConfig::from([
48            ("username".to_string(), username.to_string()),
49            ("password".to_string(), password.to_string()),
50        ])
51    }
52
53    #[tokio::test]
54    async fn encodes_username_and_password_as_a_basic_authorization_header() {
55        let strategy = BasicAuthStrategy;
56        let credentials = strategy
57            .authenticate(&config("alice", "s3cr3t"))
58            .await
59            .unwrap();
60        assert_eq!(
61            credentials.get("authorization_header").unwrap(),
62            "Basic YWxpY2U6czNjcjN0"
63        );
64    }
65
66    #[tokio::test]
67    async fn rejects_a_config_missing_either_field() {
68        let strategy = BasicAuthStrategy;
69        assert!(strategy.authenticate(&AuthConfig::new()).await.is_err());
70    }
71
72    #[test]
73    fn validates_credentials_carrying_an_authorization_header() {
74        let strategy = BasicAuthStrategy;
75        let credentials =
76            Credentials::from([("authorization_header".to_string(), "Basic abc".to_string())]);
77        assert!(strategy.validate_credentials(&credentials));
78        assert!(strategy.validate_credentials(&config("alice", "s3cr3t")));
79        assert!(!strategy.validate_credentials(&Credentials::new()));
80    }
81}