mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 00:08:28 +00:00
pairing: isolate account-scoped allowlist and pending requests
This commit is contained in:
@@ -257,7 +257,7 @@ describe("pairing store", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reads sync allowFrom with scoped + legacy dedupe and wildcard filtering", async () => {
|
it("reads sync allowFrom with account-scoped isolation and wildcard filtering", async () => {
|
||||||
await withTempStateDir(async (stateDir) => {
|
await withTempStateDir(async (stateDir) => {
|
||||||
await writeAllowFromFixture({
|
await writeAllowFromFixture({
|
||||||
stateDir,
|
stateDir,
|
||||||
@@ -273,11 +273,37 @@ describe("pairing store", () => {
|
|||||||
|
|
||||||
const scoped = readChannelAllowFromStoreSync("telegram", process.env, "yy");
|
const scoped = readChannelAllowFromStoreSync("telegram", process.env, "yy");
|
||||||
const channelScoped = readChannelAllowFromStoreSync("telegram");
|
const channelScoped = readChannelAllowFromStoreSync("telegram");
|
||||||
expect(scoped).toEqual(["1002", "1001"]);
|
expect(scoped).toEqual(["1002", "1001", "1002"]);
|
||||||
expect(channelScoped).toEqual(["1001", "1001"]);
|
expect(channelScoped).toEqual(["1001", "1001"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not reuse pairing requests across accounts for the same sender id", async () => {
|
||||||
|
await withTempStateDir(async () => {
|
||||||
|
const first = await upsertChannelPairingRequest({
|
||||||
|
channel: "telegram",
|
||||||
|
accountId: "alpha",
|
||||||
|
id: "12345",
|
||||||
|
});
|
||||||
|
const second = await upsertChannelPairingRequest({
|
||||||
|
channel: "telegram",
|
||||||
|
accountId: "beta",
|
||||||
|
id: "12345",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(first.created).toBe(true);
|
||||||
|
expect(second.created).toBe(true);
|
||||||
|
expect(second.code).not.toBe(first.code);
|
||||||
|
|
||||||
|
const alpha = await listChannelPairingRequests("telegram", process.env, "alpha");
|
||||||
|
const beta = await listChannelPairingRequests("telegram", process.env, "beta");
|
||||||
|
expect(alpha).toHaveLength(1);
|
||||||
|
expect(beta).toHaveLength(1);
|
||||||
|
expect(alpha[0]?.code).toBe(first.code);
|
||||||
|
expect(beta[0]?.code).toBe(second.code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("reads legacy channel-scoped allowFrom for default account", async () => {
|
it("reads legacy channel-scoped allowFrom for default account", async () => {
|
||||||
await withTempStateDir(async (stateDir) => {
|
await withTempStateDir(async (stateDir) => {
|
||||||
await writeAllowFromFixture({ stateDir, channel: "telegram", allowFrom: ["1001"] });
|
await writeAllowFromFixture({ stateDir, channel: "telegram", allowFrom: ["1001"] });
|
||||||
|
|||||||
@@ -218,6 +218,12 @@ function requestMatchesAccountId(entry: PairingRequest, normalizedAccountId: str
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldIncludeLegacyAllowFromEntries(normalizedAccountId: string): boolean {
|
||||||
|
// Keep backward compatibility for legacy channel-scoped allowFrom only on default account.
|
||||||
|
// Non-default accounts should remain isolated to avoid cross-account implicit approvals.
|
||||||
|
return !normalizedAccountId || normalizedAccountId === "default";
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeId(value: string | number): string {
|
function normalizeId(value: string | number): string {
|
||||||
return String(value).trim();
|
return String(value).trim();
|
||||||
}
|
}
|
||||||
@@ -344,8 +350,11 @@ export async function readChannelAllowFromStore(
|
|||||||
|
|
||||||
const scopedPath = resolveAllowFromPath(channel, env, accountId);
|
const scopedPath = resolveAllowFromPath(channel, env, accountId);
|
||||||
const scopedEntries = await readAllowFromStateForPath(channel, scopedPath);
|
const scopedEntries = await readAllowFromStateForPath(channel, scopedPath);
|
||||||
|
if (!shouldIncludeLegacyAllowFromEntries(normalizedAccountId)) {
|
||||||
|
return scopedEntries;
|
||||||
|
}
|
||||||
// Backward compatibility: legacy channel-level allowFrom store was unscoped.
|
// Backward compatibility: legacy channel-level allowFrom store was unscoped.
|
||||||
// Keep honoring it alongside account-scoped files to prevent re-pair prompts after upgrades.
|
// Keep honoring it for default account to prevent re-pair prompts after upgrades.
|
||||||
const legacyPath = resolveAllowFromPath(channel, env);
|
const legacyPath = resolveAllowFromPath(channel, env);
|
||||||
const legacyEntries = await readAllowFromStateForPath(channel, legacyPath);
|
const legacyEntries = await readAllowFromStateForPath(channel, legacyPath);
|
||||||
return dedupePreserveOrder([...scopedEntries, ...legacyEntries]);
|
return dedupePreserveOrder([...scopedEntries, ...legacyEntries]);
|
||||||
@@ -364,6 +373,9 @@ export function readChannelAllowFromStoreSync(
|
|||||||
|
|
||||||
const scopedPath = resolveAllowFromPath(channel, env, accountId);
|
const scopedPath = resolveAllowFromPath(channel, env, accountId);
|
||||||
const scopedEntries = readAllowFromStateForPathSync(channel, scopedPath);
|
const scopedEntries = readAllowFromStateForPathSync(channel, scopedPath);
|
||||||
|
if (!shouldIncludeLegacyAllowFromEntries(normalizedAccountId)) {
|
||||||
|
return scopedEntries;
|
||||||
|
}
|
||||||
const legacyPath = resolveAllowFromPath(channel, env);
|
const legacyPath = resolveAllowFromPath(channel, env);
|
||||||
const legacyEntries = readAllowFromStateForPathSync(channel, legacyPath);
|
const legacyEntries = readAllowFromStateForPathSync(channel, legacyPath);
|
||||||
return dedupePreserveOrder([...scopedEntries, ...legacyEntries]);
|
return dedupePreserveOrder([...scopedEntries, ...legacyEntries]);
|
||||||
@@ -503,7 +515,12 @@ export async function upsertChannelPairingRequest(params: {
|
|||||||
nowMs,
|
nowMs,
|
||||||
);
|
);
|
||||||
reqs = prunedExpired;
|
reqs = prunedExpired;
|
||||||
const existingIdx = reqs.findIndex((r) => r.id === id);
|
const existingIdx = reqs.findIndex((r) => {
|
||||||
|
if (r.id !== id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return requestMatchesAccountId(r, normalizePairingAccountId(normalizedAccountId));
|
||||||
|
});
|
||||||
const existingCodes = new Set(
|
const existingCodes = new Set(
|
||||||
reqs.map((req) =>
|
reqs.map((req) =>
|
||||||
String(req.code ?? "")
|
String(req.code ?? "")
|
||||||
|
|||||||
Reference in New Issue
Block a user