| import type { ChannelDock } from "../channels/dock.js"; |
| import { getChannelDock, listChannelDocks } from "../channels/dock.js"; |
| import type { ChannelId } from "../channels/plugins/types.js"; |
| import { normalizeAnyChannelId } from "../channels/registry.js"; |
| import type { OpenClawConfig } from "../config/config.js"; |
| import type { MsgContext } from "./templating.js"; |
|
|
| export type CommandAuthorization = { |
| providerId?: ChannelId; |
| ownerList: string[]; |
| senderId?: string; |
| isAuthorizedSender: boolean; |
| from?: string; |
| to?: string; |
| }; |
|
|
| function resolveProviderFromContext(ctx: MsgContext, cfg: OpenClawConfig): ChannelId | undefined { |
| const direct = |
| normalizeAnyChannelId(ctx.Provider) ?? |
| normalizeAnyChannelId(ctx.Surface) ?? |
| normalizeAnyChannelId(ctx.OriginatingChannel); |
| if (direct) { |
| return direct; |
| } |
| const candidates = [ctx.From, ctx.To] |
| .filter((value): value is string => Boolean(value?.trim())) |
| .flatMap((value) => value.split(":").map((part) => part.trim())); |
| for (const candidate of candidates) { |
| const normalized = normalizeAnyChannelId(candidate); |
| if (normalized) { |
| return normalized; |
| } |
| } |
| const configured = listChannelDocks() |
| .map((dock) => { |
| if (!dock.config?.resolveAllowFrom) { |
| return null; |
| } |
| const allowFrom = dock.config.resolveAllowFrom({ |
| cfg, |
| accountId: ctx.AccountId, |
| }); |
| if (!Array.isArray(allowFrom) || allowFrom.length === 0) { |
| return null; |
| } |
| return dock.id; |
| }) |
| .filter((value): value is ChannelId => Boolean(value)); |
| if (configured.length === 1) { |
| return configured[0]; |
| } |
| return undefined; |
| } |
|
|
| function formatAllowFromList(params: { |
| dock?: ChannelDock; |
| cfg: OpenClawConfig; |
| accountId?: string | null; |
| allowFrom: Array<string | number>; |
| }): string[] { |
| const { dock, cfg, accountId, allowFrom } = params; |
| if (!allowFrom || allowFrom.length === 0) { |
| return []; |
| } |
| if (dock?.config?.formatAllowFrom) { |
| return dock.config.formatAllowFrom({ cfg, accountId, allowFrom }); |
| } |
| return allowFrom.map((entry) => String(entry).trim()).filter(Boolean); |
| } |
|
|
| function normalizeAllowFromEntry(params: { |
| dock?: ChannelDock; |
| cfg: OpenClawConfig; |
| accountId?: string | null; |
| value: string; |
| }): string[] { |
| const normalized = formatAllowFromList({ |
| dock: params.dock, |
| cfg: params.cfg, |
| accountId: params.accountId, |
| allowFrom: [params.value], |
| }); |
| return normalized.filter((entry) => entry.trim().length > 0); |
| } |
|
|
| function resolveSenderCandidates(params: { |
| dock?: ChannelDock; |
| providerId?: ChannelId; |
| cfg: OpenClawConfig; |
| accountId?: string | null; |
| senderId?: string | null; |
| senderE164?: string | null; |
| from?: string | null; |
| }): string[] { |
| const { dock, cfg, accountId } = params; |
| const candidates: string[] = []; |
| const pushCandidate = (value?: string | null) => { |
| const trimmed = (value ?? "").trim(); |
| if (!trimmed) { |
| return; |
| } |
| candidates.push(trimmed); |
| }; |
| if (params.providerId === "whatsapp") { |
| pushCandidate(params.senderE164); |
| pushCandidate(params.senderId); |
| } else { |
| pushCandidate(params.senderId); |
| pushCandidate(params.senderE164); |
| } |
| pushCandidate(params.from); |
|
|
| const normalized: string[] = []; |
| for (const sender of candidates) { |
| const entries = normalizeAllowFromEntry({ dock, cfg, accountId, value: sender }); |
| for (const entry of entries) { |
| if (!normalized.includes(entry)) { |
| normalized.push(entry); |
| } |
| } |
| } |
| return normalized; |
| } |
|
|
| export function resolveCommandAuthorization(params: { |
| ctx: MsgContext; |
| cfg: OpenClawConfig; |
| commandAuthorized: boolean; |
| }): CommandAuthorization { |
| const { ctx, cfg, commandAuthorized } = params; |
| const providerId = resolveProviderFromContext(ctx, cfg); |
| const dock = providerId ? getChannelDock(providerId) : undefined; |
| const from = (ctx.From ?? "").trim(); |
| const to = (ctx.To ?? "").trim(); |
| const allowFromRaw = dock?.config?.resolveAllowFrom |
| ? dock.config.resolveAllowFrom({ cfg, accountId: ctx.AccountId }) |
| : []; |
| const allowFromList = formatAllowFromList({ |
| dock, |
| cfg, |
| accountId: ctx.AccountId, |
| allowFrom: Array.isArray(allowFromRaw) ? allowFromRaw : [], |
| }); |
| const allowAll = |
| allowFromList.length === 0 || allowFromList.some((entry) => entry.trim() === "*"); |
|
|
| const ownerCandidates = allowAll ? [] : allowFromList.filter((entry) => entry !== "*"); |
| if (!allowAll && ownerCandidates.length === 0 && to) { |
| const normalizedTo = normalizeAllowFromEntry({ |
| dock, |
| cfg, |
| accountId: ctx.AccountId, |
| value: to, |
| }); |
| if (normalizedTo.length > 0) { |
| ownerCandidates.push(...normalizedTo); |
| } |
| } |
| const ownerList = Array.from(new Set(ownerCandidates)); |
|
|
| const senderCandidates = resolveSenderCandidates({ |
| dock, |
| providerId, |
| cfg, |
| accountId: ctx.AccountId, |
| senderId: ctx.SenderId, |
| senderE164: ctx.SenderE164, |
| from, |
| }); |
| const matchedSender = ownerList.length |
| ? senderCandidates.find((candidate) => ownerList.includes(candidate)) |
| : undefined; |
| const senderId = matchedSender ?? senderCandidates[0]; |
|
|
| const enforceOwner = Boolean(dock?.commands?.enforceOwnerForCommands); |
| const isOwner = !enforceOwner || allowAll || ownerList.length === 0 || Boolean(matchedSender); |
| const isAuthorizedSender = commandAuthorized && isOwner; |
|
|
| return { |
| providerId, |
| ownerList, |
| senderId: senderId || undefined, |
| isAuthorizedSender, |
| from: from || undefined, |
| to: to || undefined, |
| }; |
| } |
|
|