import { test } from "node:test"; import assert from "node:assert/strict"; import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { BusinessError, checkCredentialRef, parseCredentialRef } from "../src/index.mjs"; import { FAR_DATE, tmp, tokenFile } from "./helpers.mjs"; function refuses(fn, pattern) { assert.throws(fn, (error) => { assert.ok(error instanceof BusinessError, `expected BusinessError, got ${error}`); assert.equal(error.exitCode, 2); assert.match(error.message, pattern); return true; }); } const NOW = new Date("2026-10-04T12:00:00Z"); test("parse: exactly one of file or env, plus the service's date", () => { assert.deepEqual({ ...parseCredentialRef({ file: "/s/pm-gitea.token", rotateBy: FAR_DATE }, "gitea", "r") }, { service: "gitea", file: "/s/pm-gitea.token", rotateBy: FAR_DATE }); assert.deepEqual({ ...parseCredentialRef({ env: "PM_VIKUNJA_TOKEN", expires: FAR_DATE }, "vikunja", "r") }, { service: "vikunja", env: "PM_VIKUNJA_TOKEN", expires: FAR_DATE }); refuses(() => parseCredentialRef({ file: "/a", env: "A", rotateBy: FAR_DATE }, "gitea", "r"), /exactly one/); refuses(() => parseCredentialRef({ rotateBy: FAR_DATE }, "gitea", "r"), /exactly one/); refuses(() => parseCredentialRef({ file: "/a" }, "gitea", "r"), /needs "rotateBy"/); refuses(() => parseCredentialRef({ file: "/a", expires: FAR_DATE }, "gitea", "r"), /unsupported r key: "expires"/); refuses(() => parseCredentialRef({ file: "/a", rotateBy: FAR_DATE }, "vikunja", "r"), /unsupported r key: "rotateBy"/); refuses(() => parseCredentialRef({ file: "/a", expires: FAR_DATE, token: "x" }, "vikunja", "r"), /unsupported r key: "token"/); refuses(() => parseCredentialRef({ file: "a/b", rotateBy: FAR_DATE }, "gitea", "r"), /normalized absolute path/); refuses(() => parseCredentialRef({ file: "/a/../b", rotateBy: FAR_DATE }, "gitea", "r"), /normalized absolute path/); refuses(() => parseCredentialRef({ file: "/a//b", rotateBy: FAR_DATE }, "gitea", "r"), /normalized absolute path/); refuses(() => parseCredentialRef({ env: "pm_token", rotateBy: FAR_DATE }, "gitea", "r"), /\.env must match/); refuses(() => parseCredentialRef({ env: "A", rotateBy: "2026-02-30" }, "gitea", "r"), /not a real date/); refuses(() => parseCredentialRef({ env: "A", expires: "2026-10-04T00:00:00Z" }, "vikunja", "r"), /YYYY-MM-DD/); refuses(() => parseCredentialRef({ env: "A" }, "github", "r"), /unknown credential service/); }); test("check: a good file has no problems", () => { const dir = tmp(); const ref = parseCredentialRef({ file: tokenFile(join(dir, "s"), "t"), expires: FAR_DATE }, "vikunja", "r"); assert.deepEqual(checkCredentialRef(ref, { now: NOW, forbiddenRoots: [join(dir, "repo")] }), { problems: [], warnings: [] }); }); test("check never opens the file: a write-only token passes", () => { const dir = tmp(); const file = tokenFile(join(dir, "s"), "t"); chmodSync(file, 0o200); const ref = parseCredentialRef({ file, rotateBy: FAR_DATE }, "gitea", "r"); assert.deepEqual(checkCredentialRef(ref, { now: NOW }).problems, []); }); test("check: file problems", () => { const dir = tmp(); const s = join(dir, "s"); const problems = (file, opts = {}) => checkCredentialRef(parseCredentialRef({ file, rotateBy: FAR_DATE }, "gitea", "r"), { now: NOW, ...opts }).problems; assert.match(problems(join(s, "absent")).join(), /not found/); const loose = tokenFile(s, "loose"); chmodSync(loose, 0o640); assert.match(problems(loose).join(), /mode 640 gives group or other access/); const empty = join(s, "empty"); writeFileSync(empty, ""); chmodSync(empty, 0o600); assert.match(problems(empty).join(), /empty/); const link = join(s, "link"); symlinkSync(tokenFile(s, "real"), link); assert.match(problems(link).join(), /not a symbolic link/); mkdirSync(join(s, "dir")); assert.match(problems(join(s, "dir")).join(), /regular file/); assert.deepEqual(problems(tokenFile(s, "other")), []); assert.match(problems(tokenFile(s, "uid"), { uid: process.getuid() + 1 }).join(), /owned by uid/); }); test("check: token files can't live in the repository or dataRoot, even through a linked directory", () => { const dir = tmp(); const repo = join(dir, "repo"); const inRepo = tokenFile(join(repo, "secrets"), "t"); const ref = (file) => parseCredentialRef({ file, rotateBy: FAR_DATE }, "gitea", "r"); assert.match(checkCredentialRef(ref(inRepo), { now: NOW, forbiddenRoots: [repo] }).problems.join(), /inside .*repo/); symlinkSync(join(repo, "secrets"), join(dir, "elsewhere")); const viaLink = join(dir, "elsewhere", "t"); assert.match(checkCredentialRef(ref(viaLink), { now: NOW, forbiddenRoots: [repo] }).problems.join(), /inside/); const repoLink = join(dir, "repo-link"); symlinkSync(repo, repoLink); assert.match(checkCredentialRef(ref(inRepo), { now: NOW, forbiddenRoots: [repoLink] }).problems.join(), /inside/); const sibling = tokenFile(join(dir, "repo-secrets"), "t"); assert.deepEqual(checkCredentialRef(ref(sibling), { now: NOW, forbiddenRoots: [repo, join(dir, "absent-root")] }).problems, []); }); test("check: dates and environment references", () => { const vikunja = (expires) => checkCredentialRef(parseCredentialRef({ env: "TOK", expires }, "vikunja", "r"), { now: NOW, env: { TOK: "set" } }); assert.match(vikunja("2026-10-04").problems.join(), /expired on 2026-10-04/); assert.match(vikunja("2026-10-01").problems.join(), /expired/); assert.deepEqual(vikunja("2026-10-05").problems, []); assert.match(vikunja("2026-10-05").warnings.join(), /expires on 2026-10-05/); assert.match(vikunja("2026-10-10").warnings.join(), /expires on/); assert.deepEqual(vikunja("2026-10-12"), { problems: [], warnings: [] }); const gitea = (rotateBy) => checkCredentialRef(parseCredentialRef({ env: "TOK", rotateBy }, "gitea", "r"), { now: NOW, env: { TOK: "set" } }); assert.deepEqual(gitea("2026-10-01").problems, []); assert.match(gitea("2026-10-01").warnings.join(), /rotation was due on 2026-10-01/); assert.deepEqual(gitea(FAR_DATE), { problems: [], warnings: [] }); const unset = checkCredentialRef(parseCredentialRef({ env: "TOK", rotateBy: FAR_DATE }, "gitea", "r"), { now: NOW, env: {} }); assert.deepEqual(unset.problems, []); assert.match(unset.warnings.join(), /not set in this environment/); const blank = checkCredentialRef(parseCredentialRef({ env: "TOK", rotateBy: FAR_DATE }, "gitea", "r"), { now: NOW, env: { TOK: "" } }); assert.match(blank.warnings.join(), /not set/); });