mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-20 21:23:23 +00:00
fix: avoid teams sso token key collisions
This commit is contained in:
72
extensions/msteams/src/sso-token-store.test.ts
Normal file
72
extensions/msteams/src/sso-token-store.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createMSTeamsSsoTokenStoreFs } from "./sso-token-store.js";
|
||||
|
||||
describe("msteams sso token store (fs)", () => {
|
||||
it("keeps distinct tokens when connectionName and userId contain the legacy delimiter", async () => {
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-sso-"));
|
||||
const storePath = path.join(stateDir, "msteams-sso-tokens.json");
|
||||
const store = createMSTeamsSsoTokenStoreFs({ storePath });
|
||||
|
||||
const first = {
|
||||
connectionName: "conn::alpha",
|
||||
userId: "user",
|
||||
token: "token-a",
|
||||
updatedAt: "2026-04-10T00:00:00.000Z",
|
||||
} as const;
|
||||
const second = {
|
||||
connectionName: "conn",
|
||||
userId: "alpha::user",
|
||||
token: "token-b",
|
||||
updatedAt: "2026-04-10T00:00:01.000Z",
|
||||
} as const;
|
||||
|
||||
await store.save(first);
|
||||
await store.save(second);
|
||||
|
||||
expect(await store.get(first)).toEqual(first);
|
||||
expect(await store.get(second)).toEqual(second);
|
||||
|
||||
const raw = JSON.parse(await fs.readFile(storePath, "utf8")) as {
|
||||
tokens: Record<string, unknown>;
|
||||
};
|
||||
expect(Object.keys(raw.tokens)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("loads legacy flat-key files by rebuilding keys from stored token payloads", async () => {
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-sso-legacy-"));
|
||||
const storePath = path.join(stateDir, "msteams-sso-tokens.json");
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
tokens: {
|
||||
"legacy::wrong-key": {
|
||||
connectionName: "conn",
|
||||
userId: "user-1",
|
||||
token: "token-1",
|
||||
updatedAt: "2026-04-10T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const store = createMSTeamsSsoTokenStoreFs({ storePath });
|
||||
expect(
|
||||
await store.get({
|
||||
connectionName: "conn",
|
||||
userId: "user-1",
|
||||
}),
|
||||
).toMatchObject({
|
||||
token: "token-1",
|
||||
updatedAt: "2026-04-10T00:00:00.000Z",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -40,9 +40,39 @@ type SsoStoreData = {
|
||||
};
|
||||
|
||||
const STORE_FILENAME = "msteams-sso-tokens.json";
|
||||
const STORE_KEY_VERSION_PREFIX = "v2:";
|
||||
|
||||
function makeKey(connectionName: string, userId: string): string {
|
||||
return `${connectionName}::${userId}`;
|
||||
return `${STORE_KEY_VERSION_PREFIX}${Buffer.from(
|
||||
JSON.stringify([connectionName, userId]),
|
||||
"utf8",
|
||||
).toString("base64url")}`;
|
||||
}
|
||||
|
||||
function normalizeStoredToken(value: unknown): MSTeamsSsoStoredToken | null {
|
||||
if (!value || typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
const token = value as Partial<MSTeamsSsoStoredToken>;
|
||||
if (
|
||||
typeof token.connectionName !== "string" ||
|
||||
!token.connectionName ||
|
||||
typeof token.userId !== "string" ||
|
||||
!token.userId ||
|
||||
typeof token.token !== "string" ||
|
||||
!token.token ||
|
||||
typeof token.updatedAt !== "string" ||
|
||||
!token.updatedAt
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
connectionName: token.connectionName,
|
||||
userId: token.userId,
|
||||
token: token.token,
|
||||
...(typeof token.expiresAt === "string" ? { expiresAt: token.expiresAt } : {}),
|
||||
updatedAt: token.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function isSsoStoreData(value: unknown): value is SsoStoreData {
|
||||
@@ -74,7 +104,18 @@ export function createMSTeamsSsoTokenStoreFs(params?: {
|
||||
if (!isSsoStoreData(value)) {
|
||||
return { version: 1, tokens: {} };
|
||||
}
|
||||
return value;
|
||||
const tokens: Record<string, MSTeamsSsoStoredToken> = {};
|
||||
for (const stored of Object.values(value.tokens)) {
|
||||
const normalized = normalizeStoredToken(stored);
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
tokens[makeKey(normalized.connectionName, normalized.userId)] = normalized;
|
||||
}
|
||||
return {
|
||||
version: 1,
|
||||
tokens,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user