diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts index 7254172..f4a88d4 100644 --- a/src/auth/middleware.ts +++ b/src/auth/middleware.ts @@ -85,7 +85,7 @@ export function makeAuthMiddleware(store: Store, cfg: IrisConfig) { // other: a caller who presents the gate token is not thereby anybody. if (gate !== undefined) { const header = req.header("authorization") ?? ""; - const match = header.match(/^Bearer\s+(.+)$/i); + const match = header.match(/^Bearer\s+(\S.*)$/i); // Compared after trimming, because `apiToken` trims what it read from config: a // configured `" s3cret "` must not be a gate that only an untrimmed copy opens. if (!match || match[1].trim() !== gate) { diff --git a/src/routes/quality.ts b/src/routes/quality.ts index d19642d..b95c805 100644 --- a/src/routes/quality.ts +++ b/src/routes/quality.ts @@ -29,7 +29,7 @@ function tokenMatches(presented: string, configured: string): boolean { // The bearer token on the request, if it presented one in the form the rest of the // API uses. function bearer(header: string | undefined): string | null { - const m = /^Bearer\s+(.+)$/i.exec(header ?? ""); + const m = /^Bearer\s+(\S.*)$/i.exec(header ?? ""); return m ? m[1].trim() : null; } diff --git a/src/routes/sessions.ts b/src/routes/sessions.ts index 515db69..330f671 100644 --- a/src/routes/sessions.ts +++ b/src/routes/sessions.ts @@ -281,7 +281,7 @@ export function sessionsRouter(cfg: IrisConfig, store: Store): Router { // first (free), then how many bytes of upload are already arriving, then the caller's // upload budget for the minute — and only then is a byte of this body read. r.post("/", requestSizeGate(), inFlightUploads, uploadBudget, uploadImages, async (req: AuthedRequest, res) => { - const files = (req.files as Express.Multer.File[] | undefined) ?? []; + const files = Array.isArray(req.files) ? req.files : []; if (files.length === 0) { sendError(res, 400, "invalid_request", "At least one file part named 'images' is required (image or PDF)"); return; diff --git a/test/quality-route.test.ts b/test/quality-route.test.ts index 712ad31..8bb71d3 100644 --- a/test/quality-route.test.ts +++ b/test/quality-route.test.ts @@ -128,6 +128,18 @@ test("a missing, malformed or wrong token is rejected without touching the store } }); +// The padding after "Bearer" is what `\s+(\S` must allow (fetch drops the trailing padding). +test("a whitespace-padded token opens the endpoint", async () => { + const { store } = fakeStore(); + const srv = await serve(qualityRouter(store, { quality_token: TOKEN })); + try { + const res = await srv.get("", { headers: { authorization: `Bearer ${TOKEN} ` } }); + assert.equal(res.status, 200); + } finally { + srv.close(); + } +}); + test("a valid token gets the tally, and the response is never shared-cached", async () => { const { store } = fakeStore(); const srv = await serve(qualityRouter(store, { quality_token: TOKEN }));