mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 19:17:26 +00:00
Matrix: harden device-scoped storage reuse
This commit is contained in:
@@ -283,6 +283,45 @@ describe("matrix client storage paths", () => {
|
||||
expect(rotatedStoragePaths.storagePath).toBe(oldStoragePaths.storagePath);
|
||||
});
|
||||
|
||||
it("reuses an existing token-hash storage root for the same device after the access token changes", () => {
|
||||
const stateDir = setupStateDir();
|
||||
const oldStoragePaths = resolveMatrixStoragePaths({
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "secret-token-old",
|
||||
deviceId: "DEVICE123",
|
||||
env: {},
|
||||
});
|
||||
fs.mkdirSync(oldStoragePaths.rootDir, { recursive: true });
|
||||
fs.writeFileSync(oldStoragePaths.storagePath, '{"legacy":true}');
|
||||
fs.writeFileSync(
|
||||
path.join(oldStoragePaths.rootDir, "storage-meta.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accountId: "default",
|
||||
accessTokenHash: oldStoragePaths.tokenHash,
|
||||
deviceId: "DEVICE123",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
const rotatedStoragePaths = resolveMatrixStoragePaths({
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "secret-token-new",
|
||||
deviceId: "DEVICE123",
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(rotatedStoragePaths.rootDir).toBe(oldStoragePaths.rootDir);
|
||||
expect(rotatedStoragePaths.tokenHash).toBe(oldStoragePaths.tokenHash);
|
||||
expect(rotatedStoragePaths.storagePath).toBe(oldStoragePaths.storagePath);
|
||||
});
|
||||
|
||||
it("prefers a populated older token-hash storage root over a newer empty root", () => {
|
||||
const stateDir = setupStateDir();
|
||||
const oldStoragePaths = resolveMatrixStoragePaths({
|
||||
@@ -366,4 +405,49 @@ describe("matrix client storage paths", () => {
|
||||
expect(resolvedPaths.rootDir).toBe(newerCanonicalPaths.rootDir);
|
||||
expect(resolvedPaths.tokenHash).toBe(newerCanonicalPaths.tokenHash);
|
||||
});
|
||||
|
||||
it("does not reuse a populated sibling storage root with ambiguous device metadata", () => {
|
||||
const stateDir = setupStateDir();
|
||||
const oldStoragePaths = resolveMatrixStoragePaths({
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "secret-token-old",
|
||||
env: {},
|
||||
});
|
||||
fs.mkdirSync(oldStoragePaths.rootDir, { recursive: true });
|
||||
fs.writeFileSync(oldStoragePaths.storagePath, '{"legacy":true}');
|
||||
|
||||
const newerCanonicalPaths = resolveMatrixAccountStorageRoot({
|
||||
stateDir,
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "secret-token-new",
|
||||
});
|
||||
fs.mkdirSync(newerCanonicalPaths.rootDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(newerCanonicalPaths.rootDir, "storage-meta.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accountId: "default",
|
||||
accessTokenHash: newerCanonicalPaths.tokenHash,
|
||||
deviceId: "NEWDEVICE",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
const resolvedPaths = resolveMatrixStoragePaths({
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "secret-token-new",
|
||||
deviceId: "NEWDEVICE",
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(resolvedPaths.rootDir).toBe(newerCanonicalPaths.rootDir);
|
||||
expect(resolvedPaths.tokenHash).toBe(newerCanonicalPaths.tokenHash);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,6 +143,7 @@ function isCompatibleStorageRoot(params: {
|
||||
userId: string;
|
||||
accountKey: string;
|
||||
deviceId?: string | null;
|
||||
requireExplicitDeviceMatch?: boolean;
|
||||
}): boolean {
|
||||
const metadata = readStoredRootMetadata(params.candidateRootDir);
|
||||
if (metadata.homeserver && metadata.homeserver !== params.homeserver) {
|
||||
@@ -165,6 +166,13 @@ function isCompatibleStorageRoot(params: {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
params.requireExplicitDeviceMatch &&
|
||||
params.deviceId &&
|
||||
(!metadata.deviceId || metadata.deviceId.trim() !== params.deviceId.trim())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -213,6 +221,9 @@ function resolvePreferredMatrixStorageRoot(params: {
|
||||
userId: params.userId,
|
||||
accountKey: params.accountKey,
|
||||
deviceId: params.deviceId,
|
||||
// Once auth resolves a concrete device, only sibling roots that explicitly
|
||||
// declare that same device are safe to reuse across token rotations.
|
||||
requireExplicitDeviceMatch: Boolean(params.deviceId),
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user