110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { withTempDir } from "../test-utils/temp-dir.js";
|
|
import {
|
|
clearDeviceAuthToken,
|
|
loadDeviceAuthToken,
|
|
storeDeviceAuthToken,
|
|
} from "./device-auth-store.js";
|
|
|
|
function createEnv(stateDir: string): NodeJS.ProcessEnv {
|
|
return {
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_TEST_FAST: "1",
|
|
};
|
|
}
|
|
|
|
function deviceAuthFile(stateDir: string): string {
|
|
return path.join(stateDir, "identity", "device-auth.json");
|
|
}
|
|
|
|
describe("infra/device-auth-store", () => {
|
|
it("stores and loads device auth tokens under the configured state dir", async () => {
|
|
await withTempDir("openclaw-device-auth-", async (stateDir) => {
|
|
vi.spyOn(Date, "now").mockReturnValue(1234);
|
|
|
|
const entry = storeDeviceAuthToken({
|
|
deviceId: "device-1",
|
|
role: " operator ",
|
|
token: "secret",
|
|
scopes: [" operator.write ", "operator.read", "operator.read"],
|
|
env: createEnv(stateDir),
|
|
});
|
|
|
|
expect(entry).toEqual({
|
|
token: "secret",
|
|
role: "operator",
|
|
scopes: ["operator.read", "operator.write"],
|
|
updatedAtMs: 1234,
|
|
});
|
|
expect(
|
|
loadDeviceAuthToken({
|
|
deviceId: "device-1",
|
|
role: "operator",
|
|
env: createEnv(stateDir),
|
|
}),
|
|
).toEqual(entry);
|
|
|
|
const raw = await fs.readFile(deviceAuthFile(stateDir), "utf8");
|
|
expect(raw.endsWith("\n")).toBe(true);
|
|
expect(JSON.parse(raw)).toEqual({
|
|
version: 1,
|
|
deviceId: "device-1",
|
|
tokens: {
|
|
operator: entry,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
it("returns null for missing, invalid, or mismatched stores", async () => {
|
|
await withTempDir("openclaw-device-auth-", async (stateDir) => {
|
|
const env = createEnv(stateDir);
|
|
|
|
expect(loadDeviceAuthToken({ deviceId: "device-1", role: "operator", env })).toBeNull();
|
|
|
|
await fs.mkdir(path.dirname(deviceAuthFile(stateDir)), { recursive: true });
|
|
await fs.writeFile(deviceAuthFile(stateDir), '{"version":2,"deviceId":"device-1"}\n', "utf8");
|
|
expect(loadDeviceAuthToken({ deviceId: "device-1", role: "operator", env })).toBeNull();
|
|
|
|
await fs.writeFile(
|
|
deviceAuthFile(stateDir),
|
|
'{"version":1,"deviceId":"device-2","tokens":{"operator":{"token":"x","role":"operator","scopes":[],"updatedAtMs":1}}}\n',
|
|
"utf8",
|
|
);
|
|
expect(loadDeviceAuthToken({ deviceId: "device-1", role: "operator", env })).toBeNull();
|
|
});
|
|
});
|
|
|
|
it("clears only the requested role and leaves unrelated tokens intact", async () => {
|
|
await withTempDir("openclaw-device-auth-", async (stateDir) => {
|
|
const env = createEnv(stateDir);
|
|
|
|
storeDeviceAuthToken({
|
|
deviceId: "device-1",
|
|
role: "operator",
|
|
token: "operator-token",
|
|
env,
|
|
});
|
|
storeDeviceAuthToken({
|
|
deviceId: "device-1",
|
|
role: "node",
|
|
token: "node-token",
|
|
env,
|
|
});
|
|
|
|
clearDeviceAuthToken({
|
|
deviceId: "device-1",
|
|
role: " operator ",
|
|
env,
|
|
});
|
|
|
|
expect(loadDeviceAuthToken({ deviceId: "device-1", role: "operator", env })).toBeNull();
|
|
expect(loadDeviceAuthToken({ deviceId: "device-1", role: "node", env })).toMatchObject({
|
|
token: "node-token",
|
|
});
|
|
});
|
|
});
|
|
});
|