From 8f5a069e77e0779dbb6c44380139494bdf0fe615 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Sun, 9 Aug 2026 09:52:37 +0000 Subject: [PATCH] fix(api): add /api/readyz to authMiddleware bypass list (GRO-2687) /api/readyz was returning 401 Unauthorized in UAT (GRO-2692 Shedward regression). The authMiddleware for /api/* did not whitelist /api/readyz, causing every unauthenticated probe request to be rejected. Add /api/readyz to the bypass condition alongside /api/auth/* and /api/health. Add readyz-auth-bypass.test.ts confirming the route passes through the middleware without auth and that non-whitelisted paths still block (503 when auth not configured). Closes GRO-2692 regression (UAT fail). --- src/__tests__/readyz-auth-bypass.test.ts | 44 ++++++++++++++++++++++++ src/middleware/auth.ts | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/readyz-auth-bypass.test.ts diff --git a/src/__tests__/readyz-auth-bypass.test.ts b/src/__tests__/readyz-auth-bypass.test.ts new file mode 100644 index 0000000..744ca60 --- /dev/null +++ b/src/__tests__/readyz-auth-bypass.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect, vi } from "vitest"; +import { Hono } from "hono"; + +// Mock auth lib so getAuth() throws — non-bypass paths hit the 503 "not configured" guard. +vi.mock("../lib/auth.js", () => ({ + getAuth: () => { + throw new Error("auth not configured"); + }, + initAuth: vi.fn(), + getActiveProviders: vi.fn(() => []), +})); + +describe("authMiddleware bypass: /api/readyz", () => { + it("serves /api/readyz without auth (bypass before auth check)", async () => { + // Ensure AUTH_DISABLED is not set so the bypass is exercised, not AUTH_DISABLED shortcut. + const prev = process.env.AUTH_DISABLED; + delete process.env.AUTH_DISABLED; + + const { authMiddleware } = await import("../middleware/auth.js"); + const app = new Hono(); + app.use("/api/*", authMiddleware); + app.get("/api/readyz", (c) => c.json({ status: "ok" }, 200)); + + const res = await app.request("/api/readyz", { method: "GET" }); + expect(res.status).toBe(200); + + if (prev !== undefined) process.env.AUTH_DISABLED = prev; + }); + + it("blocks non-whitelisted /api/* paths when auth is not configured", async () => { + const prev = process.env.AUTH_DISABLED; + delete process.env.AUTH_DISABLED; + + const { authMiddleware } = await import("../middleware/auth.js"); + const app = new Hono(); + app.use("/api/*", authMiddleware); + app.get("/api/staff", (c) => c.json({ ok: true }, 200)); + + const res = await app.request("/api/staff", { method: "GET" }); + expect(res.status).toBe(503); + + if (prev !== undefined) process.env.AUTH_DISABLED = prev; + }); +}); diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index 830350f..329ba82 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -23,7 +23,7 @@ if (process.env.AUTH_DISABLED === "true") { } export const authMiddleware: MiddlewareHandler = async (c, next) => { - if (c.req.path.startsWith("/api/auth/") || c.req.path === "/api/health") { + if (c.req.path.startsWith("/api/auth/") || c.req.path === "/api/health" || c.req.path === "/api/readyz") { await next(); return; }