Skip to main content

memory_mcp/auth/oauth/
github.rs

1use super::{validate_endpoint_url, DeviceFlowProvider};
2use crate::error::MemoryError;
3
4const CLIENT_ID: &str = "Ov23liWxHYkwXTxCrYHp";
5const DEVICE_CODE_URL: &str = "https://github.com/login/device/code";
6const ACCESS_TOKEN_URL: &str = "https://github.com/login/oauth/access_token";
7const SCOPES: &[&str] = &["repo"];
8
9/// GitHub's RFC 8628 device authorization flow.
10///
11/// All values are compile-time constants — no runtime configuration needed.
12pub struct GitHubDeviceFlow;
13
14impl DeviceFlowProvider for GitHubDeviceFlow {
15    fn client_id(&self) -> &str {
16        CLIENT_ID
17    }
18
19    fn device_code_url(&self) -> &str {
20        DEVICE_CODE_URL
21    }
22
23    fn access_token_url(&self) -> &str {
24        ACCESS_TOKEN_URL
25    }
26
27    fn scopes(&self) -> &[&str] {
28        SCOPES
29    }
30
31    fn validate(&self) -> Result<(), MemoryError> {
32        validate_github_client_id(self.client_id())?;
33        validate_endpoint_url(self.device_code_url(), "device_code_url")?;
34        validate_endpoint_url(self.access_token_url(), "access_token_url")?;
35        Ok(())
36    }
37}
38
39/// Validate a GitHub OAuth client ID format.
40///
41/// GitHub client IDs are alphanumeric strings, typically 20 characters.
42/// Exposed as `pub(crate)` for testing.
43pub(crate) fn validate_github_client_id(id: &str) -> Result<(), MemoryError> {
44    if id.is_empty() {
45        return Err(MemoryError::OAuth(
46            "GitHub client ID must not be empty".into(),
47        ));
48    }
49    if id.len() < 4 || id.len() > 64 {
50        return Err(MemoryError::OAuth(format!(
51            "GitHub client ID has unexpected length ({})",
52            id.len()
53        )));
54    }
55    Ok(())
56}