























@@ -1,7 +1,12 @@
1-import { GoogleAuth, OAuth2Client } from "google-auth-library";
21import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
32import { fetchWithSsrFGuard } from "../runtime-api.js";
43import type { ResolvedGoogleChatAccount } from "./accounts.js";
4+import {
5+__testing as googleAuthRuntimeTesting,
6+getGoogleAuthTransport,
7+loadGoogleAuthRuntime,
8+resolveValidatedGoogleChatCredentials,
9+} from "./google-auth.runtime.js";
510611const CHAT_SCOPE = "https://www.googleapis.com/auth/chat.bot";
712const CHAT_ISSUER = "chat@system.gserviceaccount.com";
@@ -12,10 +17,42 @@ const CHAT_CERTS_URL =
12171318// Size-capped to prevent unbounded growth in long-running deployments (#4948)
1419const MAX_AUTH_CACHE_SIZE = 32;
15-const authCache = new Map<string, { key: string; auth: GoogleAuth }>();
16-const verifyClient = new OAuth2Client();
20+type GoogleAuthModule = typeof import("google-auth-library");
21+type GoogleAuthRuntime = {
22+GoogleAuth: GoogleAuthModule["GoogleAuth"];
23+OAuth2Client: GoogleAuthModule["OAuth2Client"];
24+};
25+type GoogleAuthInstance = InstanceType<GoogleAuthRuntime["GoogleAuth"]>;
26+type GoogleAuthOptions = ConstructorParameters<GoogleAuthRuntime["GoogleAuth"]>[0];
27+type GoogleAuthTransport = NonNullable<GoogleAuthOptions>["clientOptions"] extends {
28+transporter?: infer T;
29+}
30+ ? T
31+ : never;
32+type OAuth2ClientInstance = InstanceType<GoogleAuthRuntime["OAuth2Client"]>;
33+34+const authCache = new Map<string, { key: string; auth: GoogleAuthInstance }>();
17351836let cachedCerts: { fetchedAt: number; certs: Record<string, string> } | null = null;
37+let verifyClientPromise: Promise<OAuth2ClientInstance> | null = null;
38+39+async function getVerifyClient(): Promise<OAuth2ClientInstance> {
40+if (!verifyClientPromise) {
41+verifyClientPromise = (async () => {
42+try {
43+const { OAuth2Client } = await loadGoogleAuthRuntime();
44+// google-auth-library types its transporter through gaxios' CJS surface,
45+// while the plugin imports the ESM entrypoint directly.
46+const transporter = (await getGoogleAuthTransport()) as unknown as GoogleAuthTransport;
47+return new OAuth2Client({ transporter });
48+} catch (error) {
49+verifyClientPromise = null;
50+throw error;
51+}
52+})();
53+}
54+return await verifyClientPromise;
55+}
19562057function buildAuthKey(account: ResolvedGoogleChatAccount): string {
2158if (account.credentialsFile) {
@@ -27,12 +64,18 @@ function buildAuthKey(account: ResolvedGoogleChatAccount): string {
2764return "none";
2865}
296630-function getAuthInstance(account: ResolvedGoogleChatAccount): GoogleAuth {
67+async function getAuthInstance(account: ResolvedGoogleChatAccount): Promise<GoogleAuthInstance> {
3168const key = buildAuthKey(account);
3269const cached = authCache.get(account.accountId);
3370if (cached && cached.key === key) {
3471return cached.auth;
3572}
73+const [{ GoogleAuth }, rawTransporter, credentials] = await Promise.all([
74+loadGoogleAuthRuntime(),
75+getGoogleAuthTransport(),
76+resolveValidatedGoogleChatCredentials(account),
77+]);
78+const transporter = rawTransporter as unknown as GoogleAuthTransport;
36793780const evictOldest = () => {
3881if (authCache.size > MAX_AUTH_CACHE_SIZE) {
@@ -43,21 +86,11 @@ function getAuthInstance(account: ResolvedGoogleChatAccount): GoogleAuth {
4386}
4487};
458846-if (account.credentialsFile) {
47-const auth = new GoogleAuth({ keyFile: account.credentialsFile, scopes: [CHAT_SCOPE] });
48-authCache.set(account.accountId, { key, auth });
49-evictOldest();
50-return auth;
51-}
52-53-if (account.credentials) {
54-const auth = new GoogleAuth({ credentials: account.credentials, scopes: [CHAT_SCOPE] });
55-authCache.set(account.accountId, { key, auth });
56-evictOldest();
57-return auth;
58-}
59-60-const auth = new GoogleAuth({ scopes: [CHAT_SCOPE] });
89+const auth = new GoogleAuth({
90+ ...(credentials ? { credentials } : {}),
91+clientOptions: { transporter },
92+scopes: [CHAT_SCOPE],
93+});
6194authCache.set(account.accountId, { key, auth });
6295evictOldest();
6396return auth;
@@ -66,7 +99,7 @@ function getAuthInstance(account: ResolvedGoogleChatAccount): GoogleAuth {
6699export async function getGoogleChatAccessToken(
67100account: ResolvedGoogleChatAccount,
68101): Promise<string> {
69-const auth = getAuthInstance(account);
102+const auth = await getAuthInstance(account);
70103const client = await auth.getClient();
71104const access = await client.getAccessToken();
72105const token = typeof access === "string" ? access : access?.token;
@@ -117,6 +150,7 @@ export async function verifyGoogleChatRequest(params: {
117150118151if (audienceType === "app-url") {
119152try {
153+const verifyClient = await getVerifyClient();
120154const ticket = await verifyClient.verifyIdToken({
121155idToken: bearer,
122156 audience,
@@ -153,6 +187,7 @@ export async function verifyGoogleChatRequest(params: {
153187154188if (audienceType === "project-number") {
155189try {
190+const verifyClient = await getVerifyClient();
156191const certs = await fetchChatCerts();
157192await verifyClient.verifySignedJwtWithCertsAsync(bearer, certs, audience, [CHAT_ISSUER]);
158193return { ok: true };
@@ -165,3 +200,12 @@ export async function verifyGoogleChatRequest(params: {
165200}
166201167202export const GOOGLE_CHAT_SCOPE = CHAT_SCOPE;
203+204+export const __testing = {
205+resetGoogleChatAuthForTests(): void {
206+authCache.clear();
207+cachedCerts = null;
208+verifyClientPromise = null;
209+googleAuthRuntimeTesting.resetGoogleAuthRuntimeForTests();
210+},
211+};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。