mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 13:05:04 +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);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const root = await makeTempRoot();
|
||||
const cfg: OpenClawConfig = {};
|
||||
|
||||
@@ -116,6 +116,11 @@ export const detectLegacyStateMigrations = vi.fn().mockResolvedValue({
|
||||
targetDir: "/tmp/oauth/whatsapp/default",
|
||||
hasLegacy: false,
|
||||
},
|
||||
pairingAllowFrom: {
|
||||
legacyTelegramPath: "/tmp/oauth/telegram-allowFrom.json",
|
||||
targetTelegramPath: "/tmp/oauth/telegram-default-allowFrom.json",
|
||||
hasLegacyTelegram: false,
|
||||
},
|
||||
preview: [],
|
||||
}) as unknown as MockFn;
|
||||
|
||||
@@ -306,6 +311,11 @@ export async function arrangeLegacyStateMigrationTest(): Promise<{
|
||||
targetDir: "/tmp/oauth/whatsapp/default",
|
||||
hasLegacy: false,
|
||||
},
|
||||
pairingAllowFrom: {
|
||||
legacyTelegramPath: "/tmp/oauth/telegram-allowFrom.json",
|
||||
targetTelegramPath: "/tmp/oauth/telegram-default-allowFrom.json",
|
||||
hasLegacyTelegram: false,
|
||||
},
|
||||
preview: ["- Legacy sessions detected"],
|
||||
});
|
||||
runLegacyStateMigrations.mockResolvedValueOnce({
|
||||
|
||||
@@ -55,6 +55,11 @@ export type LegacyStateDetection = {
|
||||
targetDir: string;
|
||||
hasLegacy: boolean;
|
||||
};
|
||||
pairingAllowFrom: {
|
||||
legacyTelegramPath: string;
|
||||
targetTelegramPath: string;
|
||||
hasLegacyTelegram: boolean;
|
||||
};
|
||||
preview: string[];
|
||||
};
|
||||
|
||||
@@ -612,6 +617,13 @@ export async function detectLegacyStateMigrations(params: {
|
||||
const hasLegacyWhatsAppAuth =
|
||||
fileExists(path.join(oauthDir, "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[] = [];
|
||||
if (hasLegacySessions) {
|
||||
@@ -626,6 +638,11 @@ export async function detectLegacyStateMigrations(params: {
|
||||
if (hasLegacyWhatsAppAuth) {
|
||||
preview.push(`- WhatsApp auth: ${oauthDir} → ${targetWhatsAppAuthDir} (keep oauth.json)`);
|
||||
}
|
||||
if (hasLegacyTelegramAllowFrom) {
|
||||
preview.push(
|
||||
`- Telegram pairing allowFrom: ${legacyTelegramAllowFromPath} → ${targetTelegramAllowFromPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
targetAgentId,
|
||||
@@ -651,6 +668,11 @@ export async function detectLegacyStateMigrations(params: {
|
||||
targetDir: targetWhatsAppAuthDir,
|
||||
hasLegacy: hasLegacyWhatsAppAuth,
|
||||
},
|
||||
pairingAllowFrom: {
|
||||
legacyTelegramPath: legacyTelegramAllowFromPath,
|
||||
targetTelegramPath: targetTelegramAllowFromPath,
|
||||
hasLegacyTelegram: hasLegacyTelegramAllowFrom,
|
||||
},
|
||||
preview,
|
||||
};
|
||||
}
|
||||
@@ -867,6 +889,28 @@ async function migrateLegacyWhatsAppAuth(
|
||||
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: {
|
||||
detected: LegacyStateDetection;
|
||||
now?: () => number;
|
||||
@@ -876,9 +920,20 @@ export async function runLegacyStateMigrations(params: {
|
||||
const sessions = await migrateLegacySessions(detected, now);
|
||||
const agentDir = await migrateLegacyAgentDir(detected, now);
|
||||
const whatsappAuth = await migrateLegacyWhatsAppAuth(detected);
|
||||
const telegramPairingAllowFrom = await migrateLegacyTelegramPairingAllowFrom(detected);
|
||||
return {
|
||||
changes: [...sessions.changes, ...agentDir.changes, ...whatsappAuth.changes],
|
||||
warnings: [...sessions.warnings, ...agentDir.warnings, ...whatsappAuth.warnings],
|
||||
changes: [
|
||||
...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");
|
||||
});
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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: {
|
||||
channel: PairingChannel;
|
||||
entry: string | number;
|
||||
@@ -291,12 +316,19 @@ export async function readChannelAllowFromStore(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
accountId?: string,
|
||||
): Promise<string[]> {
|
||||
const filePath = resolveAllowFromPath(channel, env, accountId);
|
||||
const { value } = await readJsonFile<AllowFromStore>(filePath, {
|
||||
version: 1,
|
||||
allowFrom: [],
|
||||
});
|
||||
return normalizeAllowFromList(channel, value);
|
||||
const normalizedAccountId = accountId?.trim().toLowerCase() ?? "";
|
||||
if (!normalizedAccountId) {
|
||||
const filePath = resolveAllowFromPath(channel, env);
|
||||
return await readAllowFromStateForPath(channel, filePath);
|
||||
}
|
||||
|
||||
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: {
|
||||
|
||||
Reference in New Issue
Block a user