mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 13:14:58 +00:00
refactor(config): compile toolsBySender policy and migrate legacy keys
This commit is contained in:
@@ -378,6 +378,49 @@ describe("doctor config flow", () => {
|
|||||||
expect(cfg.channels.discord.accounts.work.allowFrom).toEqual(["*"]);
|
expect(cfg.channels.discord.accounts.work.allowFrom).toEqual(["*"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("migrates legacy toolsBySender keys to typed id entries on repair", async () => {
|
||||||
|
const result = await runDoctorConfigWithInput({
|
||||||
|
repair: true,
|
||||||
|
config: {
|
||||||
|
channels: {
|
||||||
|
whatsapp: {
|
||||||
|
groups: {
|
||||||
|
"123@g.us": {
|
||||||
|
toolsBySender: {
|
||||||
|
owner: { allow: ["exec"] },
|
||||||
|
alice: { deny: ["exec"] },
|
||||||
|
"id:owner": { deny: ["exec"] },
|
||||||
|
"username:@ops-bot": { allow: ["fs.read"] },
|
||||||
|
"*": { deny: ["exec"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
run: loadAndMaybeMigrateDoctorConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cfg = result.cfg as unknown as {
|
||||||
|
channels: {
|
||||||
|
whatsapp: {
|
||||||
|
groups: {
|
||||||
|
"123@g.us": {
|
||||||
|
toolsBySender: Record<string, { allow?: string[]; deny?: string[] }>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const toolsBySender = cfg.channels.whatsapp.groups["123@g.us"].toolsBySender;
|
||||||
|
expect(toolsBySender.owner).toBeUndefined();
|
||||||
|
expect(toolsBySender.alice).toBeUndefined();
|
||||||
|
expect(toolsBySender["id:owner"]).toEqual({ deny: ["exec"] });
|
||||||
|
expect(toolsBySender["id:alice"]).toEqual({ deny: ["exec"] });
|
||||||
|
expect(toolsBySender["username:@ops-bot"]).toEqual({ allow: ["fs.read"] });
|
||||||
|
expect(toolsBySender["*"]).toEqual({ deny: ["exec"] });
|
||||||
|
});
|
||||||
|
|
||||||
it("repairs googlechat dm.policy open by setting dm.allowFrom on repair", async () => {
|
it("repairs googlechat dm.policy open by setting dm.allowFrom on repair", async () => {
|
||||||
const result = await runDoctorConfigWithInput({
|
const result = await runDoctorConfigWithInput({
|
||||||
repair: true,
|
repair: true,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
readConfigFileSnapshot,
|
readConfigFileSnapshot,
|
||||||
} from "../config/config.js";
|
} from "../config/config.js";
|
||||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||||
|
import { parseToolsBySenderTypedKey } from "../config/types.tools.js";
|
||||||
import {
|
import {
|
||||||
listInterpreterLikeSafeBins,
|
listInterpreterLikeSafeBins,
|
||||||
resolveMergedSafeBinProfileFixtures,
|
resolveMergedSafeBinProfileFixtures,
|
||||||
@@ -836,6 +837,120 @@ function maybeRepairExecSafeBinProfiles(cfg: OpenClawConfig): {
|
|||||||
return { config: next, changes, warnings };
|
return { config: next, changes, warnings };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegacyToolsBySenderKeyHit = {
|
||||||
|
toolsBySenderPath: Array<string | number>;
|
||||||
|
pathLabel: string;
|
||||||
|
key: string;
|
||||||
|
targetKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function collectLegacyToolsBySenderKeyHits(
|
||||||
|
value: unknown,
|
||||||
|
pathParts: Array<string | number>,
|
||||||
|
hits: LegacyToolsBySenderKeyHit[],
|
||||||
|
) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (const [index, entry] of value.entries()) {
|
||||||
|
collectLegacyToolsBySenderKeyHits(entry, [...pathParts, index], hits);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const record = asObjectRecord(value);
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolsBySender = asObjectRecord(record.toolsBySender);
|
||||||
|
if (toolsBySender) {
|
||||||
|
const path = [...pathParts, "toolsBySender"];
|
||||||
|
const pathLabel = formatPath(path);
|
||||||
|
for (const rawKey of Object.keys(toolsBySender)) {
|
||||||
|
const trimmed = rawKey.trim();
|
||||||
|
if (!trimmed || trimmed === "*" || parseToolsBySenderTypedKey(trimmed)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
hits.push({
|
||||||
|
toolsBySenderPath: path,
|
||||||
|
pathLabel,
|
||||||
|
key: rawKey,
|
||||||
|
targetKey: `id:${trimmed}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, nested] of Object.entries(record)) {
|
||||||
|
if (key === "toolsBySender") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
collectLegacyToolsBySenderKeyHits(nested, [...pathParts, key], hits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanLegacyToolsBySenderKeys(cfg: OpenClawConfig): LegacyToolsBySenderKeyHit[] {
|
||||||
|
const hits: LegacyToolsBySenderKeyHit[] = [];
|
||||||
|
collectLegacyToolsBySenderKeyHits(cfg, [], hits);
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeRepairLegacyToolsBySenderKeys(cfg: OpenClawConfig): {
|
||||||
|
config: OpenClawConfig;
|
||||||
|
changes: string[];
|
||||||
|
} {
|
||||||
|
const next = structuredClone(cfg);
|
||||||
|
const hits = scanLegacyToolsBySenderKeys(next);
|
||||||
|
if (hits.length === 0) {
|
||||||
|
return { config: cfg, changes: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const summary = new Map<string, { migrated: number; dropped: number; examples: string[] }>();
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
for (const hit of hits) {
|
||||||
|
const toolsBySender = asObjectRecord(resolvePathTarget(next, hit.toolsBySenderPath));
|
||||||
|
if (!toolsBySender || !(hit.key in toolsBySender)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const row = summary.get(hit.pathLabel) ?? { migrated: 0, dropped: 0, examples: [] };
|
||||||
|
|
||||||
|
if (toolsBySender[hit.targetKey] === undefined) {
|
||||||
|
toolsBySender[hit.targetKey] = toolsBySender[hit.key];
|
||||||
|
row.migrated++;
|
||||||
|
if (row.examples.length < 3) {
|
||||||
|
row.examples.push(`${hit.key} -> ${hit.targetKey}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.dropped++;
|
||||||
|
if (row.examples.length < 3) {
|
||||||
|
row.examples.push(`${hit.key} (kept existing ${hit.targetKey})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete toolsBySender[hit.key];
|
||||||
|
summary.set(hit.pathLabel, row);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
return { config: cfg, changes: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const changes: string[] = [];
|
||||||
|
for (const [pathLabel, row] of summary) {
|
||||||
|
if (row.migrated > 0) {
|
||||||
|
const suffix = row.examples.length > 0 ? ` (${row.examples.join(", ")})` : "";
|
||||||
|
changes.push(
|
||||||
|
`- ${pathLabel}: migrated ${row.migrated} legacy key${row.migrated === 1 ? "" : "s"} to typed id: entries${suffix}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (row.dropped > 0) {
|
||||||
|
changes.push(
|
||||||
|
`- ${pathLabel}: removed ${row.dropped} legacy key${row.dropped === 1 ? "" : "s"} where typed id: entries already existed.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { config: next, changes };
|
||||||
|
}
|
||||||
|
|
||||||
async function maybeMigrateLegacyConfig(): Promise<string[]> {
|
async function maybeMigrateLegacyConfig(): Promise<string[]> {
|
||||||
const changes: string[] = [];
|
const changes: string[] = [];
|
||||||
const home = resolveHomeDir();
|
const home = resolveHomeDir();
|
||||||
@@ -991,6 +1106,15 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
|
|||||||
pendingChanges = true;
|
pendingChanges = true;
|
||||||
cfg = allowFromRepair.config;
|
cfg = allowFromRepair.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolsBySenderRepair = maybeRepairLegacyToolsBySenderKeys(candidate);
|
||||||
|
if (toolsBySenderRepair.changes.length > 0) {
|
||||||
|
note(toolsBySenderRepair.changes.join("\n"), "Doctor changes");
|
||||||
|
candidate = toolsBySenderRepair.config;
|
||||||
|
pendingChanges = true;
|
||||||
|
cfg = toolsBySenderRepair.config;
|
||||||
|
}
|
||||||
|
|
||||||
const safeBinProfileRepair = maybeRepairExecSafeBinProfiles(candidate);
|
const safeBinProfileRepair = maybeRepairExecSafeBinProfiles(candidate);
|
||||||
if (safeBinProfileRepair.changes.length > 0) {
|
if (safeBinProfileRepair.changes.length > 0) {
|
||||||
note(safeBinProfileRepair.changes.join("\n"), "Doctor changes");
|
note(safeBinProfileRepair.changes.join("\n"), "Doctor changes");
|
||||||
@@ -1035,6 +1159,20 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolsBySenderHits = scanLegacyToolsBySenderKeys(candidate);
|
||||||
|
if (toolsBySenderHits.length > 0) {
|
||||||
|
const sample = toolsBySenderHits[0];
|
||||||
|
const sampleLabel = sample ? `${sample.pathLabel}.${sample.key}` : "toolsBySender";
|
||||||
|
note(
|
||||||
|
[
|
||||||
|
`- Found ${toolsBySenderHits.length} legacy untyped toolsBySender key${toolsBySenderHits.length === 1 ? "" : "s"} (for example ${sampleLabel}).`,
|
||||||
|
"- Untyped sender keys are deprecated; use explicit prefixes (id:, e164:, username:, name:).",
|
||||||
|
`- Run "${formatCliCommand("openclaw doctor --fix")}" to migrate legacy keys to typed id: entries.`,
|
||||||
|
].join("\n"),
|
||||||
|
"Doctor warnings",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const safeBinCoverage = scanExecSafeBinCoverage(candidate);
|
const safeBinCoverage = scanExecSafeBinCoverage(candidate);
|
||||||
if (safeBinCoverage.length > 0) {
|
if (safeBinCoverage.length > 0) {
|
||||||
const interpreterHits = safeBinCoverage.filter((hit) => hit.isInterpreter);
|
const interpreterHits = safeBinCoverage.filter((hit) => hit.isInterpreter);
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import type { ChannelId } from "../channels/plugins/types.js";
|
import type { ChannelId } from "../channels/plugins/types.js";
|
||||||
import { normalizeAccountId } from "../routing/session-key.js";
|
import { normalizeAccountId } from "../routing/session-key.js";
|
||||||
import type { OpenClawConfig } from "./config.js";
|
import type { OpenClawConfig } from "./config.js";
|
||||||
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
|
import {
|
||||||
|
parseToolsBySenderTypedKey,
|
||||||
|
type GroupToolPolicyBySenderConfig,
|
||||||
|
type GroupToolPolicyConfig,
|
||||||
|
type ToolsBySenderKeyType,
|
||||||
|
} from "./types.tools.js";
|
||||||
|
|
||||||
export type GroupPolicyChannel = ChannelId;
|
export type GroupPolicyChannel = ChannelId;
|
||||||
|
|
||||||
@@ -51,15 +56,22 @@ export type GroupToolPolicySender = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type SenderKeyType = "id" | "e164" | "username" | "name";
|
type SenderKeyType = "id" | "e164" | "username" | "name";
|
||||||
|
type CompiledSenderPolicy = {
|
||||||
|
buckets: SenderPolicyBuckets;
|
||||||
|
wildcard?: GroupToolPolicyConfig;
|
||||||
|
};
|
||||||
|
|
||||||
const SENDER_KEY_TYPES: SenderKeyType[] = ["id", "e164", "username", "name"];
|
|
||||||
const warnedLegacyToolsBySenderKeys = new Set<string>();
|
const warnedLegacyToolsBySenderKeys = new Set<string>();
|
||||||
|
const compiledToolsBySenderCache = new WeakMap<
|
||||||
|
GroupToolPolicyBySenderConfig,
|
||||||
|
CompiledSenderPolicy
|
||||||
|
>();
|
||||||
|
|
||||||
type ParsedSenderPolicyKey =
|
type ParsedSenderPolicyKey =
|
||||||
| { kind: "wildcard" }
|
| { kind: "wildcard" }
|
||||||
| { kind: "typed"; type: SenderKeyType; key: string };
|
| { kind: "typed"; type: SenderKeyType; key: string };
|
||||||
|
|
||||||
type SenderPolicyBuckets = Record<SenderKeyType, Map<string, GroupToolPolicyConfig>>;
|
type SenderPolicyBuckets = Record<ToolsBySenderKeyType, Map<string, GroupToolPolicyConfig>>;
|
||||||
|
|
||||||
function normalizeSenderKey(
|
function normalizeSenderKey(
|
||||||
value: string,
|
value: string,
|
||||||
@@ -87,21 +99,6 @@ function normalizeLegacySenderKey(value: string): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTypedSenderKey(rawKey: string): { type: SenderKeyType; value: string } | undefined {
|
|
||||||
const lowered = rawKey.toLowerCase();
|
|
||||||
for (const type of SENDER_KEY_TYPES) {
|
|
||||||
const prefix = `${type}:`;
|
|
||||||
if (!lowered.startsWith(prefix)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
value: rawKey.slice(prefix.length),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function warnLegacyToolsBySenderKey(rawKey: string) {
|
function warnLegacyToolsBySenderKey(rawKey: string) {
|
||||||
const trimmed = rawKey.trim();
|
const trimmed = rawKey.trim();
|
||||||
if (!trimmed || warnedLegacyToolsBySenderKeys.has(trimmed)) {
|
if (!trimmed || warnedLegacyToolsBySenderKeys.has(trimmed)) {
|
||||||
@@ -125,7 +122,7 @@ function parseSenderPolicyKey(rawKey: string): ParsedSenderPolicyKey | undefined
|
|||||||
if (trimmed === "*") {
|
if (trimmed === "*") {
|
||||||
return { kind: "wildcard" };
|
return { kind: "wildcard" };
|
||||||
}
|
}
|
||||||
const typed = parseTypedSenderKey(trimmed);
|
const typed = parseToolsBySenderTypedKey(trimmed);
|
||||||
if (typed) {
|
if (typed) {
|
||||||
const key = normalizeTypedSenderKey(typed.value, typed.type);
|
const key = normalizeTypedSenderKey(typed.value, typed.type);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
@@ -160,39 +157,9 @@ function createSenderPolicyBuckets(): SenderPolicyBuckets {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeCandidate(value: string | null | undefined, type: SenderKeyType): string {
|
function compileToolsBySenderPolicy(
|
||||||
const trimmed = value?.trim();
|
toolsBySender: GroupToolPolicyBySenderConfig,
|
||||||
if (!trimmed) {
|
): CompiledSenderPolicy | undefined {
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return normalizeTypedSenderKey(trimmed, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeSenderIdCandidates(value: string | null | undefined): string[] {
|
|
||||||
const trimmed = value?.trim();
|
|
||||||
if (!trimmed) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const typed = normalizeTypedSenderKey(trimmed, "id");
|
|
||||||
const legacy = normalizeLegacySenderKey(trimmed);
|
|
||||||
if (!typed) {
|
|
||||||
return legacy ? [legacy] : [];
|
|
||||||
}
|
|
||||||
if (!legacy || legacy === typed) {
|
|
||||||
return [typed];
|
|
||||||
}
|
|
||||||
return [typed, legacy];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveToolsBySender(
|
|
||||||
params: {
|
|
||||||
toolsBySender?: GroupToolPolicyBySenderConfig;
|
|
||||||
} & GroupToolPolicySender,
|
|
||||||
): GroupToolPolicyConfig | undefined {
|
|
||||||
const toolsBySender = params.toolsBySender;
|
|
||||||
if (!toolsBySender) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const entries = Object.entries(toolsBySender);
|
const entries = Object.entries(toolsBySender);
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -218,34 +185,97 @@ export function resolveToolsBySender(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { buckets, wildcard };
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCompiledToolsBySenderPolicy(
|
||||||
|
toolsBySender: GroupToolPolicyBySenderConfig,
|
||||||
|
): CompiledSenderPolicy | undefined {
|
||||||
|
const cached = compiledToolsBySenderCache.get(toolsBySender);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
const compiled = compileToolsBySenderPolicy(toolsBySender);
|
||||||
|
if (!compiled) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Config is loaded once and treated as immutable; cache compiled sender policy by object identity.
|
||||||
|
compiledToolsBySenderCache.set(toolsBySender, compiled);
|
||||||
|
return compiled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCandidate(value: string | null | undefined, type: SenderKeyType): string {
|
||||||
|
const trimmed = value?.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return normalizeTypedSenderKey(trimmed, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSenderIdCandidates(value: string | null | undefined): string[] {
|
||||||
|
const trimmed = value?.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const typed = normalizeTypedSenderKey(trimmed, "id");
|
||||||
|
const legacy = normalizeLegacySenderKey(trimmed);
|
||||||
|
if (!typed) {
|
||||||
|
return legacy ? [legacy] : [];
|
||||||
|
}
|
||||||
|
if (!legacy || legacy === typed) {
|
||||||
|
return [typed];
|
||||||
|
}
|
||||||
|
return [typed, legacy];
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchToolsBySenderPolicy(
|
||||||
|
compiled: CompiledSenderPolicy,
|
||||||
|
params: GroupToolPolicySender,
|
||||||
|
): GroupToolPolicyConfig | undefined {
|
||||||
for (const senderIdCandidate of normalizeSenderIdCandidates(params.senderId)) {
|
for (const senderIdCandidate of normalizeSenderIdCandidates(params.senderId)) {
|
||||||
const match = buckets.id.get(senderIdCandidate);
|
const match = compiled.buckets.id.get(senderIdCandidate);
|
||||||
if (match) {
|
if (match) {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const senderE164 = normalizeCandidate(params.senderE164, "e164");
|
const senderE164 = normalizeCandidate(params.senderE164, "e164");
|
||||||
if (senderE164) {
|
if (senderE164) {
|
||||||
const match = buckets.e164.get(senderE164);
|
const match = compiled.buckets.e164.get(senderE164);
|
||||||
if (match) {
|
if (match) {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const senderUsername = normalizeCandidate(params.senderUsername, "username");
|
const senderUsername = normalizeCandidate(params.senderUsername, "username");
|
||||||
if (senderUsername) {
|
if (senderUsername) {
|
||||||
const match = buckets.username.get(senderUsername);
|
const match = compiled.buckets.username.get(senderUsername);
|
||||||
if (match) {
|
if (match) {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const senderName = normalizeCandidate(params.senderName, "name");
|
const senderName = normalizeCandidate(params.senderName, "name");
|
||||||
if (senderName) {
|
if (senderName) {
|
||||||
const match = buckets.name.get(senderName);
|
const match = compiled.buckets.name.get(senderName);
|
||||||
if (match) {
|
if (match) {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wildcard;
|
return compiled.wildcard;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveToolsBySender(
|
||||||
|
params: {
|
||||||
|
toolsBySender?: GroupToolPolicyBySenderConfig;
|
||||||
|
} & GroupToolPolicySender,
|
||||||
|
): GroupToolPolicyConfig | undefined {
|
||||||
|
const toolsBySender = params.toolsBySender;
|
||||||
|
if (!toolsBySender) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const compiled = resolveCompiledToolsBySenderPolicy(toolsBySender);
|
||||||
|
if (!compiled) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return matchToolsBySenderPolicy(compiled, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveChannelGroups(
|
function resolveChannelGroups(
|
||||||
|
|||||||
@@ -176,6 +176,30 @@ export type GroupToolPolicyConfig = {
|
|||||||
deny?: string[];
|
deny?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TOOLS_BY_SENDER_KEY_TYPES = ["id", "e164", "username", "name"] as const;
|
||||||
|
export type ToolsBySenderKeyType = (typeof TOOLS_BY_SENDER_KEY_TYPES)[number];
|
||||||
|
|
||||||
|
export function parseToolsBySenderTypedKey(
|
||||||
|
rawKey: string,
|
||||||
|
): { type: ToolsBySenderKeyType; value: string } | undefined {
|
||||||
|
const trimmed = rawKey.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const lowered = trimmed.toLowerCase();
|
||||||
|
for (const type of TOOLS_BY_SENDER_KEY_TYPES) {
|
||||||
|
const prefix = `${type}:`;
|
||||||
|
if (!lowered.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
value: trimmed.slice(prefix.length),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-sender overrides.
|
* Per-sender overrides.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user