Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/auth/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/quality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/routes/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions test/quality-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand Down
Loading