openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
140 lines (139 loc) • 6.9 kB
JavaScript
import { r as getAsyncWorkSignal, t as AsyncWorkScope } from "./async-work-scope-DDiH0P7W.mjs";
import { t as isAbortError } from "./abort-signal-QqAmLWTl.mjs";
import { o as emitInternalDiagnosticEvent } from "./diagnostic-events-DT4ejwoa.mjs";
import { d as runHttpConnectionRequest, l as createHttpRequestAbortSignal, p as waitForHttpRequestRejection } from "./http-body-BBg_CVrI.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import "./io-BGddrCVX.mjs";
import { G as readUserProfileAliasRevision } from "./user-profiles-internal-B_G1rF82.mjs";
import { c as readOperatorRolePolicyRevision } from "./operator-role-policy-BPiQfn95.mjs";
import { i as hasCurrentGatewayOperatorAccess, n as GatewayOperatorAccessDeniedError, t as GATEWAY_OPERATOR_ACCESS_DENIED_MESSAGE } from "./operator-access-policy-DQXLvTEQ.mjs";
import { t as ToolAuthorizationError } from "./tool-input-error-mjW74R8m.mjs";
import { r as isGatewayAuthPolicyCurrent, t as captureGatewayAuthPolicy } from "./auth-policy-tP8zZBpP.mjs";
import { a as prepareGatewayHttpErrorResponse, c as sendGatewayAuthFailure, m as sendUnauthorized, u as sendJson } from "./http-common-Cr4G2eKt.mjs";
import { t as resolveSharedGatewaySessionGeneration } from "./ws-shared-generation-BeRsGF9T.mjs";
import { AsyncLocalStorage } from "node:async_hooks";
//#region src/gateway/http-operator-access.ts
function sendGatewayHttpAuthFailure(res, authResult) {
if (authResult.reason !== "operator_access_denied") {
sendGatewayAuthFailure(res, authResult);
return;
}
if (!prepareGatewayHttpErrorResponse(res, "Forbidden")) return;
sendJson(res, 403, { error: {
message: GATEWAY_OPERATOR_ACCESS_DENIED_MESSAGE,
type: "forbidden"
} });
}
/** The listener belongs to this response, never to a reusable keep-alive socket. */
function bindHttpOperatorAccessAuthority(res, authority) {
if (!authority) return true;
if (!hasCurrentGatewayOperatorAccess(authority)) {
if (!res.writableEnded && !res.destroyed) sendGatewayHttpAuthFailure(res, {
ok: false,
reason: "operator_access_denied"
});
return false;
}
if (res.writableEnded || res.destroyed) return false;
const release = () => {
authority.signal.removeEventListener("abort", revoke);
res.off("finish", release);
res.off("close", release);
};
const revoke = () => {
release();
if (!res.writableEnded && !res.destroyed) res.destroy();
};
authority.signal.addEventListener("abort", revoke, { once: true });
res.once("finish", release);
res.once("close", release);
return true;
}
//#endregion
//#region src/gateway/http-request-authority.ts
/** Replaces startup custody with the live Gateway and one disconnect-aware request scope. */
function runGatewayHttpRequest(req, res, context, handle) {
const run = async () => {
const work = new AsyncWorkScope();
const requestContext = work.run(() => AsyncLocalStorage.snapshot());
const client = createHttpRequestAbortSignal(req, res);
const shutdown = context?.requestEntryLifetime?.signal;
const signal = shutdown ? AbortSignal.any([client.signal, shutdown]) : client.signal;
let cancelledBy;
const cancel = () => requestContext(() => {
cancelledBy = client.signal.aborted ? "client" : "shutdown";
work.beginClose(signal.reason);
if (cancelledBy === "shutdown" && !res.writableFinished && !res.destroyed) res.destroy();
});
signal.addEventListener("abort", cancel, { once: true });
if (signal.aborted) cancel();
let failed = false;
try {
await work.track(() => runHttpConnectionRequest(req, async () => {
failed = await handle() === "failed";
}, res));
} catch (error) {
failed = true;
throw error;
} finally {
signal.removeEventListener("abort", cancel);
client.cleanup();
if (cancelledBy && !failed && res.statusCode < 500 && !(cancelledBy === "client" && waitForHttpRequestRejection(req))) emitInternalDiagnosticEvent({
type: "gateway.http.cancelled",
source: cancelledBy
});
await requestContext(() => work.drain());
}
};
return context ? context.trackExecution(run) : run();
}
/** Authority ended; its response has already been answered or closed. */
var GatewayHttpRequestAuthorityError = class extends Error {};
/** Request owners consume authority outcomes; unexpected failures keep their own diagnostics. */
function finishGatewayHttpAuthorityError(res, error) {
const signal = getAsyncWorkSignal();
if (signal?.aborted && (error === signal.reason || isAbortError(error))) {
if (!res.writableEnded && !res.destroyed) res.destroy();
return true;
}
if (error instanceof GatewayOperatorAccessDeniedError) {
sendGatewayHttpAuthFailure(res, {
ok: false,
reason: "operator_access_denied"
});
return true;
}
return error instanceof GatewayHttpRequestAuthorityError;
}
function assertGatewayHttpRequestCurrent(requestAuth) {
if (requestAuth.hasCurrentClientAuthority?.() === false) throw new ToolAuthorizationError("Gateway requester authority changed");
}
function captureHttpRequestAuthority(params) {
const cfg = params.cfg ?? getRuntimeConfig();
const policy = captureGatewayAuthPolicy(cfg, null);
const authGeneration = resolveSharedGatewaySessionGeneration(params.auth, params.trustedProxies ?? cfg.gateway?.trustedProxies);
const roleRevision = cfg.gateway?.roles ? readOperatorRolePolicyRevision() : void 0;
const aliasRevision = readUserProfileAliasRevision();
return () => {
const current = params.getRuntimeConfig?.() ?? getRuntimeConfig();
return !params.req.socket?.destroyed && isGatewayAuthPolicyCurrent(policy, current) && (roleRevision === void 0 || roleRevision === readOperatorRolePolicyRevision()) && aliasRevision === readUserProfileAliasRevision() && authGeneration === resolveSharedGatewaySessionGeneration(params.getResolvedAuth?.() ?? params.auth, params.trustedProxies ?? current.gateway?.trustedProxies);
};
}
function bindHttpResponseAuthority(auth, res, hasCurrentClientAuthority) {
const assertCurrent = () => {
if (res.writableEnded || res.destroyed) throw new GatewayHttpRequestAuthorityError("HTTP request authority expired");
if (!hasCurrentGatewayOperatorAccess(auth.operatorAccessAuthority)) throw new GatewayOperatorAccessDeniedError();
if (!hasCurrentClientAuthority()) {
sendUnauthorized(res);
throw new GatewayHttpRequestAuthorityError("Unauthorized");
}
};
return {
...auth,
hasCurrentClientAuthority: () => !res.writableEnded && !res.destroyed && hasCurrentClientAuthority() && hasCurrentGatewayOperatorAccess(auth.operatorAccessAuthority),
assertCurrent,
revalidate: async () => assertCurrent()
};
}
//#endregion
export { finishGatewayHttpAuthorityError as a, sendGatewayHttpAuthFailure as c, captureHttpRequestAuthority as i, assertGatewayHttpRequestCurrent as n, runGatewayHttpRequest as o, bindHttpResponseAuthority as r, bindHttpOperatorAccessAuthority as s, GatewayHttpRequestAuthorityError as t };