diff --git a/lib/apify/__tests__/validateGetScraperResultsRequest.test.ts b/lib/apify/__tests__/validateGetScraperResultsRequest.test.ts index 46101820..e638341b 100644 --- a/lib/apify/__tests__/validateGetScraperResultsRequest.test.ts +++ b/lib/apify/__tests__/validateGetScraperResultsRequest.test.ts @@ -2,9 +2,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest, NextResponse } from "next/server"; import { validateGetScraperResultsRequest } from "../validateGetScraperResultsRequest"; -import { validateAdminAuth } from "@/lib/admins/validateAdminAuth"; +import { validateAuthContext } from "@/lib/auth/validateAuthContext"; -vi.mock("@/lib/admins/validateAdminAuth", () => ({ validateAdminAuth: vi.fn() })); +vi.mock("@/lib/auth/validateAuthContext", () => ({ validateAuthContext: vi.fn() })); vi.mock("@/lib/networking/getCorsHeaders", () => ({ getCorsHeaders: () => ({}) })); const RUN_ID = "run_abc_123"; @@ -17,27 +17,21 @@ const makeRequest = (path = `/api/apify/runs/${RUN_ID}`) => describe("validateGetScraperResultsRequest", () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(validateAdminAuth).mockResolvedValue(authResult); + vi.mocked(validateAuthContext).mockResolvedValue(authResult); }); it("propagates auth error before checking runId", async () => { const err = NextResponse.json({}, { status: 401 }); - vi.mocked(validateAdminAuth).mockResolvedValue(err); + vi.mocked(validateAuthContext).mockResolvedValue(err); expect(await validateGetScraperResultsRequest(makeRequest(), "")).toBe(err); }); - it("propagates 403 from non-admin auth", async () => { - const err = NextResponse.json({}, { status: 403 }); - vi.mocked(validateAdminAuth).mockResolvedValue(err); - expect(await validateGetScraperResultsRequest(makeRequest(), RUN_ID)).toBe(err); - }); - - it("returns 400 when runId is empty after admin auth succeeds", async () => { + it("returns 400 when runId is empty after auth succeeds", async () => { const res = (await validateGetScraperResultsRequest(makeRequest(), "")) as NextResponse; expect(res.status).toBe(400); }); - it("returns validated runId on happy path", async () => { + it("passes any authenticated caller through — no admin or ownership check", async () => { expect(await validateGetScraperResultsRequest(makeRequest(), RUN_ID)).toEqual({ runId: RUN_ID, }); diff --git a/lib/apify/validateGetScraperResultsRequest.ts b/lib/apify/validateGetScraperResultsRequest.ts index dd2acc8d..07caec13 100644 --- a/lib/apify/validateGetScraperResultsRequest.ts +++ b/lib/apify/validateGetScraperResultsRequest.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { validateAdminAuth } from "@/lib/admins/validateAdminAuth"; +import { validateAuthContext } from "@/lib/auth/validateAuthContext"; export const getScraperResultsParamsSchema = z.object({ runId: z.string().min(1), @@ -10,18 +10,22 @@ export const getScraperResultsParamsSchema = z.object({ export type GetScraperResultsParams = z.infer; /** - * Authenticates the request as an admin, then validates the runId path param. + * Authenticates the request (any valid API key or Bearer token), then + * validates the runId path param. * - * Admin-only: Apify run identifiers are not account-scoped, and the poller - * is a backend-only caller (tasks). + * Deliberately not owner-scoped (chat#1840 decision, 2026-07-03): scrape + * datasets are public social content and Apify run ids are unguessable, + * so possession of a runId plus any valid credential is sufficient. + * Authentication still gates the endpoint so anonymous callers can't use + * it as a free Apify proxy. */ export async function validateGetScraperResultsRequest( request: NextRequest, runId: string, ): Promise { - const authResult = await validateAdminAuth(request); - if (authResult instanceof NextResponse) { - return authResult; + const auth = await validateAuthContext(request); + if (auth instanceof NextResponse) { + return auth; } const parsed = getScraperResultsParamsSchema.safeParse({ runId });