/** * @file Route tests for git credentials (issue #45): /api/git-credentials * never returns a password and refuses a cross-origin write, and a lane's * fetch against a remote that wants a login answers 409 EGITAUTH with the * host the git bar's login form needs. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, before, after } = 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-creds-route-")); process.env.DASHBOARD_DB_PATH = path.join(ROOT, "t.db"); process.env.CLAUDE_HOME = path.join(ROOT, ".claude"); process.env.DASHBOARD_REMOTE_SYNC_MS = "0"; process.env.DASHBOARD_LIVENESS_PROBE = "0"; process.env.GIT_CONFIG_NOSYSTEM = "1"; process.env.HOME = ROOT; const { createApp } = require("../index"); let app; let remote; let base; let host; function request(method, p, body, headers = {}) { return fetch(`${base}${p}`, { method, headers: { "Content-Type": "application/json", ...headers }, body: body === undefined ? undefined : JSON.stringify(body), }).then(async (r) => ({ status: r.status, body: await r.json().catch(() => null) })); } before(async () => { remote = http.createServer((_req, res) => { res.writeHead(401, { "WWW-Authenticate": 'Basic realm="t"' }); res.end(); }); await new Promise((r) => remote.listen(0, "127.0.0.1", r)); host = `127.0.0.1:${remote.address().port}`; app = http.createServer(createApp()); await new Promise((r) => app.listen(0, "127.0.0.1", r)); base = `http://127.0.0.1:${app.address().port}`; }); after(async () => { await new Promise((r) => app.close(r)); await new Promise((r) => remote.close(r)); }); describe("/api/git-credentials", () => { it("saves, lists without the secret, and deletes", async () => { const saved = await request("POST", "/api/git-credentials", { protocol: "https", host: "git.example.test", username: "vi", password: "super-secret-token", }); assert.equal(saved.status, 200); const list = await request("GET", "/api/git-credentials"); assert.equal(list.body.items[0].hasToken, true); assert.ok(!JSON.stringify(list.body).includes("super-secret-token")); const del = await request("DELETE", "/api/git-credentials/https/git.example.test"); assert.deepEqual(del.body.items, []); }); it("refuses a cross-origin write", async () => { const r = await request( "POST", "/api/git-credentials", { protocol: "https", host: "h.test", username: "u", password: "p" }, { Origin: "https://evil.example" } ); assert.equal(r.status, 403); }); }); describe("POST /api/lanes/:id/git/fetch", () => { it("answers 409 EGITAUTH with the host when the remote wants a login", async () => { const repo = path.join(ROOT, "repo"); execFileSync("git", ["init", "-q", repo]); execFileSync("git", ["-C", repo, "remote", "add", "origin", `http://${host}/o/r.git`]); const lane = await request("POST", "/api/lanes/ensure", { cwd: repo, tool: "claude" }); const r = await request("POST", `/api/lanes/${lane.body.lane.id}/git/fetch`, {}); assert.equal(r.status, 409); assert.equal(r.body.error.code, "EGITAUTH"); assert.equal(r.body.error.auth.host, host); assert.equal(r.body.error.auth.rejected, false); }); });