Tests: fast-path gateway auth bypass discovery

This commit is contained in:
Gustavo Madeira Santana
2026-04-17 01:57:42 -04:00
parent 178c36532d
commit 807c6648f9
8 changed files with 229 additions and 82 deletions

View File

@@ -0,0 +1,63 @@
import { describe, expect, it, vi } from "vitest";
const { loadBundledPluginPublicArtifactModuleSyncMock } = vi.hoisted(() => ({
loadBundledPluginPublicArtifactModuleSyncMock: vi.fn(
({ artifactBasename, dirName }: { artifactBasename: string; dirName: string }) => {
if (dirName === "mattermost" && artifactBasename === "gateway-auth-api.js") {
return {
resolveGatewayAuthBypassPaths: () => [
" /api/channels/mattermost/command ",
"",
null,
"/api/channels/mattermost/work",
],
};
}
if (dirName === "broken" && artifactBasename === "gateway-auth-api.js") {
throw new Error("broken gateway auth artifact");
}
throw new Error(
`Unable to resolve bundled plugin public surface ${dirName}/${artifactBasename}`,
);
},
),
}));
vi.mock("../../plugins/public-surface-loader.js", () => ({
loadBundledPluginPublicArtifactModuleSync: loadBundledPluginPublicArtifactModuleSyncMock,
}));
import { resolveBundledChannelGatewayAuthBypassPaths } from "./gateway-auth-bypass.js";
describe("bundled channel gateway auth bypass fast path", () => {
it("loads the narrow gateway auth artifact for configured channels", () => {
const paths = resolveBundledChannelGatewayAuthBypassPaths({
channelId: "mattermost",
cfg: { channels: { mattermost: {} } },
});
expect(paths).toEqual(["/api/channels/mattermost/command", "/api/channels/mattermost/work"]);
expect(loadBundledPluginPublicArtifactModuleSyncMock).toHaveBeenCalledWith({
dirName: "mattermost",
artifactBasename: "gateway-auth-api.js",
});
});
it("treats missing gateway auth artifacts as no bypass paths", () => {
expect(
resolveBundledChannelGatewayAuthBypassPaths({
channelId: "discord",
cfg: { channels: { discord: {} } },
}),
).toEqual([]);
});
it("surfaces errors from present gateway auth artifacts", () => {
expect(() =>
resolveBundledChannelGatewayAuthBypassPaths({
channelId: "broken",
cfg: { channels: { broken: {} } },
}),
).toThrow("broken gateway auth artifact");
});
});

View File

@@ -0,0 +1,32 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { loadBundledPluginPublicArtifactModuleSync } from "../../plugins/public-surface-loader.js";
type GatewayAuthBypassApi = {
resolveGatewayAuthBypassPaths?: (params: { cfg: OpenClawConfig }) => readonly unknown[];
};
const GATEWAY_AUTH_API_ARTIFACT_BASENAME = "gateway-auth-api.js";
const MISSING_PUBLIC_SURFACE_PREFIX = "Unable to resolve bundled plugin public surface ";
function loadBundledChannelGatewayAuthApi(channelId: string): GatewayAuthBypassApi | undefined {
try {
return loadBundledPluginPublicArtifactModuleSync<GatewayAuthBypassApi>({
dirName: channelId,
artifactBasename: GATEWAY_AUTH_API_ARTIFACT_BASENAME,
});
} catch (error) {
if (error instanceof Error && error.message.startsWith(MISSING_PUBLIC_SURFACE_PREFIX)) {
return undefined;
}
throw error;
}
}
export function resolveBundledChannelGatewayAuthBypassPaths(params: {
channelId: string;
cfg: OpenClawConfig;
}): string[] {
const api = loadBundledChannelGatewayAuthApi(params.channelId);
const paths = api?.resolveGatewayAuthBypassPaths?.({ cfg: params.cfg }) ?? [];
return paths.flatMap((path) => (typeof path === "string" && path.trim() ? [path.trim()] : []));
}

View File

@@ -335,6 +335,7 @@ export type ChannelLogoutContext<ResolvedAccount = unknown> = {
export type ChannelGatewayAdapter<ResolvedAccount = unknown> = {
startAccount?: (ctx: ChannelGatewayContext<ResolvedAccount>) => Promise<unknown>;
stopAccount?: (ctx: ChannelGatewayContext<ResolvedAccount>) => Promise<void>;
/** Keep gateway auth bypass resolution mirrored through a lightweight top-level `gateway-auth-api.ts` artifact. */
resolveGatewayAuthBypassPaths?: (params: { cfg: OpenClawConfig }) => string[];
loginWithQrStart?: (params: {
accountId?: string;

View File

@@ -10,6 +10,7 @@ import type { TlsOptions } from "node:tls";
import type { WebSocketServer } from "ws";
import { A2UI_PATH, CANVAS_WS_PATH, handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
import type { CanvasHostHandler } from "../canvas-host/server.js";
import { resolveBundledChannelGatewayAuthBypassPaths } from "../channels/plugins/gateway-auth-bypass.js";
import { loadConfig } from "../config/config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { createSubsystemLogger } from "../logging/subsystem.js";
@@ -77,9 +78,6 @@ type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
const HOOK_AUTH_FAILURE_LIMIT = 20;
const HOOK_AUTH_FAILURE_WINDOW_MS = 60_000;
let bundledChannelsModulePromise:
| Promise<typeof import("../channels/plugins/bundled.js")>
| undefined;
let identityAvatarModulePromise: Promise<typeof import("../agents/identity-avatar.js")> | undefined;
let controlUiModulePromise: Promise<typeof import("./control-ui.js")> | undefined;
let embeddingsHttpModulePromise: Promise<typeof import("./embeddings-http.js")> | undefined;
@@ -92,11 +90,6 @@ let sessionHistoryHttpModulePromise:
let sessionKillHttpModulePromise: Promise<typeof import("./session-kill-http.js")> | undefined;
let toolsInvokeHttpModulePromise: Promise<typeof import("./tools-invoke-http.js")> | undefined;
function getBundledChannelsModule() {
bundledChannelsModulePromise ??= import("../channels/plugins/bundled.js");
return bundledChannelsModulePromise;
}
function getIdentityAvatarModule() {
identityAvatarModulePromise ??= import("../agents/identity-avatar.js");
return identityAvatarModulePromise;
@@ -202,21 +195,12 @@ async function resolvePluginGatewayAuthBypassPaths(
if (!configuredChannels || Object.keys(configuredChannels).length === 0) {
return paths;
}
const { getBundledChannelPlugin, getBundledChannelSetupPlugin } =
await getBundledChannelsModule();
for (const channelId of Object.keys(configuredChannels)) {
const setupPlugin = getBundledChannelSetupPlugin(channelId);
const plugin = setupPlugin?.gateway?.resolveGatewayAuthBypassPaths
? setupPlugin
: getBundledChannelPlugin(channelId);
if (!plugin) {
continue;
}
for (const path of plugin.gateway?.resolveGatewayAuthBypassPaths?.({ cfg: configSnapshot }) ??
[]) {
if (typeof path === "string" && path.trim()) {
paths.add(path.trim());
}
for (const path of resolveBundledChannelGatewayAuthBypassPaths({
channelId,
cfg: configSnapshot,
})) {
paths.add(path);
}
}
return paths;