UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

179 lines (178 loc) • 7.43 kB
import { D as resolveExpiresAtMsFromDurationMs, F as resolveTimerTimeoutMs, T as positiveSecondsToSafeMilliseconds, _ as nonNegativeSecondsToSafeMilliseconds } from "./number-coercion-CuVhdCBs.mjs"; import { d as sleepWithAbort } from "./src-D1BHGNTj.mjs"; import { m as readProviderJsonResponse } from "./provider-http-errors-DmewLLH7.mjs"; import { i as fetchWithSsrFGuard } from "./fetch-guard-DI6biu8d.mjs"; import "./number-runtime-CgyXj1A5.mjs"; import "./retry-runtime-CZf8_bHp.mjs"; import "./ssrf-runtime-BzkFo_VJ.mjs"; import "./provider-http-CuRGJD0L.mjs"; import { r as normalizeGithubCopilotDomain, t as PUBLIC_GITHUB_COPILOT_DOMAIN } from "./domain-VvPJpQNG.mjs"; //#region extensions/github-copilot/login.ts const CLIENT_ID = "Iv1.b507a08c87ecfe98"; const GITHUB_DEVICE_FLOW_REQUEST_TIMEOUT_MS = 3e4; const GITHUB_DEVICE_FLOW_DEFAULT_INTERVAL_MS = 5e3; const GITHUB_DEVICE_FLOW_SLOW_DOWN_INCREMENT_MS = 5e3; const deviceCodeUrl = (domain) => `https://${domain}/login/device/code`; const accessTokenUrl = (domain) => `https://${domain}/login/oauth/access_token`; const deviceVerificationUrl = (domain) => `https://${domain}/login/device`; const githubAuthSsrfPolicy = (domain) => ({ hostnameAllowlist: [domain] }); function parseJsonResponse(value) { if (!value || typeof value !== "object") throw new Error("Unexpected response from GitHub"); return value; } function parseDeviceCodeResponse(value, issuedAt) { const expiresInMs = positiveSecondsToSafeMilliseconds(value.expires_in); const intervalMs = value.interval === void 0 ? GITHUB_DEVICE_FLOW_DEFAULT_INTERVAL_MS : nonNegativeSecondsToSafeMilliseconds(value.interval); const expiresAt = expiresInMs === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(expiresInMs, { nowMs: issuedAt }); if (typeof value.device_code !== "string" || !value.device_code || typeof value.user_code !== "string" || !value.user_code || typeof value.verification_uri !== "string" || !value.verification_uri || expiresInMs === void 0 || expiresAt === void 0 || intervalMs === void 0) throw new Error("GitHub device code response missing fields"); return { deviceCode: value.device_code, userCode: value.user_code, verificationUri: value.verification_uri, expiresInMs, expiresAt, intervalMs }; } async function postGitHubDeviceFlowForm(params) { const { response, release } = await fetchWithSsrFGuard({ url: params.url, init: { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" }, body: params.body }, ...params.signal ? { signal: params.signal } : {}, ...params.assertCurrent ? { beforeRequest: params.assertCurrent } : {}, requireHttps: true, policy: githubAuthSsrfPolicy(params.domain), auditContext: "github-copilot-device-flow", timeoutMs: GITHUB_DEVICE_FLOW_REQUEST_TIMEOUT_MS }); try { if (!response.ok) { await response.body?.cancel().catch(() => void 0); throw new Error(`${params.failureLabel}: HTTP ${response.status}`); } return parseJsonResponse(await readProviderJsonResponse(response, "github-copilot.device-flow")); } finally { await release(); } } async function requestDeviceCode(params) { const body = new URLSearchParams({ client_id: CLIENT_ID, scope: params.scope }); const json = await postGitHubDeviceFlowForm({ url: deviceCodeUrl(params.domain), body, failureLabel: "GitHub device code failed", domain: params.domain, ...params.signal ? { signal: params.signal } : {}, ...params.assertCurrent ? { assertCurrent: params.assertCurrent } : {} }); params.assertCurrent?.(); return parseDeviceCodeResponse(json, Date.now()); } async function pollForAccessToken(params) { const bodyBase = new URLSearchParams({ client_id: CLIENT_ID, device_code: params.deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }); let intervalMs = params.intervalMs; while (Date.now() < params.expiresAt) { await sleepGitHubDevicePollDelay(intervalMs, params.expiresAt, params.signal); params.assertCurrent?.(); if (Date.now() >= params.expiresAt) break; const json = await postGitHubDeviceFlowForm({ url: accessTokenUrl(params.domain), body: bodyBase, failureLabel: "GitHub device token failed", domain: params.domain, ...params.signal ? { signal: params.signal } : {}, ...params.assertCurrent ? { assertCurrent: params.assertCurrent } : {} }); params.assertCurrent?.(); if ("access_token" in json) { if (typeof json.access_token === "string") return { status: "authorized", accessToken: json.access_token }; throw new Error("GitHub device flow returned an invalid access token"); } const err = json.error; if (err === "authorization_pending") continue; if (err === "slow_down") { intervalMs = Math.max(Math.min(Number.MAX_SAFE_INTEGER, intervalMs + GITHUB_DEVICE_FLOW_SLOW_DOWN_INCREMENT_MS), positiveSecondsToSafeMilliseconds(json.interval) ?? 0); continue; } if (err === "expired_token") return { status: "expired" }; if (err === "access_denied") return { status: "access_denied" }; throw new Error(`GitHub device flow error: ${err}`); } return { status: "expired" }; } async function sleepGitHubDevicePollDelay(delayMs, expiresAt, signal) { const requestedDelayMs = Math.max(1, Math.floor(delayMs)); const targetAt = Math.min(Date.now() + requestedDelayMs, expiresAt); while (Date.now() < targetAt) { const remainingMs = Math.max(1, targetAt - Date.now()); const safeDelayMs = resolveTimerTimeoutMs(remainingMs, 1); await sleepWithAbort(Math.min(safeDelayMs, remainingMs), signal).catch(() => { throw signal?.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("GitHub login cancelled"); }); } } function normalizeGitHubDeviceVerificationUrl(raw, domain) { const parsed = URL.parse(raw); if (!parsed) throw new Error("GitHub device flow returned an invalid verification URL"); if (parsed.protocol !== "https:" || parsed.hostname !== domain || parsed.pathname !== "/login/device" || parsed.username || parsed.password) throw new Error("GitHub device flow returned an unexpected verification URL"); return deviceVerificationUrl(domain); } function normalizeGitHubDeviceUserCode(raw) { const userCode = raw.trim(); if (!userCode || userCode.length > 64) throw new Error("GitHub device flow returned an invalid user code"); return userCode; } async function runGitHubCopilotDeviceFlow(io, domain = PUBLIC_GITHUB_COPILOT_DOMAIN) { const host = normalizeGithubCopilotDomain(domain); const assertCurrent = () => { io.signal?.throwIfAborted(); io.assertCurrent?.(); }; assertCurrent(); const device = await requestDeviceCode({ scope: "read:user", domain: host, ...io.signal ? { signal: io.signal } : {}, assertCurrent }); const verificationUrl = normalizeGitHubDeviceVerificationUrl(device.verificationUri, host); const userCode = normalizeGitHubDeviceUserCode(device.userCode); assertCurrent(); await io.showCode({ verificationUrl, userCode, expiresInMs: device.expiresInMs }); assertCurrent(); try { await io.openUrl?.(verificationUrl); } catch {} assertCurrent(); return await pollForAccessToken({ deviceCode: device.deviceCode, intervalMs: Math.max(1e3, device.intervalMs), expiresAt: device.expiresAt, domain: host, ...io.signal ? { signal: io.signal } : {}, assertCurrent }); } //#endregion export { runGitHubCopilotDeviceFlow as t };