openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
115 lines (114 loc) • 4.78 kB
JavaScript
import { y as resolveStateDir } from "./paths-mvMm5bYV.js";
import "./json-files-2umMHm0W.js";
import { t as safeEqualSecret } from "./secret-equal-DRsL8lKD.js";
import path from "node:path";
import { randomBytes } from "node:crypto";
//#region src/shared/operator-scope-compat.ts
const OPERATOR_ROLE = "operator";
const OPERATOR_ADMIN_SCOPE = "operator.admin";
const OPERATOR_READ_SCOPE = "operator.read";
const OPERATOR_WRITE_SCOPE = "operator.write";
const OPERATOR_SCOPE_PREFIX = "operator.";
function normalizeScopeList(scopes) {
const out = /* @__PURE__ */ new Set();
for (const scope of scopes) {
const trimmed = scope.trim();
if (trimmed) out.add(trimmed);
}
return [...out];
}
function operatorScopeSatisfied(requestedScope, granted) {
if (!requestedScope.startsWith(OPERATOR_SCOPE_PREFIX)) return false;
if (granted.has(OPERATOR_ADMIN_SCOPE)) return true;
if (requestedScope === OPERATOR_READ_SCOPE) return granted.has(OPERATOR_READ_SCOPE) || granted.has(OPERATOR_WRITE_SCOPE);
if (requestedScope === OPERATOR_WRITE_SCOPE) return granted.has(OPERATOR_WRITE_SCOPE);
return granted.has(requestedScope);
}
/** Returns true when a role grant satisfies requested scopes, including operator implications. */
function roleScopesAllow(params) {
const requested = normalizeScopeList(params.requestedScopes);
if (requested.length === 0) return true;
const allowed = normalizeScopeList(params.allowedScopes);
if (allowed.length === 0) return false;
const allowedSet = new Set(allowed);
if (params.role.trim() !== OPERATOR_ROLE) {
const prefix = `${params.role.trim()}.`;
return requested.every((scope) => scope.startsWith(prefix) && allowedSet.has(scope));
}
return requested.every((scope) => operatorScopeSatisfied(scope, allowedSet));
}
/** Returns the first requested scope not covered by the role's allowed scopes. */
function resolveMissingRequestedScope(params) {
for (const scope of params.requestedScopes) if (!roleScopesAllow({
role: params.role,
requestedScopes: [scope],
allowedScopes: params.allowedScopes
})) return scope;
return null;
}
/** Returns the first requested scope that does not belong to any requested role. */
function resolveScopeOutsideRequestedRoles(params) {
for (const scope of params.requestedScopes) if (!params.requestedRoles.some((role) => roleScopesAllow({
role,
requestedScopes: [scope],
allowedScopes: [scope]
}))) return scope;
return null;
}
//#endregion
//#region src/infra/pairing-files.ts
/** Resolve pending/paired JSON file locations for one pairing namespace. */
function resolvePairingPaths(baseDir, subdir) {
const root = baseDir ?? resolveStateDir();
const dir = path.join(root, subdir);
return {
dir,
pendingPath: path.join(dir, "pending.json"),
pairedPath: path.join(dir, "paired.json")
};
}
/** Coerce persisted pairing maps, treating malformed arrays/scalars as empty state. */
function coercePairingStateRecord(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
return value;
}
/** Remove pending requests older than the caller's pairing TTL. */
function pruneExpiredPending(pendingById, nowMs, ttlMs) {
for (const [id, req] of Object.entries(pendingById)) if (nowMs - req.ts > ttlMs) delete pendingById[id];
}
/** Refresh one compatible pending request or replace a superseded request set atomically. */
async function reconcilePendingPairingRequests(params) {
if (params.existing.length === 1 && params.canRefreshSingle(params.existing[0], params.incoming)) {
const refreshed = params.refreshSingle(params.existing[0], params.incoming);
params.pendingById[refreshed.requestId] = refreshed;
await params.persist();
return {
status: "pending",
request: refreshed,
created: false
};
}
for (const existing of params.existing) delete params.pendingById[existing.requestId];
const request = params.buildReplacement({
existing: params.existing,
incoming: params.incoming
});
params.pendingById[request.requestId] = request;
await params.persist();
return {
status: "pending",
request,
created: true
};
}
/** Generate a URL-safe bearer token for pairing and bootstrap flows. */
function generatePairingToken() {
return randomBytes(32).toString("base64url");
}
/** Verify nonblank pairing tokens with constant-time secret comparison. */
function verifyPairingToken(provided, expected) {
if (provided.trim().length === 0 || expected.trim().length === 0) return false;
return safeEqualSecret(provided, expected);
}
//#endregion
export { reconcilePendingPairingRequests as a, resolveScopeOutsideRequestedRoles as c, pruneExpiredPending as i, roleScopesAllow as l, verifyPairingToken as n, resolvePairingPaths as o, coercePairingStateRecord as r, resolveMissingRequestedScope as s, generatePairingToken as t };