Add GoogleTokenStore for workspace-local refresh token

This commit is contained in:
EugeneTes
2026-09-23 09:55:09 +00:00
parent e9c39c5f87
commit e776c32ca3
2 changed files with 87 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
import { mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createGoogleTokenStore } from "./google-token-store.js";
describe("createGoogleTokenStore", () => {
let dir: string;
beforeEach(() => {
dir = mkdtempSync(path.join(tmpdir(), "vs-token-"));
});
afterEach(() => {
rmSync(dir, { recursive: true, force: true });
});
it("read returns null when the file does not exist", () => {
const store = createGoogleTokenStore(dir);
expect(store.read()).toBeNull();
});
it("read returns null when the file is empty, malformed, or missing the key", () => {
const store = createGoogleTokenStore(dir);
const p = path.join(dir, ".secretary", "google-token.json");
mkdirSync(path.dirname(p), { recursive: true });
// Empty
writeFileSync(p, "", { mode: 0o600 });
expect(store.read()).toBeNull();
// Malformed
writeFileSync(p, "{not-json", { mode: 0o600 });
expect(store.read()).toBeNull();
// Right shape but no key
writeFileSync(p, JSON.stringify({ other: "x" }), { mode: 0o600 });
expect(store.read()).toBeNull();
});
it("write creates the .secretary directory if it does not exist and round-trips through read", () => {
const store = createGoogleTokenStore(dir);
store.write("refresh-token-abc");
expect(store.read()).toBe("refresh-token-abc");
});
it("write replaces a previous token", () => {
const store = createGoogleTokenStore(dir);
store.write("first");
store.write("second");
expect(store.read()).toBe("second");
});
it("write applies mode 0600 on POSIX", () => {
if (process.platform === "win32") return;
const store = createGoogleTokenStore(dir);
store.write("t");
const mode = statSync(path.join(dir, ".secretary", "google-token.json")).mode & 0o777;
expect(mode).toBe(0o600);
});
});

View File

@@ -0,0 +1,31 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
export interface GoogleTokenStore {
read(): string | null;
write(refreshToken: string): void;
}
export function createGoogleTokenStore(workspaceDir: string): GoogleTokenStore {
const dir = path.join(workspaceDir, ".secretary");
const file = path.join(dir, "google-token.json");
return {
read() {
if (!existsSync(file)) return null;
try {
const raw = readFileSync(file, "utf8");
if (!raw.trim()) return null;
const parsed = JSON.parse(raw) as { refresh_token?: unknown };
return typeof parsed.refresh_token === "string" && parsed.refresh_token
? parsed.refresh_token
: null;
} catch {
return null;
}
},
write(refreshToken: string) {
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
writeFileSync(file, JSON.stringify({ refresh_token: refreshToken }), { mode: 0o600 });
},
};
}