openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
387 lines (386 loc) • 16.1 kB
JavaScript
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js";
import { t as normalizeAnyChannelId } from "./registry-normalize-CY8V_nbx.js";
import "./registry-COqNvmCg.js";
import { i as listLoadedChannelPlugins, r as getLoadedChannelPluginForRead, t as getLoadedChannelPluginById } from "./registry-loaded-Bh7xuMJh.js";
import { t as resolveAccountEntry } from "./account-lookup-CvFhSs3G.js";
import { t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-constants-2zSoJXQC.js";
import { n as normalizeMessageChannel } from "./message-channel-core-AaEWic-A.js";
import { a as isInternalMessageChannel } from "./message-channel-BQrhwUEA.js";
//#region src/auto-reply/sender-identity.ts
/** Shared sender identity helpers for authorization checks. */
function isConversationLikeIdentity(value) {
const normalized = normalizeOptionalLowercaseString(value);
if (!normalized) return false;
if (normalized.startsWith("chat_id:")) return true;
return /(^|:)(channel|group|thread|topic|room|space|spaces):/.test(normalized);
}
function shouldUseFromAsSenderFallback(params) {
const from = normalizeOptionalString(params.from) ?? "";
if (!from) return false;
const chatType = normalizeLowercaseStringOrEmpty(params.chatType);
if (chatType && chatType !== "direct") return false;
return !isConversationLikeIdentity(from);
}
//#endregion
//#region src/auto-reply/command-auth.ts
/** Command authorization helpers for owner and allowlist checks. */
function resolveProviderFromContext(ctx, cfg) {
const explicitMessageChannels = [
ctx.Surface,
ctx.OriginatingChannel,
ctx.Provider
].map((value) => normalizeMessageChannel(value)).filter((value) => Boolean(value));
const explicitMessageChannel = explicitMessageChannels.find((value) => value !== INTERNAL_MESSAGE_CHANNEL);
if (!explicitMessageChannel && explicitMessageChannels.includes("webchat")) return {
providerId: void 0,
hadResolutionError: false
};
const direct = normalizeAnyChannelId(explicitMessageChannel ?? void 0) ?? explicitMessageChannel ?? normalizeAnyChannelId(ctx.Provider) ?? normalizeAnyChannelId(ctx.Surface) ?? normalizeAnyChannelId(ctx.OriginatingChannel);
if (direct) return {
providerId: direct,
hadResolutionError: false
};
const candidates = [ctx.From, ctx.To].filter((value) => Boolean(value?.trim())).flatMap((value) => value.split(":").map((part) => part.trim()));
for (const candidate of candidates) {
const normalizedCandidateChannel = normalizeMessageChannel(candidate);
if (normalizedCandidateChannel === "webchat") return {
providerId: void 0,
hadResolutionError: false
};
const normalized = normalizeAnyChannelId(normalizedCandidateChannel ?? void 0) ?? normalizedCandidateChannel ?? normalizeAnyChannelId(candidate);
if (normalized) return {
providerId: normalized,
hadResolutionError: false
};
}
const inferredProviders = probeInferredProviders(ctx, cfg);
const inferred = inferredProviders.candidates[0];
if (inferredProviders.candidates.length === 1 && inferred) return inferred;
return {
providerId: void 0,
hadResolutionError: inferredProviders.droppedResolutionError || inferredProviders.candidates.some((entry) => entry.hadResolutionError)
};
}
function probeInferredProviders(ctx, cfg) {
let droppedResolutionError = false;
const candidates = [];
for (const plugin of listLoadedChannelPlugins()) {
const resolved = resolveProviderAllowFrom({
plugin,
cfg,
accountId: ctx.AccountId
});
if (resolved.allowFromList.length > 0) candidates.push({
providerId: plugin.id,
hadResolutionError: resolved.hadResolutionError
});
else if (resolved.hadResolutionError) droppedResolutionError = true;
}
return {
candidates,
droppedResolutionError
};
}
function formatAllowFromList(params) {
const { plugin, cfg, accountId, allowFrom } = params;
if (!allowFrom || allowFrom.length === 0) return [];
if (plugin?.config?.formatAllowFrom) return plugin.config.formatAllowFrom({
cfg,
accountId,
allowFrom
});
return normalizeStringEntries(allowFrom);
}
function normalizeAllowFromEntry(params) {
return formatAllowFromList({
...params,
allowFrom: [params.value]
}).filter((entry) => Boolean(entry.trim()));
}
function isWildcardAllowFromEntry(entry) {
return entry.trim() === "*";
}
function hasWildcardAllowFrom(list) {
return list.some((entry) => isWildcardAllowFromEntry(entry));
}
function stripWildcardAllowFrom(list) {
return list.filter((entry) => !isWildcardAllowFromEntry(entry));
}
function resolveProviderAllowFrom(params) {
const { plugin, cfg, accountId } = params;
const providerId = params.forceFallbackResolutionError ? params.providerId ?? plugin?.id : plugin?.id;
const resolveFallback = () => resolveFallbackAllowFrom({
cfg,
providerId,
accountId
});
let hadResolutionError = Boolean(params.forceFallbackResolutionError);
let allowFrom;
if (hadResolutionError || !plugin?.config?.resolveAllowFrom) allowFrom = resolveFallback();
else try {
const resolved = plugin.config.resolveAllowFrom({
cfg,
accountId
});
if (resolved == null || Array.isArray(resolved)) allowFrom = resolved ?? [];
else {
console.warn(`[command-auth] resolveAllowFrom returned an invalid allowFrom for provider "${providerId}", falling back to config allowFrom: invalid_result`);
hadResolutionError = true;
allowFrom = resolveFallback();
}
} catch (err) {
console.warn(`[command-auth] resolveAllowFrom threw for provider "${providerId}", falling back to config allowFrom: ${describeAllowFromResolutionError(err)}`);
hadResolutionError = true;
allowFrom = resolveFallback();
}
return {
allowFrom,
allowFromList: formatAllowFromList({
plugin,
cfg,
accountId,
allowFrom
}),
hadResolutionError
};
}
function describeAllowFromResolutionError(err) {
if (err instanceof Error) return (normalizeOptionalString(err.name) ?? "") || "Error";
return "unknown_error";
}
function resolveOwnerAllowFromList(params) {
const raw = params.allowFrom ?? params.cfg.commands?.ownerAllowFrom;
if (!Array.isArray(raw) || raw.length === 0) return [];
const filtered = [];
for (const entry of raw) {
const trimmed = normalizeOptionalString(String(entry ?? "")) ?? "";
if (!trimmed) continue;
const separatorIndex = trimmed.indexOf(":");
if (separatorIndex > 0) {
const prefix = trimmed.slice(0, separatorIndex);
const channel = normalizeAnyChannelId(prefix);
if (channel) {
if (!params.providerId || channel !== params.providerId) continue;
const remainder = trimmed.slice(separatorIndex + 1).trim();
if (remainder) filtered.push(remainder);
continue;
}
}
filtered.push(trimmed);
}
return formatAllowFromList({
...params,
allowFrom: filtered
});
}
/**
* Resolves the commands.allowFrom list for a given provider.
* Returns the provider-specific list if defined, otherwise the "*" global list.
* Returns null if commands.allowFrom is not configured at all (fall back to channel allowFrom).
*/
function resolveCommandsAllowFromList(params) {
const commandsAllowFrom = params.cfg.commands?.allowFrom;
if (!commandsAllowFrom || typeof commandsAllowFrom !== "object") return null;
const providerList = commandsAllowFrom[params.providerId ?? ""];
const globalList = commandsAllowFrom["*"];
const rawList = Array.isArray(providerList) ? providerList : globalList;
if (!Array.isArray(rawList)) return null;
return formatAllowFromList({
...params,
allowFrom: rawList
});
}
function resolveOwnerCandidatesForCommands(params) {
if (params.allowAll) return [];
const ownerCandidatesForCommands = stripWildcardAllowFrom(params.allowFromList);
if (ownerCandidatesForCommands.length > 0 || !params.to) return ownerCandidatesForCommands;
return normalizeAllowFromEntry({
...params,
value: params.to
});
}
function resolveOwnerAuthorizationState(params) {
const configOwnerAllowFromList = resolveOwnerAllowFromList({
...params,
allowFrom: params.configOwnerAllowFrom
});
const contextOwnerAllowFromList = resolveOwnerAllowFromList({
...params,
allowFrom: params.contextOwnerAllowFrom
});
const allowAll = !params.hadResolutionError && (params.allowFromList.length === 0 || hasWildcardAllowFrom(params.allowFromList));
const channelCommandOwners = resolveOwnerCandidatesForCommands({
...params,
allowAll
});
const explicitOwners = Array.from(new Set(stripWildcardAllowFrom(configOwnerAllowFromList)));
const contextCommandOwners = stripWildcardAllowFrom(contextOwnerAllowFromList);
return {
commandOwnerCandidates: Array.from(new Set(explicitOwners.length > 0 ? explicitOwners : contextCommandOwners.length > 0 ? contextCommandOwners : channelCommandOwners)),
explicitOwners
};
}
function resolveCommandSenderAuthorization(params) {
if (params.enforceOwnerForCommands && !params.isOwnerForCommands) return "denied";
if (params.commandsAllowFromList !== null || params.providerResolutionError && params.commandsAllowFromConfigured) {
const commandsAllowFromList = params.commandsAllowFromList;
const commandsAllowAll = !params.providerResolutionError && Boolean(commandsAllowFromList && hasWildcardAllowFrom(commandsAllowFromList));
const matchedCommandsAllowFrom = commandsAllowFromList?.length ? params.senderCandidates.find((candidate) => commandsAllowFromList.includes(candidate)) : void 0;
return !params.providerResolutionError && (commandsAllowAll || Boolean(matchedCommandsAllowFrom)) ? "commands" : "denied";
}
if (!params.commandAuthorized) return "denied";
return params.isOwnerForCommands ? "commands" : "reset-only";
}
function resolveSenderCandidates(params) {
const { plugin, cfg, accountId } = params;
const candidates = [];
const pushCandidate = (value) => {
const trimmed = normalizeOptionalString(value) ?? "";
if (!trimmed) return;
candidates.push(trimmed);
};
if (plugin?.commands?.preferSenderE164ForCommands) {
pushCandidate(params.senderE164);
pushCandidate(params.senderId);
} else {
pushCandidate(params.senderId);
pushCandidate(params.senderE164);
}
if (candidates.length === 0 && shouldUseFromAsSenderFallback({
from: params.from,
chatType: params.chatType
})) pushCandidate(params.from);
const normalized = [];
for (const sender of candidates) {
const entries = normalizeAllowFromEntry({
plugin,
cfg,
accountId,
value: sender
});
for (const entry of entries) if (!normalized.includes(entry)) normalized.push(entry);
}
return normalized;
}
function resolveFallbackAllowFrom(params) {
const providerId = normalizeOptionalString(params.providerId);
if (!providerId) return [];
const channelCfg = params.cfg.channels?.[providerId];
const accountCfg = resolveFallbackAccountConfig(channelCfg?.accounts, params.accountId) ?? resolveFallbackDefaultAccountConfig(channelCfg);
const allowFrom = accountCfg?.allowFrom ?? accountCfg?.dm?.allowFrom ?? channelCfg?.allowFrom ?? channelCfg?.dm?.allowFrom;
return Array.isArray(allowFrom) ? allowFrom : [];
}
function resolveFallbackAccountConfig(accounts, accountId) {
const normalizedAccountId = normalizeOptionalLowercaseString(accountId);
if (!accounts || !normalizedAccountId) return;
return accounts[normalizedAccountId] ?? resolveAccountEntry(accounts, normalizedAccountId);
}
function resolveFallbackDefaultAccountConfig(channelCfg) {
const accounts = channelCfg?.accounts;
if (!accounts) return;
const preferred = resolveFallbackAccountConfig(accounts, channelCfg?.defaultAccount) ?? resolveFallbackAccountConfig(accounts, "default");
if (preferred) return preferred;
const definedAccounts = Object.values(accounts).filter(Boolean);
return definedAccounts.length === 1 ? definedAccounts[0] : void 0;
}
function resolveCommandAuthorizationState(params) {
const { ctx, cfg, commandAuthorized } = params;
const { providerId, hadResolutionError: providerResolutionError } = resolveProviderFromContext(ctx, cfg);
const plugin = providerId ? getLoadedChannelPluginById(providerId) : void 0;
const from = normalizeOptionalString(ctx.From) ?? "";
const to = normalizeOptionalString(ctx.To) ?? "";
const commandsAllowFromConfigured = Boolean(cfg.commands?.allowFrom && typeof cfg.commands.allowFrom === "object");
const commandsAllowFromList = resolveCommandsAllowFromList({
plugin,
cfg,
accountId: ctx.AccountId,
providerId
});
const resolvedAllowFrom = resolveProviderAllowFrom({
plugin,
cfg,
accountId: ctx.AccountId,
providerId,
forceFallbackResolutionError: providerResolutionError
});
const ownerState = resolveOwnerAuthorizationState({
plugin,
cfg,
accountId: ctx.AccountId,
providerId,
to,
allowFromList: resolvedAllowFrom.allowFromList,
hadResolutionError: resolvedAllowFrom.hadResolutionError,
configOwnerAllowFrom: cfg.commands?.ownerAllowFrom,
contextOwnerAllowFrom: ctx.OwnerAllowFrom
});
const senderCandidates = resolveSenderCandidates({
plugin,
cfg,
accountId: ctx.AccountId,
senderId: ctx.SenderId,
senderE164: ctx.SenderE164,
from,
chatType: ctx.ChatType
});
const matchedSender = ownerState.explicitOwners.length ? senderCandidates.find((candidate) => ownerState.explicitOwners.includes(candidate)) : void 0;
const matchedCommandOwner = ownerState.commandOwnerCandidates.length ? senderCandidates.find((candidate) => ownerState.commandOwnerCandidates.includes(candidate)) : void 0;
const senderId = matchedSender ?? matchedCommandOwner ?? senderCandidates[0];
const enforceOwner = Boolean(plugin?.commands?.enforceOwnerForCommands);
const senderIsOwnerByIdentity = Boolean(matchedSender);
const senderIsOwnerByScope = isInternalMessageChannel(ctx.Provider) && Array.isArray(ctx.GatewayClientScopes) && ctx.GatewayClientScopes.includes("operator.admin");
const ownerAllowlistConfigured = ownerState.explicitOwners.length > 0;
const senderIsOwner = senderIsOwnerByIdentity || senderIsOwnerByScope;
const isOwnerForCommands = !(enforceOwner || ownerAllowlistConfigured) ? true : ownerAllowlistConfigured ? senderIsOwner : senderIsOwnerByScope || Boolean(matchedCommandOwner);
const access = ctx.CommandInterpretationSuppressed === true ? "denied" : resolveCommandSenderAuthorization({
commandAuthorized,
enforceOwnerForCommands: enforceOwner,
isOwnerForCommands,
senderCandidates,
commandsAllowFromList,
providerResolutionError,
commandsAllowFromConfigured
});
return {
authorization: {
providerId,
ownerList: ownerState.explicitOwners,
senderId: senderId || void 0,
senderIsOwner,
isAuthorizedSender: access === "commands",
from: from || void 0,
to: to || void 0
},
access
};
}
function resolveCommandAuthorization(params) {
return resolveCommandAuthorizationState(params).authorization;
}
/** Recheck admitted sender identity against the current global owner list. */
function isConfiguredCommandOwner(cfg, requester) {
const providerId = normalizeAnyChannelId(requester.channel) ?? requester.channel;
const params = {
cfg,
plugin: providerId ? getLoadedChannelPluginForRead(providerId) : void 0,
providerId,
accountId: requester.accountId
};
const owners = stripWildcardAllowFrom(resolveOwnerAllowFromList(params));
return resolveSenderCandidates({
...params,
senderId: requester.senderId
}).some((sender) => owners.includes(sender));
}
/** Resolves reset admission without granting other command or owner authority. */
function isResetAuthorizedForContext(params) {
if (resolveCommandAuthorizationState(params).access === "denied") return false;
const provider = params.ctx.Provider;
if (!(provider ? isInternalMessageChannel(provider) : isInternalMessageChannel(params.ctx.Surface))) return true;
const scopes = params.ctx.GatewayClientScopes;
if (!Array.isArray(scopes) || scopes.length === 0) return true;
return scopes.includes("operator.admin");
}
//#endregion
export { shouldUseFromAsSenderFallback as i, isResetAuthorizedForContext as n, resolveCommandAuthorization as r, isConfiguredCommandOwner as t };