Files
stack/packages/organization/tests/credentials.test.mjs
jason.woltje a8d9eecf65 organization: move packages/business to packages/organization (rename r2 stage B1)
git mv packages/business -> packages/organization (@mosaic/organization). The
old package stays as one-line re-export shims of each moved module, with
src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs
forwarding to the moved executable. BusinessError is OrganizationConfigError
(one constructor, name kept BusinessError); OrganizationError stays distinct.
The bus adapter gains organization-context.mjs (busOrganization), with
business.mjs (busBusiness) as the same function.

Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh
COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure
walker (organization-imports.mjs, both roots required) move to the new path.
v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage
A contract tests stay where they were and pass as before.
2026-11-09 11:47:48 -06:00

115 lines
6.5 KiB
JavaScript

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/);
});