/** * @file Issue #45: a dashboard fetch/pull/push against a remote that needs a * login. A local HTTP "remote" answers 401 to every request and records the * Authorization header it was sent, so the tests see exactly what git sent: * nothing → EGITAUTH naming the host; a typed credential → that credential, * as Basic auth, to that host; saved only when git succeeded AND asked to. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, before, after, beforeEach } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const http = require("node:http"); const { execFileSync } = require("node:child_process"); const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "git-ops-auth-")); process.env.CLAUDE_HOME = path.join(ROOT, ".claude"); // The machine's own helpers must not answer for the fake host. process.env.GIT_CONFIG_NOSYSTEM = "1"; process.env.HOME = ROOT; const { runGitAction } = require("../lib/git-ops"); const creds = require("../lib/git-credentials"); let server; let host; const seen = []; const LANE = path.join(ROOT, "lane"); before(async () => { server = http.createServer((req, res) => { seen.push(req.headers.authorization || null); res.writeHead(401, { "WWW-Authenticate": 'Basic realm="t"' }); res.end(); }); await new Promise((r) => server.listen(0, "127.0.0.1", r)); host = `127.0.0.1:${server.address().port}`; execFileSync("git", ["init", "-q", LANE]); execFileSync("git", ["-C", LANE, "remote", "add", "origin", `http://${host}/o/r.git`]); }); after(() => new Promise((r) => server.close(r))); beforeEach(() => { seen.length = 0; fs.rmSync(process.env.CLAUDE_HOME, { recursive: true, force: true }); }); describe("git auth on fetch", () => { it("answers EGITAUTH naming the host when git has no credential", async () => { await assert.rejects(runGitAction(LANE, "fetch"), (e) => { assert.equal(e.code, "EGITAUTH"); assert.deepEqual(e.auth, { protocol: "http", host, username: null, rejected: false }); return true; }); }); it("sends a typed credential as Basic auth, and does not save a failed one", async () => { await assert.rejects( runGitAction(LANE, "fetch", { credentials: { protocol: "http", host, username: "vi", password: "tok", remember: true }, }), (e) => e.code === "EGITAUTH" && e.auth.rejected === true ); const basic = `Basic ${Buffer.from("vi:tok").toString("base64")}`; assert.ok(seen.includes(basic), `expected ${basic} in ${JSON.stringify(seen)}`); assert.deepEqual(creds.list(), [], "a credential git rejected is never saved"); }); it("uses a stored credential without being asked", async () => { creds.save({ protocol: "http", host, username: "saved", password: "s3" }); await assert.rejects(runGitAction(LANE, "fetch"), { code: "EGITAUTH" }); assert.ok(seen.includes(`Basic ${Buffer.from("saved:s3").toString("base64")}`)); }); it("refuses a credential for a host that is not this repo's remote", async () => { await assert.rejects( runGitAction(LANE, "fetch", { credentials: { protocol: "https", host: "evil.test", username: "u", password: "p" }, }), { code: "EBADHOST" } ); assert.equal(seen.length, 0); }); });