Initial curated CertScope source from fe2bbe3ca5c98bf64be26a09c6c58ee805a7f87e
This commit is contained in:
commit
7348faf6c0
65 files changed
+12509
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { InspectionInputError, InspectionResolutionError } from "@/lib/inspect/policy";
|
||||
|
||||
const { inspectMock } = vi.hoisted(() => ({ inspectMock: vi.fn() }));
|
||||
vi.mock("@/lib/inspect/inspect", () => ({ inspectHost: inspectMock }));
|
||||
|
||||
import { classifyInspectionError, GET } from "./route";
|
||||
|
||||
function request(host = "example.com") {
|
||||
return new NextRequest("http://localhost/api/inspect?host=" + encodeURIComponent(host));
|
||||
}
|
||||
|
||||
describe("GET /api/inspect error classification", () => {
|
||||
beforeEach(() => inspectMock.mockReset());
|
||||
|
||||
it("returns a report with no-store caching on success", async () => {
|
||||
inspectMock.mockResolvedValue({ host: "example.com" });
|
||||
const response = await GET(request());
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("cache-control")).toBe("no-store");
|
||||
expect(await response.json()).toEqual({ host: "example.com" });
|
||||
});
|
||||
|
||||
it("maps user input and resolution policy errors to client errors", async () => {
|
||||
inspectMock.mockRejectedValueOnce(new InspectionInputError("invalid host"));
|
||||
expect((await GET(request("bad input"))).status).toBe(400);
|
||||
inspectMock.mockRejectedValueOnce(new InspectionResolutionError("not found"));
|
||||
expect((await GET(request("missing.example"))).status).toBe(422);
|
||||
});
|
||||
|
||||
it("classifies unexpected failures as a generic 500 without exposing details", () => {
|
||||
expect(classifyInspectionError(new Error("private upstream detail"))).toEqual({
|
||||
status: 500,
|
||||
message: "Inspection service is temporarily unavailable. Try again shortly.",
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { checkRateLimit, clientKeyFromHeaders, tryAcquireInspection } from "@/lib/inspect/admission";
|
||||
import { inspectHost } from "@/lib/inspect/inspect";
|
||||
import { InspectionInputError, InspectionPolicyError, InspectionResolutionError } from "@/lib/inspect/policy";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
function jsonError(error: string, status: number, headers: Record<string, string> = {}) {
|
||||
return NextResponse.json({ error }, { status, headers: { "Cache-Control": "no-store", ...headers } });
|
||||
}
|
||||
|
||||
export function classifyInspectionError(error: unknown) {
|
||||
if (error instanceof InspectionInputError || error instanceof InspectionPolicyError) return { status: 400, message: error.message };
|
||||
if (error instanceof InspectionResolutionError) return { status: 422, message: error.message };
|
||||
return { status: 500, message: "Inspection service is temporarily unavailable. Try again shortly." };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const host = request.nextUrl.searchParams.get("host") ?? "";
|
||||
const rate = checkRateLimit(clientKeyFromHeaders(request.headers));
|
||||
if (!rate.allowed) return jsonError("Request rate limit reached. Try again shortly.", 429, { "Retry-After": String(rate.retryAfterSeconds) });
|
||||
const release = tryAcquireInspection();
|
||||
if (!release) {
|
||||
return jsonError("Inspection capacity is temporarily full. Try again shortly.", 429, { "Retry-After": "1" });
|
||||
}
|
||||
|
||||
try {
|
||||
return NextResponse.json(await inspectHost(host), { headers: { "Cache-Control": "no-store" } });
|
||||
} catch (error) {
|
||||
const classified = classifyInspectionError(error);
|
||||
if (classified.status === 500) console.error("inspection_unexpected_error", {
|
||||
name: error instanceof Error ? error.name : "UnknownError",
|
||||
code: typeof error === "object" && error !== null && "code" in error ? String((error as { code?: unknown }).code ?? "") : "",
|
||||
});
|
||||
return jsonError(classified.message, classified.status);
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user