Files
openclaw/src/plugin-sdk/approval-auth-helpers.ts
Agustin Rivera 08a73dbe4b fix(qqbot): gate fallback approval buttons (#87154)
QQBot fallback approval buttons now reuse the same slash-command authorization path as real commands, including access groups and default-account config merging.

Verification:
- node scripts/test-extension.mjs qqbot
- node --max-old-space-size=8192 --import tsx scripts/generate-plugin-sdk-api-baseline.ts --check && git diff --check
- pnpm lint --threads=8
- node scripts/run-vitest.mjs src/agents/agent-command.live-model-switch.test.ts
- GitHub PR checks for 7cc0f15031: passed

Thanks @eleqtrizit.

Co-authored-by: Agustin Rivera <agustin@rivera-web.com>
2026-05-27 08:44:55 +01:00

76 lines
2.4 KiB
TypeScript

import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { OpenClawConfig } from "./config-runtime.js";
type ApprovalKind = "exec" | "plugin";
type ApprovalAuthorizationResult = {
authorized: boolean;
reason?: string;
};
const IMPLICIT_SAME_CHAT_APPROVAL_AUTHORIZATION = Symbol(
"openclaw.implicitSameChatApprovalAuthorization",
);
export function markImplicitSameChatApprovalAuthorization(
result: ApprovalAuthorizationResult,
): ApprovalAuthorizationResult {
// Keep this non-enumerable to avoid changing auth payload shape.
// Consumers must pass the same object reference to
// `isImplicitSameChatApprovalAuthorization`; spread/Object.assign/JSON clones
// drop this marker.
Object.defineProperty(result, IMPLICIT_SAME_CHAT_APPROVAL_AUTHORIZATION, {
value: true,
enumerable: false,
});
return result;
}
export function isImplicitSameChatApprovalAuthorization(
result: ApprovalAuthorizationResult | null | undefined,
): boolean {
return Boolean(
result &&
(
result as ApprovalAuthorizationResult & {
[IMPLICIT_SAME_CHAT_APPROVAL_AUTHORIZATION]?: true;
}
)[IMPLICIT_SAME_CHAT_APPROVAL_AUTHORIZATION],
);
}
export function createResolvedApproverActionAuthAdapter(params: {
channelLabel: string;
resolveApprovers: (params: { cfg: OpenClawConfig; accountId?: string | null }) => string[];
normalizeSenderId?: (value: string) => string | undefined;
}) {
const normalizeSenderId = params.normalizeSenderId ?? normalizeOptionalString;
return {
authorizeActorAction({
cfg,
accountId,
senderId,
approvalKind,
}: {
cfg: OpenClawConfig;
accountId?: string | null;
senderId?: string | null;
action: "approve";
approvalKind: ApprovalKind;
}) {
const approvers = params.resolveApprovers({ cfg, accountId });
if (approvers.length === 0) {
// Empty approver sets are implicit same-chat fallback, not explicit approver bypass.
return markImplicitSameChatApprovalAuthorization({ authorized: true });
}
const normalizedSenderId = senderId ? normalizeSenderId(senderId) : undefined;
if (normalizedSenderId && approvers.includes(normalizedSenderId)) {
return { authorized: true } as const;
}
return {
authorized: false,
reason: `❌ You are not authorized to approve ${approvalKind} requests on ${params.channelLabel}.`,
} as const;
},
};
}