openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
221 lines (220 loc) • 8.98 kB
JavaScript
import { j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
import "./number-coercion-Z7n6tXLk.js";
import { n as ensureAuthProfileStore } from "./store-C8spD0DG.js";
import { o as normalizeProviderId } from "./model-selection-normalize-roKDxQ9_.js";
import "./model-auth-markers-Sq8HdQFX.js";
import "./model-selection-CA_65iXi.js";
import "./model-auth-env-O5hF4UJr.js";
import { d as resolveApiKeyForProvider$1 } from "./model-auth-DVfmdW0b.js";
import "./api-key-rotation-CzSU_mi0.js";
import { fileURLToPath, pathToFileURL } from "node:url";
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { createServer } from "node:http";
//#region src/plugin-sdk/provider-auth-runtime.ts
function resolveProviderAuthProfileMetadata(params) {
const store = ensureAuthProfileStore(params.agentDir, {
config: params.cfg,
readOnly: true
});
const normalizedProvider = normalizeProviderId(params.provider);
const [profileId, profile] = (params.profileId ? [params.profileId, store.profiles[params.profileId]] : Object.entries(store.profiles).find(([, profile]) => normalizeProviderId(profile.provider) === normalizedProvider)) ?? [];
if (!profile) return {};
return {
profileId,
...profile.type === "oauth" && profile.accountId ? { accountId: profile.accountId } : {}
};
}
function buildOAuthCallbackOriginResolver(allowedHosts) {
if (!allowedHosts || allowedHosts.length === 0) return () => void 0;
const normalized = new Set(allowedHosts.map((host) => host.trim().toLowerCase()).filter((host) => host.length > 0));
if (normalized.size === 0) return () => void 0;
return (originHeader) => {
const value = Array.isArray(originHeader) ? originHeader[0] : originHeader;
if (!value) return;
try {
const parsed = new URL(value);
if (parsed.protocol !== "https:") return;
return normalized.has(parsed.host.toLowerCase()) ? parsed.origin : void 0;
} catch {
return;
}
};
}
/**
* Generates a high-entropy OAuth state token for local callback validation.
*/
function generateOAuthState() {
return crypto.randomBytes(32).toString("hex");
}
/**
* Parses a pasted OAuth redirect URL into callback code/state fields.
*/
function parseOAuthCallbackInput(input, messages = {}) {
const trimmed = input.trim();
if (!trimmed) return { error: "No input provided" };
try {
const url = new URL(trimmed);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
if (!code) return { error: "Missing 'code' parameter in URL" };
if (!state) return { error: messages.missingState ?? "Missing 'state' parameter in URL" };
return {
code,
state
};
} catch {
return { error: messages.invalidInput ?? "Paste the full redirect URL, not just the code." };
}
}
/**
* Starts a temporary loopback HTTP listener and waits for a validated OAuth callback.
*/
async function waitForLocalOAuthCallback(params) {
const hostname = params.hostname ?? "localhost";
const timeoutMs = resolveTimerTimeoutMs(params.timeoutMs, 1);
const escapedSuccessTitle = escapeHtmlText(params.successTitle);
const resolveOAuthCallbackOrigin = buildOAuthCallbackOriginResolver(params.corsOriginAllowlist);
const hasCorsOriginAllowlist = params.corsOriginAllowlist?.some((host) => host.trim().length > 0) ?? false;
return new Promise((resolve, reject) => {
let settled = false;
let timeout = null;
const server = createServer((req, res) => {
try {
applyOAuthCallbackCorsHeaders(req, res, hasCorsOriginAllowlist ? resolveOAuthCallbackOrigin : void 0);
const requestUrl = new URL(req.url ?? "/", `http://${hostname}:${params.port}`);
if (req.method === "OPTIONS") {
res.statusCode = 204;
res.end();
return;
}
if (requestUrl.pathname !== params.callbackPath) {
res.statusCode = 404;
res.setHeader("Content-Type", "text/plain");
res.end("Not found");
return;
}
if (req.method !== "GET") {
res.statusCode = 405;
res.setHeader("Allow", "GET, OPTIONS");
res.setHeader("Content-Type", "text/plain");
res.end("Method not allowed");
return;
}
const error = requestUrl.searchParams.get("error");
const code = requestUrl.searchParams.get("code")?.trim();
const state = requestUrl.searchParams.get("state")?.trim();
if (error) {
res.statusCode = 400;
res.setHeader("Content-Type", "text/plain");
res.end(`Authentication failed: ${error}`);
finish(/* @__PURE__ */ new Error(`OAuth error: ${error}`));
return;
}
if (!code || !state) {
res.statusCode = 400;
res.setHeader("Content-Type", "text/plain");
res.end("Missing code or state");
finish(/* @__PURE__ */ new Error("Missing OAuth code or state"));
return;
}
if (state !== params.expectedState) {
res.statusCode = 400;
res.setHeader("Content-Type", "text/plain");
res.end("Invalid state");
finish(/* @__PURE__ */ new Error("OAuth state mismatch"));
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(`<!doctype html><html><head><meta charset='utf-8'/></head><body><h2>${escapedSuccessTitle}</h2><p>You can close this window and return to OpenClaw.</p></body></html>`);
finish(void 0, {
code,
state
});
} catch (err) {
finish(err instanceof Error ? err : /* @__PURE__ */ new Error("OAuth callback failed"));
}
});
const finish = (err, result) => {
if (settled) return;
settled = true;
if (timeout) clearTimeout(timeout);
try {
server.close();
} catch {}
if (err) reject(err);
else if (result) resolve(result);
};
server.once("error", (err) => {
finish(err instanceof Error ? err : /* @__PURE__ */ new Error("OAuth callback server error"));
});
server.listen(params.port, hostname, () => {
params.onProgress?.(params.progressMessage ?? `Waiting for OAuth callback on ${params.redirectUri}...`);
});
timeout = setTimeout(() => {
finish(/* @__PURE__ */ new Error("OAuth callback timeout"));
}, timeoutMs);
});
}
function applyOAuthCallbackCorsHeaders(req, res, resolveOrigin) {
const origin = resolveOrigin === void 0 ? typeof req.headers.origin === "string" && isHttpOrigin(req.headers.origin) ? req.headers.origin : void 0 : resolveOrigin(req.headers.origin);
if (origin) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers");
}
if (resolveOrigin !== void 0 && !origin) return;
const requestedHeaders = req.headers["access-control-request-headers"];
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", typeof requestedHeaders === "string" && requestedHeaders.trim().length > 0 ? requestedHeaders : "content-type");
res.setHeader("Access-Control-Allow-Private-Network", "true");
res.setHeader("Access-Control-Max-Age", "600");
}
function isHttpOrigin(value) {
try {
const url = new URL(value);
return (url.protocol === "http:" || url.protocol === "https:") && url.origin === value;
} catch {
return false;
}
}
function escapeHtmlText(value) {
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
const RUNTIME_MODEL_AUTH_CANDIDATES = ["./runtime-model-auth.runtime", "../plugins/runtime/runtime-model-auth.runtime"];
const RUNTIME_MODEL_AUTH_EXTENSIONS = [
".js",
".ts",
".mjs",
".mts",
".cjs",
".cts"
];
function resolveRuntimeModelAuthModuleHref() {
const baseDir = path.dirname(fileURLToPath(import.meta.url));
for (const relativeBase of RUNTIME_MODEL_AUTH_CANDIDATES) for (const ext of RUNTIME_MODEL_AUTH_EXTENSIONS) {
const candidate = path.resolve(baseDir, `${relativeBase}${ext}`);
if (fs.existsSync(candidate)) return pathToFileURL(candidate).href;
}
throw new Error(`Unable to resolve runtime model auth module from ${import.meta.url}`);
}
async function loadRuntimeModelAuthModule() {
return await import(resolveRuntimeModelAuthModuleHref());
}
/**
* Resolves provider API-key auth through the runtime auth module when available.
*/
async function resolveApiKeyForProvider(params) {
const runtimeAuth = await loadRuntimeModelAuthModule();
return (typeof runtimeAuth.resolveApiKeyForProvider === "function" ? runtimeAuth.resolveApiKeyForProvider : resolveApiKeyForProvider$1)(params);
}
/**
* Resolves the prepared runtime auth payload for a concrete model request.
*/
async function getRuntimeAuthForModel(params) {
const { getRuntimeAuthForModel: getRuntimeAuthForModelLocal } = await loadRuntimeModelAuthModule();
return getRuntimeAuthForModelLocal(params);
}
//#endregion
export { resolveApiKeyForProvider as a, parseOAuthCallbackInput as i, generateOAuthState as n, resolveProviderAuthProfileMetadata as o, getRuntimeAuthForModel as r, waitForLocalOAuthCallback as s, buildOAuthCallbackOriginResolver as t };