Allow api key query fallback
ci / test (push) Successful in 11s

This commit is contained in:
Luna
2026-07-16 20:32:35 +00:00
parent 9905d0f496
commit 0ccd4e47ae
3 changed files with 33 additions and 36 deletions
+6 -6
View File
@@ -15,23 +15,23 @@ Tiny API that clones a Git repo and counts its non-empty lines of code.
## Endpoints
- `GET /loc.txt?repo=<git-url>&ssh_key=<optional-key-file>&ref=<optional-ref>`
- `GET /loc.txt?repo=<git-url>&ssh_key=<optional-key-file>&ref=<optional-ref>&api_key=<optional-api-key>`
- Returns the line count as plain text.
- `GET /loc?repo=<git-url>&ssh_key=<optional-key-file>&ref=<optional-ref>`
- `GET /loc?repo=<git-url>&ssh_key=<optional-key-file>&ref=<optional-ref>&api_key=<optional-api-key>`
- Returns JSON metadata, including a language breakdown by files and non-empty lines.
- `GET /health`
- Health plus queue/cache stats.
You can also pass `query=<git-url>` as a compatibility alias for `repo`.
This is considered unsafe and discouraged because the name is too generic and easier to misuse or collide with upstream tooling.
Prefer `repo` unless you explicitly need legacy compatibility.
## Auth
No auth by default. If `API_KEY` is set, send it as either:
- `x-api-key: ...`
- `Authorization: Bearer ...`
- `api_key=...` query param
The `api_key` query param is unsafe and discouraged because it can leak through logs, browser history, analytics, caches, and referrers.
Prefer `x-api-key` or `Authorization` whenever possible.
## SSH keys
+3 -6
View File
@@ -42,8 +42,9 @@ export function createApp(config: RuntimeConfig, deps: AppDependencies) {
const headerKey = req.header("x-api-key");
const bearer = req.header("authorization")?.replace(/^Bearer\s+/i, "").trim();
const queryKey = readOptionalString(req.query.api_key);
if (headerKey === config.apiKey || bearer === config.apiKey) {
if (headerKey === config.apiKey || bearer === config.apiKey || queryKey === config.apiKey) {
next();
return;
}
@@ -71,11 +72,7 @@ export function createApp(config: RuntimeConfig, deps: AppDependencies) {
}
function readCountRequest(req: Request): CountRequest {
const repo = typeof req.query.repo === "string"
? req.query.repo.trim()
: typeof req.query.query === "string"
? req.query.query.trim()
: "";
const repo = typeof req.query.repo === "string" ? req.query.repo.trim() : "";
if (!repo) {
throw new HttpError(400, "Missing repo query parameter");
}
+24 -24
View File
@@ -45,30 +45,6 @@ test("GET /loc returns count metadata", async () => {
assert.equal(response.body.languages[0].language, "TypeScript");
});
test("GET /loc accepts query as a repo alias", async () => {
const app = createApp(baseConfig, {
getHealth: () => ({ cacheEntries: 0, inFlight: 0, maxConcurrentScans: 4, activeScans: 0, queuedScans: 0 }),
getPublicKey: async () => "ssh-ed25519 AAAA",
getDefaultKeyName: () => "loc_via_git_ed25519",
count: async (requestInput) => ({
repo: requestInput.repo,
ref: requestInput.ref,
sshKey: requestInput.sshKey,
cached: false,
lineCount: 2,
fileCount: 1,
languages: [{ language: "Plain Text", files: 1, lines: 2 }],
scannedAt: "2026-01-01T00:00:00.000Z",
durationMs: 10
})
});
const response = await request(app).get("/loc?query=https://example.com/query.git");
assert.equal(response.status, 200);
assert.equal(response.body.repo, "https://example.com/query.git");
});
test("GET /ssh/public-key returns text without auth", async () => {
const app = createApp({ ...baseConfig, apiKey: "secret" }, {
getHealth: () => ({ cacheEntries: 0, inFlight: 0, maxConcurrentScans: 4, activeScans: 0, queuedScans: 0 }),
@@ -99,3 +75,27 @@ test("GET /loc enforces api key when configured", async () => {
assert.equal(response.status, 401);
});
test("GET /loc accepts api_key query param as an unsafe fallback", async () => {
const app = createApp({ ...baseConfig, apiKey: "secret" }, {
getHealth: () => ({ cacheEntries: 0, inFlight: 0, maxConcurrentScans: 4, activeScans: 0, queuedScans: 0 }),
getPublicKey: async () => "ssh-ed25519 AAAA",
getDefaultKeyName: () => "loc_via_git_ed25519",
count: async (requestInput) => ({
repo: requestInput.repo,
ref: requestInput.ref,
sshKey: requestInput.sshKey,
cached: false,
lineCount: 5,
fileCount: 1,
languages: [{ language: "Plain Text", files: 1, lines: 5 }],
scannedAt: "2026-01-01T00:00:00.000Z",
durationMs: 10
})
});
const response = await request(app).get("/loc?repo=https://example.com/repo.git&api_key=secret");
assert.equal(response.status, 200);
assert.equal(response.body.lineCount, 5);
});