fix: use TLS 1.2 for OAuth requests via proxy
This commit is contained in:
parent
2ad550abe8
commit
1b2a573012
@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
|
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
|
||||||
|
import { ProxyAgent } from "undici";
|
||||||
|
|
||||||
// OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync
|
// OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync
|
||||||
const decode = (s: string) => Buffer.from(s, "base64").toString();
|
const decode = (s: string) => Buffer.from(s, "base64").toString();
|
||||||
@ -28,6 +29,37 @@ const CODE_ASSIST_ENDPOINTS = [
|
|||||||
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const PROXY_ENV_KEYS = [
|
||||||
|
"CLAWDBOT_HTTPS_PROXY",
|
||||||
|
"CLAWDBOT_HTTP_PROXY",
|
||||||
|
"HTTPS_PROXY",
|
||||||
|
"HTTP_PROXY",
|
||||||
|
"https_proxy",
|
||||||
|
"http_proxy",
|
||||||
|
];
|
||||||
|
|
||||||
|
function resolveProxyUrl(): string | undefined {
|
||||||
|
for (const key of PROXY_ENV_KEYS) {
|
||||||
|
const value = process.env[key]?.trim();
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyUrl = resolveProxyUrl();
|
||||||
|
const proxyAgent = proxyUrl
|
||||||
|
? new ProxyAgent({
|
||||||
|
uri: proxyUrl,
|
||||||
|
requestTls: { maxVersion: "TLSv1.2" },
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
function fetchWithProxy(input: RequestInfo | URL, init?: RequestInit) {
|
||||||
|
if (!proxyAgent) return fetch(input, init);
|
||||||
|
const nextInit = init ? { ...init, dispatcher: proxyAgent } : { dispatcher: proxyAgent };
|
||||||
|
return fetch(input, nextInit);
|
||||||
|
}
|
||||||
|
|
||||||
const RESPONSE_PAGE = `<!DOCTYPE html>
|
const RESPONSE_PAGE = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -178,7 +210,7 @@ async function exchangeCode(params: {
|
|||||||
code: string;
|
code: string;
|
||||||
verifier: string;
|
verifier: string;
|
||||||
}): Promise<{ access: string; refresh: string; expires: number }> {
|
}): Promise<{ access: string; refresh: string; expires: number }> {
|
||||||
const response = await fetch(TOKEN_URL, {
|
const response = await fetchWithProxy(TOKEN_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
@ -215,7 +247,7 @@ async function exchangeCode(params: {
|
|||||||
|
|
||||||
async function fetchUserEmail(accessToken: string): Promise<string | undefined> {
|
async function fetchUserEmail(accessToken: string): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
|
const response = await fetchWithProxy("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
if (!response.ok) return undefined;
|
if (!response.ok) return undefined;
|
||||||
@ -241,7 +273,7 @@ async function fetchProjectId(accessToken: string): Promise<string> {
|
|||||||
|
|
||||||
for (const endpoint of CODE_ASSIST_ENDPOINTS) {
|
for (const endpoint of CODE_ASSIST_ENDPOINTS) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
|
const response = await fetchWithProxy(`${endpoint}/v1internal:loadCodeAssist`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|||||||
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import { delimiter, dirname, join } from "node:path";
|
import { delimiter, dirname, join } from "node:path";
|
||||||
|
import { ProxyAgent } from "undici";
|
||||||
|
|
||||||
const CLIENT_ID_KEYS = ["CLAWDBOT_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"];
|
const CLIENT_ID_KEYS = ["CLAWDBOT_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"];
|
||||||
const CLIENT_SECRET_KEYS = [
|
const CLIENT_SECRET_KEYS = [
|
||||||
@ -23,6 +24,37 @@ const TIER_FREE = "free-tier";
|
|||||||
const TIER_LEGACY = "legacy-tier";
|
const TIER_LEGACY = "legacy-tier";
|
||||||
const TIER_STANDARD = "standard-tier";
|
const TIER_STANDARD = "standard-tier";
|
||||||
|
|
||||||
|
const PROXY_ENV_KEYS = [
|
||||||
|
"CLAWDBOT_HTTPS_PROXY",
|
||||||
|
"CLAWDBOT_HTTP_PROXY",
|
||||||
|
"HTTPS_PROXY",
|
||||||
|
"HTTP_PROXY",
|
||||||
|
"https_proxy",
|
||||||
|
"http_proxy",
|
||||||
|
];
|
||||||
|
|
||||||
|
function resolveProxyUrl(): string | undefined {
|
||||||
|
for (const key of PROXY_ENV_KEYS) {
|
||||||
|
const value = process.env[key]?.trim();
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyUrl = resolveProxyUrl();
|
||||||
|
const proxyAgent = proxyUrl
|
||||||
|
? new ProxyAgent({
|
||||||
|
uri: proxyUrl,
|
||||||
|
requestTls: { maxVersion: "TLSv1.2" },
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
function fetchWithProxy(input: RequestInfo | URL, init?: RequestInit) {
|
||||||
|
if (!proxyAgent) return fetch(input, init);
|
||||||
|
const nextInit = init ? { ...init, dispatcher: proxyAgent } : { dispatcher: proxyAgent };
|
||||||
|
return fetch(input, nextInit);
|
||||||
|
}
|
||||||
|
|
||||||
export type GeminiCliOAuthCredentials = {
|
export type GeminiCliOAuthCredentials = {
|
||||||
access: string;
|
access: string;
|
||||||
refresh: string;
|
refresh: string;
|
||||||
@ -312,7 +344,7 @@ async function exchangeCodeForTokens(code: string, verifier: string): Promise<Ge
|
|||||||
body.set("client_secret", clientSecret);
|
body.set("client_secret", clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(TOKEN_URL, {
|
const response = await fetchWithProxy(TOKEN_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body,
|
body,
|
||||||
@ -348,7 +380,7 @@ async function exchangeCodeForTokens(code: string, verifier: string): Promise<Ge
|
|||||||
|
|
||||||
async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(USERINFO_URL, {
|
const response = await fetchWithProxy(USERINFO_URL, {
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@ -387,7 +419,7 @@ async function discoverProject(accessToken: string): Promise<string> {
|
|||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
|
const response = await fetchWithProxy(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(loadBody),
|
body: JSON.stringify(loadBody),
|
||||||
@ -441,7 +473,7 @@ async function discoverProject(accessToken: string): Promise<string> {
|
|||||||
(onboardBody.metadata as Record<string, unknown>).duetProject = envProject;
|
(onboardBody.metadata as Record<string, unknown>).duetProject = envProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
|
const onboardResponse = await fetchWithProxy(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(onboardBody),
|
body: JSON.stringify(onboardBody),
|
||||||
@ -495,7 +527,7 @@ async function pollOperation(
|
|||||||
): Promise<{ done?: boolean; response?: { cloudaicompanionProject?: { id?: string } } }> {
|
): Promise<{ done?: boolean; response?: { cloudaicompanionProject?: { id?: string } } }> {
|
||||||
for (let attempt = 0; attempt < 24; attempt += 1) {
|
for (let attempt = 0; attempt < 24; attempt += 1) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {
|
const response = await fetchWithProxy(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
if (!response.ok) continue;
|
if (!response.ok) continue;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user