The parts of the agent module that hold whichever way the console is registered: X25519 + HKDF + AES-GCM from Node's own library so the bundle carries no dependency; the predecessor's lineage rule (rotation only if newer, a re-issue adopted, a switch regardless) with its incidents as tests; an atomic 0600 write that strips any refresh token and keeps keys it does not know; the account read from the agent's own state file. Manifest and renderer follow.
55 lines
2.7 KiB
TypeScript
55 lines
2.7 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { mkdtempSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import {
|
|
decideApply, grantOf, holdsLogin, readCredentials, withGrant, writeCredentials, type Grant,
|
|
} from "../dist/grant.js";
|
|
|
|
const NOW = 1_700_000_000_000;
|
|
const HOUR = 3_600_000;
|
|
const g = (over: Partial<Grant> = {}): Grant => ({
|
|
accessToken: "tok-A", expiresAt: NOW + HOUR, refreshTokenExpiresAt: NOW + 30 * 24 * HOUR, ...over,
|
|
});
|
|
|
|
test("a rotation applies a newer grant of the same licence", () => {
|
|
assert.deepEqual(decideApply(g(), g({ accessToken: "tok-B", expiresAt: NOW + 2 * HOUR }), "rotation"), { apply: true });
|
|
});
|
|
|
|
test("a rotation refuses a grant that arrived late and is older", () => {
|
|
const d = decideApply(g({ accessToken: "new", expiresAt: NOW + 2 * HOUR }), g({ accessToken: "old" }), "rotation");
|
|
assert.equal(d.apply === false && d.reason, "not-newer");
|
|
});
|
|
|
|
test("a grant re-issued by a login is adopted even though it expires sooner (2026-09-05)", () => {
|
|
const local = g({ expiresAt: NOW + 8 * HOUR, refreshTokenExpiresAt: NOW + 30 * 24 * HOUR });
|
|
const offered = g({ accessToken: "reissued", expiresAt: NOW + HOUR, refreshTokenExpiresAt: NOW + 5 * 24 * HOUR });
|
|
assert.deepEqual(decideApply(local, offered, "rotation"), { apply: true, reissued: true });
|
|
});
|
|
|
|
test("a switch to another licence applies whatever the expiries say", () => {
|
|
const local = g({ expiresAt: NOW + 8 * HOUR });
|
|
assert.equal(decideApply(local, g({ accessToken: "other", expiresAt: NOW + HOUR }), "switch").apply, true);
|
|
});
|
|
|
|
test("the same token is not rewritten", () => {
|
|
assert.deepEqual(decideApply(g(), g(), "switch"), { apply: false, reason: "already-current" });
|
|
});
|
|
|
|
test("a full grant left by a login is seen as a login, and stripped when the node's own is written", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "claude-code-"));
|
|
const path = join(dir, ".claude", ".credentials.json");
|
|
writeFileSync(join(dir, "x"), "");
|
|
const login = { claudeAiOauth: { accessToken: "at-login", refreshToken: "rt-login", expiresAt: NOW }, other: 1 };
|
|
assert.equal(holdsLogin(login), true);
|
|
writeCredentials(path, withGrant(login, g({ accessToken: "at-mesh", scopes: ["user:inference"] })));
|
|
const back = readCredentials(path)!;
|
|
assert.equal(holdsLogin(back), false);
|
|
assert.equal(grantOf(back)!.accessToken, "at-mesh");
|
|
assert.deepEqual(back.claudeAiOauth!.scopes, ["user:inference"]);
|
|
assert.equal(back.other, 1, "a key the module does not know was lost");
|
|
assert.ok(!readFileSync(path, "utf8").includes("rt-login"));
|
|
assert.equal(statSync(path).mode & 0o777, 0o600);
|
|
});
|