mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:28:38 +00:00
fix(pairing): support legacy telegram allowFrom migration
This commit is contained in:
@@ -178,6 +178,42 @@ describe("doctor legacy state migrations", () => {
|
|||||||
expect(fs.existsSync(path.join(oauthDir, "creds.json"))).toBe(false);
|
expect(fs.existsSync(path.join(oauthDir, "creds.json"))).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("migrates legacy Telegram pairing allowFrom store to account-scoped default file", async () => {
|
||||||
|
const root = await makeTempRoot();
|
||||||
|
const cfg: OpenClawConfig = {};
|
||||||
|
|
||||||
|
const oauthDir = path.join(root, "credentials");
|
||||||
|
fs.mkdirSync(oauthDir, { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(oauthDir, "telegram-allowFrom.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
allowFrom: ["123456"],
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
) + "\n",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const detected = await detectLegacyStateMigrations({
|
||||||
|
cfg,
|
||||||
|
env: { OPENCLAW_STATE_DIR: root } as NodeJS.ProcessEnv,
|
||||||
|
});
|
||||||
|
expect(detected.pairingAllowFrom.hasLegacyTelegram).toBe(true);
|
||||||
|
|
||||||
|
const result = await runLegacyStateMigrations({ detected, now: () => 123 });
|
||||||
|
expect(result.warnings).toEqual([]);
|
||||||
|
|
||||||
|
const target = path.join(oauthDir, "telegram-default-allowFrom.json");
|
||||||
|
expect(fs.existsSync(target)).toBe(true);
|
||||||
|
expect(JSON.parse(fs.readFileSync(target, "utf-8"))).toEqual({
|
||||||
|
version: 1,
|
||||||
|
allowFrom: ["123456"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("no-ops when nothing detected", async () => {
|
it("no-ops when nothing detected", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const cfg: OpenClawConfig = {};
|
const cfg: OpenClawConfig = {};
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ export const detectLegacyStateMigrations = vi.fn().mockResolvedValue({
|
|||||||
targetDir: "/tmp/oauth/whatsapp/default",
|
targetDir: "/tmp/oauth/whatsapp/default",
|
||||||
hasLegacy: false,
|
hasLegacy: false,
|
||||||
},
|
},
|
||||||
|
pairingAllowFrom: {
|
||||||
|
legacyTelegramPath: "/tmp/oauth/telegram-allowFrom.json",
|
||||||
|
targetTelegramPath: "/tmp/oauth/telegram-default-allowFrom.json",
|
||||||
|
hasLegacyTelegram: false,
|
||||||
|
},
|
||||||
preview: [],
|
preview: [],
|
||||||
}) as unknown as MockFn;
|
}) as unknown as MockFn;
|
||||||
|
|
||||||
@@ -306,6 +311,11 @@ export async function arrangeLegacyStateMigrationTest(): Promise<{
|
|||||||
targetDir: "/tmp/oauth/whatsapp/default",
|
targetDir: "/tmp/oauth/whatsapp/default",
|
||||||
hasLegacy: false,
|
hasLegacy: false,
|
||||||
},
|
},
|
||||||
|
pairingAllowFrom: {
|
||||||
|
legacyTelegramPath: "/tmp/oauth/telegram-allowFrom.json",
|
||||||
|
targetTelegramPath: "/tmp/oauth/telegram-default-allowFrom.json",
|
||||||
|
hasLegacyTelegram: false,
|
||||||
|
},
|
||||||
preview: ["- Legacy sessions detected"],
|
preview: ["- Legacy sessions detected"],
|
||||||
});
|
});
|
||||||
runLegacyStateMigrations.mockResolvedValueOnce({
|
runLegacyStateMigrations.mockResolvedValueOnce({
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ export type LegacyStateDetection = {
|
|||||||
targetDir: string;
|
targetDir: string;
|
||||||
hasLegacy: boolean;
|
hasLegacy: boolean;
|
||||||
};
|
};
|
||||||
|
pairingAllowFrom: {
|
||||||
|
legacyTelegramPath: string;
|
||||||
|
targetTelegramPath: string;
|
||||||
|
hasLegacyTelegram: boolean;
|
||||||
|
};
|
||||||
preview: string[];
|
preview: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -612,6 +617,13 @@ export async function detectLegacyStateMigrations(params: {
|
|||||||
const hasLegacyWhatsAppAuth =
|
const hasLegacyWhatsAppAuth =
|
||||||
fileExists(path.join(oauthDir, "creds.json")) &&
|
fileExists(path.join(oauthDir, "creds.json")) &&
|
||||||
!fileExists(path.join(targetWhatsAppAuthDir, "creds.json"));
|
!fileExists(path.join(targetWhatsAppAuthDir, "creds.json"));
|
||||||
|
const legacyTelegramAllowFromPath = path.join(oauthDir, "telegram-allowFrom.json");
|
||||||
|
const targetTelegramAllowFromPath = path.join(
|
||||||
|
oauthDir,
|
||||||
|
`telegram-${DEFAULT_ACCOUNT_ID}-allowFrom.json`,
|
||||||
|
);
|
||||||
|
const hasLegacyTelegramAllowFrom =
|
||||||
|
fileExists(legacyTelegramAllowFromPath) && !fileExists(targetTelegramAllowFromPath);
|
||||||
|
|
||||||
const preview: string[] = [];
|
const preview: string[] = [];
|
||||||
if (hasLegacySessions) {
|
if (hasLegacySessions) {
|
||||||
@@ -626,6 +638,11 @@ export async function detectLegacyStateMigrations(params: {
|
|||||||
if (hasLegacyWhatsAppAuth) {
|
if (hasLegacyWhatsAppAuth) {
|
||||||
preview.push(`- WhatsApp auth: ${oauthDir} → ${targetWhatsAppAuthDir} (keep oauth.json)`);
|
preview.push(`- WhatsApp auth: ${oauthDir} → ${targetWhatsAppAuthDir} (keep oauth.json)`);
|
||||||
}
|
}
|
||||||
|
if (hasLegacyTelegramAllowFrom) {
|
||||||
|
preview.push(
|
||||||
|
`- Telegram pairing allowFrom: ${legacyTelegramAllowFromPath} → ${targetTelegramAllowFromPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
targetAgentId,
|
targetAgentId,
|
||||||
@@ -651,6 +668,11 @@ export async function detectLegacyStateMigrations(params: {
|
|||||||
targetDir: targetWhatsAppAuthDir,
|
targetDir: targetWhatsAppAuthDir,
|
||||||
hasLegacy: hasLegacyWhatsAppAuth,
|
hasLegacy: hasLegacyWhatsAppAuth,
|
||||||
},
|
},
|
||||||
|
pairingAllowFrom: {
|
||||||
|
legacyTelegramPath: legacyTelegramAllowFromPath,
|
||||||
|
targetTelegramPath: targetTelegramAllowFromPath,
|
||||||
|
hasLegacyTelegram: hasLegacyTelegramAllowFrom,
|
||||||
|
},
|
||||||
preview,
|
preview,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -867,6 +889,28 @@ async function migrateLegacyWhatsAppAuth(
|
|||||||
return { changes, warnings };
|
return { changes, warnings };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function migrateLegacyTelegramPairingAllowFrom(
|
||||||
|
detected: LegacyStateDetection,
|
||||||
|
): Promise<{ changes: string[]; warnings: string[] }> {
|
||||||
|
const changes: string[] = [];
|
||||||
|
const warnings: string[] = [];
|
||||||
|
if (!detected.pairingAllowFrom.hasLegacyTelegram) {
|
||||||
|
return { changes, warnings };
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyPath = detected.pairingAllowFrom.legacyTelegramPath;
|
||||||
|
const targetPath = detected.pairingAllowFrom.targetTelegramPath;
|
||||||
|
try {
|
||||||
|
ensureDir(path.dirname(targetPath));
|
||||||
|
fs.copyFileSync(legacyPath, targetPath);
|
||||||
|
changes.push(`Copied Telegram pairing allowFrom → ${targetPath}`);
|
||||||
|
} catch (err) {
|
||||||
|
warnings.push(`Failed migrating Telegram pairing allowFrom (${legacyPath}): ${String(err)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { changes, warnings };
|
||||||
|
}
|
||||||
|
|
||||||
export async function runLegacyStateMigrations(params: {
|
export async function runLegacyStateMigrations(params: {
|
||||||
detected: LegacyStateDetection;
|
detected: LegacyStateDetection;
|
||||||
now?: () => number;
|
now?: () => number;
|
||||||
@@ -876,9 +920,20 @@ export async function runLegacyStateMigrations(params: {
|
|||||||
const sessions = await migrateLegacySessions(detected, now);
|
const sessions = await migrateLegacySessions(detected, now);
|
||||||
const agentDir = await migrateLegacyAgentDir(detected, now);
|
const agentDir = await migrateLegacyAgentDir(detected, now);
|
||||||
const whatsappAuth = await migrateLegacyWhatsAppAuth(detected);
|
const whatsappAuth = await migrateLegacyWhatsAppAuth(detected);
|
||||||
|
const telegramPairingAllowFrom = await migrateLegacyTelegramPairingAllowFrom(detected);
|
||||||
return {
|
return {
|
||||||
changes: [...sessions.changes, ...agentDir.changes, ...whatsappAuth.changes],
|
changes: [
|
||||||
warnings: [...sessions.warnings, ...agentDir.warnings, ...whatsappAuth.warnings],
|
...sessions.changes,
|
||||||
|
...agentDir.changes,
|
||||||
|
...whatsappAuth.changes,
|
||||||
|
...telegramPairingAllowFrom.changes,
|
||||||
|
],
|
||||||
|
warnings: [
|
||||||
|
...sessions.warnings,
|
||||||
|
...agentDir.warnings,
|
||||||
|
...whatsappAuth.warnings,
|
||||||
|
...telegramPairingAllowFrom.warnings,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,4 +184,38 @@ describe("pairing store", () => {
|
|||||||
expect(channelScoped).not.toContain("12345");
|
expect(channelScoped).not.toContain("12345");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reads legacy channel-scoped allowFrom for default account", async () => {
|
||||||
|
await withTempStateDir(async (stateDir) => {
|
||||||
|
const oauthDir = resolveOAuthDir(process.env, stateDir);
|
||||||
|
await fs.mkdir(oauthDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(oauthDir, "telegram-allowFrom.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
allowFrom: ["1001"],
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
) + "\n",
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(oauthDir, "telegram-default-allowFrom.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
allowFrom: ["1002"],
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
) + "\n",
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const scoped = await readChannelAllowFromStore("telegram", process.env, "default");
|
||||||
|
expect(scoped).toEqual(["1002", "1001"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -234,6 +234,31 @@ function normalizeAllowFromInput(channel: PairingChannel, entry: string | number
|
|||||||
return normalizeAllowEntry(channel, normalizeId(entry));
|
return normalizeAllowEntry(channel, normalizeId(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dedupePreserveOrder(entries: string[]): string[] {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const entry of entries) {
|
||||||
|
const normalized = String(entry).trim();
|
||||||
|
if (!normalized || seen.has(normalized)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(normalized);
|
||||||
|
out.push(normalized);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readAllowFromStateForPath(
|
||||||
|
channel: PairingChannel,
|
||||||
|
filePath: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const { value } = await readJsonFile<AllowFromStore>(filePath, {
|
||||||
|
version: 1,
|
||||||
|
allowFrom: [],
|
||||||
|
});
|
||||||
|
return normalizeAllowFromList(channel, value);
|
||||||
|
}
|
||||||
|
|
||||||
async function readAllowFromState(params: {
|
async function readAllowFromState(params: {
|
||||||
channel: PairingChannel;
|
channel: PairingChannel;
|
||||||
entry: string | number;
|
entry: string | number;
|
||||||
@@ -291,12 +316,19 @@ export async function readChannelAllowFromStore(
|
|||||||
env: NodeJS.ProcessEnv = process.env,
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
accountId?: string,
|
accountId?: string,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const filePath = resolveAllowFromPath(channel, env, accountId);
|
const normalizedAccountId = accountId?.trim().toLowerCase() ?? "";
|
||||||
const { value } = await readJsonFile<AllowFromStore>(filePath, {
|
if (!normalizedAccountId) {
|
||||||
version: 1,
|
const filePath = resolveAllowFromPath(channel, env);
|
||||||
allowFrom: [],
|
return await readAllowFromStateForPath(channel, filePath);
|
||||||
});
|
}
|
||||||
return normalizeAllowFromList(channel, value);
|
|
||||||
|
const scopedPath = resolveAllowFromPath(channel, env, accountId);
|
||||||
|
const scopedEntries = await readAllowFromStateForPath(channel, scopedPath);
|
||||||
|
// Backward compatibility: legacy channel-level allowFrom store was unscoped.
|
||||||
|
// Keep honoring it alongside account-scoped files to prevent re-pair prompts after upgrades.
|
||||||
|
const legacyPath = resolveAllowFromPath(channel, env);
|
||||||
|
const legacyEntries = await readAllowFromStateForPath(channel, legacyPath);
|
||||||
|
return dedupePreserveOrder([...scopedEntries, ...legacyEntries]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addChannelAllowFromStoreEntry(params: {
|
export async function addChannelAllowFromStoreEntry(params: {
|
||||||
|
|||||||
Reference in New Issue
Block a user