import fs from "node:fs/promises"; import type { ConnectionOptions } from "node:tls"; import type { PinnedDispatcherPolicy } from "openclaw/plugin-sdk/ssrf-dispatcher"; import {
buildHostnameAllowlistPolicyFromSuffixAllowlist,
fetchWithSsrFGuard,
} from "openclaw/plugin-sdk/ssrf-runtime"; import { resolveUserPath } from "openclaw/plugin-sdk/text-runtime"; import type { ResolvedGoogleChatAccount } from "./accounts.js";
type ProxyRule = RegExp | URL | string;
type TlsCert = ConnectionOptions["cert"];
type TlsKey = ConnectionOptions["key"];
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
type GoogleAuthModule = typeofimport("google-auth-library");
type GaxiosModule = typeofimport("gaxios");
type GoogleAuthRuntime = {
Gaxios: GaxiosModule["Gaxios"];
GoogleAuth: GoogleAuthModule["GoogleAuth"];
OAuth2Client: GoogleAuthModule["OAuth2Client"];
};
type GoogleAuthTransport = InstanceType<GaxiosModule["Gaxios"]>;
type GuardedGoogleAuthRequestInit = RequestInit & {
agent?: unknown;
cert?: unknown;
dispatcher?: unknown;
fetchImplementation?: unknown;
key?: unknown;
noProxy?: unknown;
proxy?: unknown;
};
type TlsOptions = {
cert?: TlsCert;
key?: TlsKey;
};
type ProxyAgentLike = {
connectOpts?: TlsOptions;
proxy: URL;
};
type TlsAgentLike = {
options?: TlsOptions;
};
type GoogleChatServiceAccountCredentials = Record<string, unknown> & {
auth_provider_x509_cert_url?: string;
auth_uri?: string;
client_email: string;
client_x509_cert_url?: string;
private_key: string;
token_uri?: string;
type?: string;
universe_domain?: string;
};
let googleAuthRuntimePromise: Promise<GoogleAuthRuntime> | null = null;
let googleAuthTransportPromise: Promise<GoogleAuthTransport> | null = null;
function asNullableObjectRecord(value: unknown): Record<string, unknown> | null { return value !== null && typeof value === "object" ? (value as Record<string, unknown>) : null;
}
function hasProxyAgentShape(value: unknown): value is ProxyAgentLike { const record = asNullableObjectRecord(value); return record !== null && record.proxy instanceof URL;
}
function hasTlsAgentShape(value: unknown): value is TlsAgentLike { const record = asNullableObjectRecord(value); return record !== null && asNullableObjectRecord(record.options) !== null;
}
function collectGoogleAuthNoProxyRules(noProxy: ProxyRule[] = []): ProxyRule[] { const rules = [...noProxy]; const envRules = (process.env.NO_PROXY ?? process.env.no_proxy)?.split(",") ?? []; for (const rule of envRules) { const trimmed = rule.trim(); if (trimmed.length > 0) {
rules.push(trimmed);
}
} return rules;
}
function shouldBypassGoogleAuthProxy(url: URL, noProxy: ProxyRule[] = []): boolean { for (const rule of collectGoogleAuthNoProxyRules(noProxy)) { if (rule instanceof RegExp) { if (rule.test(url.toString())) { returntrue;
} continue;
} if (rule instanceof URL) { if (rule.origin === url.origin) { returntrue;
} continue;
} if (rule.startsWith("*.") || rule.startsWith(".")) { const cleanedRule = rule.replace(/^\*\./, "."); if (url.hostname.endsWith(cleanedRule)) { returntrue;
} continue;
} if (rule === url.origin || rule === url.hostname || rule === url.href) { returntrue;
}
} returnfalse;
}
function readGoogleAuthProxyUrl(value: unknown): string | undefined { if (typeof value === "string") { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined;
} if (value instanceof URL) { return value.toString();
} return undefined;
}
function readOptionalTrimmedString(
record: Record<string, unknown>,
fieldName: string,
): string | undefined { const value = record[fieldName]; if (value === undefined || value === null) { return undefined;
} if (typeof value !== "string") { thrownew Error(`Google Chat service account field "${fieldName}" must be a string`);
} const trimmed = value.trim(); if (!trimmed) { thrownew Error(`Google Chat service account field "${fieldName}" cannot be empty`);
} return trimmed;
}
function readRequiredTrimmedString(record: Record<string, unknown>, fieldName: string): string { return (
readOptionalTrimmedString(record, fieldName) ??
(() => { thrownew Error(`Google Chat service account is missing "${fieldName}"`);
})()
);
}
function assertExactUrlField(
record: Record<string, unknown>,
fieldName: string,
expectedUrl: string,
): void { const value = readOptionalTrimmedString(record, fieldName); if (!value) { return;
} if (value !== expectedUrl) { thrownew Error(
`Google Chat service account field "${fieldName}" must be ${expectedUrl}, got ${value}`,
);
}
}
function assertUrlPrefixField(
record: Record<string, unknown>,
fieldName: string,
expectedPrefix: string,
): void { const value = readOptionalTrimmedString(record, fieldName); if (!value) { return;
} if (!value.startsWith(expectedPrefix)) { thrownew Error(
`Google Chat service account field "${fieldName}" must start with ${expectedPrefix}, got ${value}`,
);
}
}
function validateGoogleChatServiceAccountCredentials(
credentials: Record<string, unknown>,
): GoogleChatServiceAccountCredentials { const type = readOptionalTrimmedString(credentials, "type"); if (type && type !== "service_account") { thrownew Error(`Google Chat credentials must use service_account auth, got "${type}" instead`);
}
return credentials as GoogleChatServiceAccountCredentials;
}
async function readCredentialsFile(filePath: string): Promise<Record<string, unknown>> { const resolvedPath = resolveUserPath(filePath); if (!resolvedPath) { thrownew Error("Google Chat service account file path is empty");
}
let handle: Awaited<ReturnType<typeof fs.open>> | null = null; try {
handle = await fs.open(resolvedPath, "r");
} catch { thrownew Error("Failed to load Google Chat service account file.");
}
try { const stat = await handle.stat(); if (!stat.isFile()) { thrownew Error("Google Chat service account file must be a regular file.");
} if (stat.size > MAX_GOOGLE_CHAT_SERVICE_ACCOUNT_FILE_BYTES) { thrownew Error(
`Google Chat service account file exceeds ${MAX_GOOGLE_CHAT_SERVICE_ACCOUNT_FILE_BYTES} bytes.`,
);
}
let raw: string; try {
raw = await handle.readFile({ encoding: "utf8" });
} catch { thrownew Error("Failed to load Google Chat service account file.");
} if (Buffer.byteLength(raw, "utf8") > MAX_GOOGLE_CHAT_SERVICE_ACCOUNT_FILE_BYTES) { thrownew Error(
`Google Chat service account file exceeds ${MAX_GOOGLE_CHAT_SERVICE_ACCOUNT_FILE_BYTES} bytes.`,
);
}
let parsed: unknown; try {
parsed = JSON.parse(raw);
} catch { thrownew Error("Invalid Google Chat service account JSON.");
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { thrownew Error("Google Chat service account file must contain a JSON object.");
} return parsed as Record<string, unknown>;
} finally {
await handle.close().catch(() => {});
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.