fix(copilot): add proper OAuth device-flow authentication
The existing Copilot provider passes a static Bearer token, but the Copilot API requires short-lived session tokens obtained via GitHub's OAuth device code flow, plus mandatory editor headers. This replaces the stub with a dedicated CopilotProvider that: - Runs the OAuth device code flow on first use (same client ID as VS Code) - Exchanges the OAuth token for a Copilot API key via api.github.com/copilot_internal/v2/token - Sends required Editor-Version/Editor-Plugin-Version headers - Caches tokens to disk (~/.config/zeroclaw/copilot/) with auto-refresh - Uses Mutex to prevent concurrent refresh races / duplicate device prompts - Writes token files with 0600 permissions (owner-only) - Respects GitHub's polling interval and code expiry from device flow - Sanitizes error messages to prevent token leakage - Uses async filesystem I/O (tokio::fs) throughout - Optionally accepts a pre-supplied GitHub token via config api_key Fixes: 403 'Access to this endpoint is forbidden' Fixes: 400 'missing Editor-Version header for IDE auth'
This commit is contained in:
parent
a2f29838b4
commit
3c62b59a72
2 changed files with 748 additions and 5 deletions
|
|
@ -1,5 +1,6 @@
|
|||
pub mod anthropic;
|
||||
pub mod compatible;
|
||||
pub mod copilot;
|
||||
pub mod gemini;
|
||||
pub mod ollama;
|
||||
pub mod openai;
|
||||
|
|
@ -37,9 +38,18 @@ fn token_end(input: &str, from: usize) -> usize {
|
|||
|
||||
/// Scrub known secret-like token prefixes from provider error strings.
|
||||
///
|
||||
/// Redacts tokens with prefixes like `sk-`, `xoxb-`, and `xoxp-`.
|
||||
/// Redacts tokens with prefixes like `sk-`, `xoxb-`, `xoxp-`, `ghp_`, `gho_`,
|
||||
/// `ghu_`, and `github_pat_`.
|
||||
pub fn scrub_secret_patterns(input: &str) -> String {
|
||||
const PREFIXES: [&str; 3] = ["sk-", "xoxb-", "xoxp-"];
|
||||
const PREFIXES: [&str; 7] = [
|
||||
"sk-",
|
||||
"xoxb-",
|
||||
"xoxp-",
|
||||
"ghp_",
|
||||
"gho_",
|
||||
"ghu_",
|
||||
"github_pat_",
|
||||
];
|
||||
|
||||
let mut scrubbed = input.to_string();
|
||||
|
||||
|
|
@ -290,9 +300,9 @@ pub fn create_provider_with_url(
|
|||
"cohere" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||
"Cohere", "https://api.cohere.com/compatibility", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"copilot" | "github-copilot" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||
"GitHub Copilot", "https://api.githubcopilot.com", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"copilot" | "github-copilot" => {
|
||||
Ok(Box::new(copilot::CopilotProvider::new(api_key)))
|
||||
},
|
||||
"lmstudio" | "lm-studio" => {
|
||||
let lm_studio_key = api_key
|
||||
.map(str::trim)
|
||||
|
|
@ -967,4 +977,32 @@ mod tests {
|
|||
let result = sanitize_api_error(input);
|
||||
assert_eq!(result, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrub_github_personal_access_token() {
|
||||
let input = "auth failed with token ghp_abc123def456";
|
||||
let result = scrub_secret_patterns(input);
|
||||
assert_eq!(result, "auth failed with token [REDACTED]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrub_github_oauth_token() {
|
||||
let input = "Bearer gho_1234567890abcdef";
|
||||
let result = scrub_secret_patterns(input);
|
||||
assert_eq!(result, "Bearer [REDACTED]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrub_github_user_token() {
|
||||
let input = "token ghu_sessiontoken123";
|
||||
let result = scrub_secret_patterns(input);
|
||||
assert_eq!(result, "token [REDACTED]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrub_github_fine_grained_pat() {
|
||||
let input = "failed: github_pat_11AABBC_xyzzy789";
|
||||
let result = scrub_secret_patterns(input);
|
||||
assert_eq!(result, "failed: [REDACTED]");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue