/** * The gate every order-scoped route handler stands behind. * * Signed in, and the order is one this user has claimed. Both checks belong * here rather than in each handler because a route handler is reachable * directly, and an ownership check that only three of four routes perform is * the one an attacker uses. * * Refusals are deliberately indistinguishable: an order that does not exist * and an order belonging to somebody else both answer 404, so the route cannot * be used to find out which order ids are real. */ import { auth } from "@clerk/nextjs/server"; import { NextResponse } from "next/server"; import { userOwnsOrder } from "@/lib/checkout-order-claim"; import type { OrderLookupError } from "@/vendor/carrier/client"; export type OrderRouteGuard = | { ok: true; userId: string; orderId: string } | { ok: false; response: NextResponse }; export async function guardOrderRoute( params: Promise<{ orderId: string }>, ): Promise { const { userId } = await auth(); if (!userId) { return { ok: false, response: NextResponse.json({ error: "Unauthorized" }, { status: 401 }) }; } const { orderId } = await params; if (!orderId) { return { ok: false, response: NextResponse.json({ error: "orderId is required" }, { status: 400 }), }; } if (!(await userOwnsOrder(userId, orderId))) { return { ok: false, response: NextResponse.json({ error: "Order not found" }, { status: 404 }), }; } return { ok: true, userId, orderId }; } /** * One fulfilment failure, as customer copy plus a status. * * `action` names the thing that did not happen ("read your usage", "cancel * this plan"), because "something went wrong" tells the reader nothing about * whether their eSIM is still working. */ export function fulfilmentFailureResponse( reason: OrderLookupError, action: string, orderId: string, ): NextResponse { if (reason === "not-found") { return NextResponse.json({ error: "Order not found", code: "not_found" }, { status: 404 }); } if (reason === "unconfigured") { console.error( `[orders] NEXT_PUBLIC_CARRIER_API_URL is unset — cannot ${action} for ${orderId}`, ); return NextResponse.json( { error: `We can't ${action} yet. Please contact support with your order number.`, code: "fulfilment_unconfigured", }, { status: 503 }, ); } console.error(`[orders] failed to ${action} for ${orderId}: ${reason}`); return NextResponse.json( { error: `We couldn't ${action} right now. Please try again in a moment.`, code: "fulfilment_unavailable", }, { status: 502 }, ); }