UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

150 lines (149 loc) 5.29 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { t as safeEqualSecret } from "./secret-equal-DRsL8lKD.js"; import { s as isLoopbackHost } from "./net-DbNPs6Xm.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./ssrf-runtime-Bum5C6NN.js"; import "./security-runtime-Ckf0kc0h.js"; import { createServer } from "node:http"; import express from "express"; //#region extensions/browser/src/browser/http-listen.ts function listenBrowserHttpServer(app, port, host) { return new Promise((resolve, reject) => { const server = createServer(app); const onError = (error) => { server.off("listening", onListening); reject(error); }; const onListening = () => { server.off("error", onError); resolve(server); }; server.once("error", onError); server.once("listening", onListening); server.listen(port, host); }); } //#endregion //#region extensions/browser/src/browser/csrf.ts function firstHeader(value) { return Array.isArray(value) ? value[0] ?? "" : value ?? ""; } function isMutatingMethod(method) { const m = (method || "").trim().toUpperCase(); return m === "POST" || m === "PUT" || m === "PATCH" || m === "DELETE"; } function isLoopbackUrl(value) { const v = value.trim(); if (!v || v === "null") return false; try { const parsed = new URL(v); return isLoopbackHost(parsed.hostname); } catch { return false; } } /** Return true when a request should be rejected as browser-originated CSRF. */ function shouldRejectBrowserMutation(params) { if (!isMutatingMethod(params.method)) return false; if (normalizeLowercaseStringOrEmpty(params.secFetchSite) === "cross-site") return true; const origin = (params.origin ?? "").trim(); if (origin) return !isLoopbackUrl(origin); const referer = (params.referer ?? "").trim(); if (referer) return !isLoopbackUrl(referer); return false; } /** Create middleware that rejects unsafe browser-control mutations. */ function browserMutationGuardMiddleware() { return (req, res, next) => { const method = (req.method || "").trim().toUpperCase(); if (method === "OPTIONS") return next(); if (shouldRejectBrowserMutation({ method, origin: firstHeader(req.headers.origin), referer: firstHeader(req.headers.referer), secFetchSite: firstHeader(req.headers["sec-fetch-site"]) })) { res.status(403).send("Forbidden"); return; } next(); }; } //#endregion //#region extensions/browser/src/browser/http-auth.ts function firstHeaderValue(value) { return Array.isArray(value) ? value[0] ?? "" : value ?? ""; } function parseBearerToken(authorization) { if (!normalizeLowercaseStringOrEmpty(authorization).startsWith("bearer ")) return; return authorization.slice(7).trim() || void 0; } function parseBasicPassword(authorization) { if (!normalizeLowercaseStringOrEmpty(authorization).startsWith("basic ")) return; const encoded = authorization.slice(6).trim(); if (!encoded) return; try { const decoded = Buffer.from(encoded, "base64").toString("utf8"); const sep = decoded.indexOf(":"); if (sep < 0) return; return decoded.slice(sep + 1).trim() || void 0; } catch { return; } } /** Return true when request headers satisfy browser-control auth. */ function isAuthorizedBrowserRequest(req, auth) { const authorization = firstHeaderValue(req.headers.authorization).trim(); if (auth.token) { const bearer = parseBearerToken(authorization); if (bearer && safeEqualSecret(bearer, auth.token)) return true; } if (auth.password) { const passwordHeader = firstHeaderValue(req.headers["x-openclaw-password"]).trim(); if (passwordHeader && safeEqualSecret(passwordHeader, auth.password)) return true; const basicPassword = parseBasicPassword(authorization); if (basicPassword && safeEqualSecret(basicPassword, auth.password)) return true; } return false; } //#endregion //#region extensions/browser/src/browser/server-middleware.ts const BROWSER_AUTH_VERIFIED_FLAG = "__openclawBrowserAuthVerified"; /** Returns whether Browser auth middleware already verified this request. */ function hasVerifiedBrowserAuth(req) { return req[BROWSER_AUTH_VERIFIED_FLAG] === true; } function markVerifiedBrowserAuth(req) { req[BROWSER_AUTH_VERIFIED_FLAG] = true; } /** Installs common Browser control-server middleware. */ function installBrowserCommonMiddleware(app) { app.use((req, res, next) => { const ctrl = new AbortController(); const abort = () => ctrl.abort(/* @__PURE__ */ new Error("request aborted")); req.once("aborted", abort); res.once("close", () => { if (!res.writableEnded) abort(); }); Object.defineProperty(req, "signal", { value: ctrl.signal, configurable: true }); next(); }); app.use(browserMutationGuardMiddleware()); app.use(express.json({ limit: "1mb" })); } /** Installs optional token/password auth for Browser control-server requests. */ function installBrowserAuthMiddleware(app, auth) { if (!auth.token && !auth.password) return; app.use((req, res, next) => { if (isAuthorizedBrowserRequest(req, auth)) { markVerifiedBrowserAuth(req); return next(); } res.status(401).send("Unauthorized"); }); } //#endregion export { listenBrowserHttpServer as i, installBrowserAuthMiddleware as n, installBrowserCommonMiddleware as r, hasVerifiedBrowserAuth as t };