mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 20:12:55 +00:00
refactor: share plugin http dispatch helpers
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
} from "../../../packages/gateway-protocol/src/client-info.js";
|
||||
import { PROTOCOL_VERSION } from "../../../packages/gateway-protocol/src/index.js";
|
||||
import type { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import type { PluginRegistry } from "../../plugins/registry.js";
|
||||
import type { PluginHttpRouteRegistration, PluginRegistry } from "../../plugins/registry.js";
|
||||
import { withPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js";
|
||||
import type { AuthorizedGatewayHttpRequest } from "../http-utils.js";
|
||||
import type { GatewayRequestContext, GatewayRequestOptions } from "../server-methods/types.js";
|
||||
@@ -30,6 +30,18 @@ export {
|
||||
export { shouldEnforceGatewayAuthForPluginPath } from "./plugins-http/route-auth.js";
|
||||
|
||||
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
|
||||
type PluginRouteRuntimeScope = Parameters<typeof withPluginRuntimeGatewayRequestScope>[0];
|
||||
|
||||
function resolvePluginRoutePathContextForRequest(
|
||||
req: IncomingMessage,
|
||||
providedPathContext: PluginRoutePathContext | undefined,
|
||||
): PluginRoutePathContext {
|
||||
if (providedPathContext) {
|
||||
return providedPathContext;
|
||||
}
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
return resolvePluginRoutePathContext(url.pathname);
|
||||
}
|
||||
|
||||
function createPluginRouteRuntimeClient(
|
||||
scopes: readonly string[],
|
||||
@@ -55,6 +67,54 @@ function writeUpgradeUnauthorized(socket: Duplex) {
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
type PluginRouteRuntimeDispatchContext = {
|
||||
gatewayRequestAuth?: AuthorizedGatewayHttpRequest;
|
||||
gatewayRequestOperatorScopes?: readonly string[];
|
||||
};
|
||||
|
||||
function getMissingPluginRouteRuntimeContext(
|
||||
route: PluginHttpRouteRegistration,
|
||||
context: PluginRouteRuntimeDispatchContext,
|
||||
): "caller auth context" | "caller scope context" | undefined {
|
||||
if (route.auth !== "gateway") {
|
||||
return undefined;
|
||||
}
|
||||
if (route.gatewayRuntimeScopeSurface === "trusted-operator") {
|
||||
return context.gatewayRequestAuth ? undefined : "caller auth context";
|
||||
}
|
||||
return context.gatewayRequestOperatorScopes === undefined ? "caller scope context" : undefined;
|
||||
}
|
||||
|
||||
function createPluginRouteRuntimeScope(params: {
|
||||
route: PluginHttpRouteRegistration;
|
||||
req: IncomingMessage;
|
||||
gatewayRequestContext?: GatewayRequestContext;
|
||||
gatewayRequestAuth?: AuthorizedGatewayHttpRequest;
|
||||
gatewayRequestOperatorScopes?: readonly string[];
|
||||
}): PluginRouteRuntimeScope {
|
||||
const runtimeScopes =
|
||||
params.route.auth !== "gateway"
|
||||
? []
|
||||
: params.route.gatewayRuntimeScopeSurface === "trusted-operator"
|
||||
? resolvePluginRouteRuntimeOperatorScopes(
|
||||
params.req,
|
||||
params.gatewayRequestAuth!,
|
||||
"trusted-operator",
|
||||
)
|
||||
: params.gatewayRequestOperatorScopes!;
|
||||
const runtimeClient = createPluginRouteRuntimeClient(runtimeScopes);
|
||||
return {
|
||||
...(params.gatewayRequestContext ? { context: params.gatewayRequestContext } : {}),
|
||||
client: runtimeClient,
|
||||
isWebchatConnect: () => false,
|
||||
...(params.route.pluginId ? { pluginId: params.route.pluginId } : {}),
|
||||
...(params.route.source ? { pluginSource: params.route.source } : {}),
|
||||
...(params.route.gatewayMethodDispatchAllowed === true
|
||||
? { gatewayMethodDispatchAllowed: true }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export type PluginRouteDispatchContext = {
|
||||
gatewayAuthSatisfied?: boolean;
|
||||
gatewayRequestAuth?: AuthorizedGatewayHttpRequest;
|
||||
@@ -91,12 +151,7 @@ export function createGatewayPluginRequestHandler(params: {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pathContext =
|
||||
providedPathContext ??
|
||||
(() => {
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
return resolvePluginRoutePathContext(url.pathname);
|
||||
})();
|
||||
const pathContext = resolvePluginRoutePathContextForRequest(req, providedPathContext);
|
||||
const matchedRoutes = findMatchingPluginHttpRoutes(registry, pathContext);
|
||||
if (matchedRoutes.length === 0) {
|
||||
return false;
|
||||
@@ -112,53 +167,28 @@ export function createGatewayPluginRequestHandler(params: {
|
||||
// Fail closed before invoking any handlers when matched gateway routes are
|
||||
// missing the runtime auth/scope context they require.
|
||||
for (const route of matchedRoutes) {
|
||||
if (route.auth !== "gateway") {
|
||||
continue;
|
||||
}
|
||||
if (route.gatewayRuntimeScopeSurface === "trusted-operator") {
|
||||
if (!gatewayRequestAuth) {
|
||||
log.warn(
|
||||
`plugin http route blocked without caller auth context (${pathContext.canonicalPath})`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (gatewayRequestOperatorScopes === undefined) {
|
||||
const missingRuntimeContext = getMissingPluginRouteRuntimeContext(route, {
|
||||
gatewayRequestAuth,
|
||||
gatewayRequestOperatorScopes,
|
||||
});
|
||||
if (missingRuntimeContext) {
|
||||
log.warn(
|
||||
`plugin http route blocked without caller scope context (${pathContext.canonicalPath})`,
|
||||
`plugin http route blocked without ${missingRuntimeContext} (${pathContext.canonicalPath})`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const route of matchedRoutes) {
|
||||
let runtimeScopes: readonly string[] = [];
|
||||
if (route.auth === "gateway") {
|
||||
if (route.gatewayRuntimeScopeSurface === "trusted-operator") {
|
||||
runtimeScopes = resolvePluginRouteRuntimeOperatorScopes(
|
||||
req,
|
||||
gatewayRequestAuth!,
|
||||
"trusted-operator",
|
||||
);
|
||||
} else {
|
||||
runtimeScopes = gatewayRequestOperatorScopes!;
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeClient = createPluginRouteRuntimeClient(runtimeScopes);
|
||||
try {
|
||||
const handled = await withPluginRuntimeGatewayRequestScope(
|
||||
{
|
||||
...(gatewayRequestContext ? { context: gatewayRequestContext } : {}),
|
||||
client: runtimeClient,
|
||||
isWebchatConnect: () => false,
|
||||
...(route.pluginId ? { pluginId: route.pluginId } : {}),
|
||||
...(route.source ? { pluginSource: route.source } : {}),
|
||||
...(route.gatewayMethodDispatchAllowed === true
|
||||
? { gatewayMethodDispatchAllowed: true }
|
||||
: {}),
|
||||
},
|
||||
createPluginRouteRuntimeScope({
|
||||
route,
|
||||
req,
|
||||
gatewayRequestContext,
|
||||
gatewayRequestAuth,
|
||||
gatewayRequestOperatorScopes,
|
||||
}),
|
||||
async () => route.handler(req, res),
|
||||
);
|
||||
if (handled !== false) {
|
||||
@@ -193,12 +223,7 @@ export function createGatewayPluginUpgradeHandler(params: {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pathContext =
|
||||
providedPathContext ??
|
||||
(() => {
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
return resolvePluginRoutePathContext(url.pathname);
|
||||
})();
|
||||
const pathContext = resolvePluginRoutePathContextForRequest(req, providedPathContext);
|
||||
const matchedRoutes = findMatchingPluginHttpRoutes(registry, pathContext).filter(
|
||||
(route) => typeof route.handleUpgrade === "function",
|
||||
);
|
||||
@@ -215,22 +240,13 @@ export function createGatewayPluginUpgradeHandler(params: {
|
||||
const gatewayRequestOperatorScopes = dispatchContext?.gatewayRequestOperatorScopes;
|
||||
|
||||
for (const route of matchedRoutes) {
|
||||
if (route.auth !== "gateway") {
|
||||
continue;
|
||||
}
|
||||
if (route.gatewayRuntimeScopeSurface === "trusted-operator") {
|
||||
if (!gatewayRequestAuth) {
|
||||
log.warn(
|
||||
`plugin http upgrade blocked without caller auth context (${pathContext.canonicalPath})`,
|
||||
);
|
||||
writeUpgradeUnauthorized(socket);
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (gatewayRequestOperatorScopes === undefined) {
|
||||
const missingRuntimeContext = getMissingPluginRouteRuntimeContext(route, {
|
||||
gatewayRequestAuth,
|
||||
gatewayRequestOperatorScopes,
|
||||
});
|
||||
if (missingRuntimeContext) {
|
||||
log.warn(
|
||||
`plugin http upgrade blocked without caller scope context (${pathContext.canonicalPath})`,
|
||||
`plugin http upgrade blocked without ${missingRuntimeContext} (${pathContext.canonicalPath})`,
|
||||
);
|
||||
writeUpgradeUnauthorized(socket);
|
||||
return true;
|
||||
@@ -238,32 +254,15 @@ export function createGatewayPluginUpgradeHandler(params: {
|
||||
}
|
||||
|
||||
for (const route of matchedRoutes) {
|
||||
let runtimeScopes: readonly string[] = [];
|
||||
if (route.auth === "gateway") {
|
||||
if (route.gatewayRuntimeScopeSurface === "trusted-operator") {
|
||||
runtimeScopes = resolvePluginRouteRuntimeOperatorScopes(
|
||||
req,
|
||||
gatewayRequestAuth!,
|
||||
"trusted-operator",
|
||||
);
|
||||
} else {
|
||||
runtimeScopes = gatewayRequestOperatorScopes!;
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeClient = createPluginRouteRuntimeClient(runtimeScopes);
|
||||
try {
|
||||
const handled = await withPluginRuntimeGatewayRequestScope(
|
||||
{
|
||||
...(gatewayRequestContext ? { context: gatewayRequestContext } : {}),
|
||||
client: runtimeClient,
|
||||
isWebchatConnect: () => false,
|
||||
...(route.pluginId ? { pluginId: route.pluginId } : {}),
|
||||
...(route.source ? { pluginSource: route.source } : {}),
|
||||
...(route.gatewayMethodDispatchAllowed === true
|
||||
? { gatewayMethodDispatchAllowed: true }
|
||||
: {}),
|
||||
},
|
||||
createPluginRouteRuntimeScope({
|
||||
route,
|
||||
req,
|
||||
gatewayRequestContext,
|
||||
gatewayRequestAuth,
|
||||
gatewayRequestOperatorScopes,
|
||||
}),
|
||||
async () => route.handleUpgrade?.(req, socket, head),
|
||||
);
|
||||
if (handled !== false) {
|
||||
|
||||
Reference in New Issue
Block a user