52 lines
1.4 KiB
TypeScript
52 lines
1.4 KiB
TypeScript
|
|
/**
|
||
|
|
* Shared helper for admin action proxy routes.
|
||
|
|
* Validates the caller is an admin (checks is_admin in the JWT), then
|
||
|
|
* proxies the action to the V2 gateway.
|
||
|
|
*/
|
||
|
|
import { type NextRequest, NextResponse } from "next/server";
|
||
|
|
import { gatewayUrl } from "@/lib/api/gateway";
|
||
|
|
import { getAccessToken } from "@/lib/auth/session";
|
||
|
|
import { decodeJwt } from "@/lib/auth/jwt";
|
||
|
|
|
||
|
|
export async function adminProxy(
|
||
|
|
_req: NextRequest,
|
||
|
|
gatewayPath: string,
|
||
|
|
method: string = "POST"
|
||
|
|
): Promise<NextResponse> {
|
||
|
|
const token = await getAccessToken();
|
||
|
|
if (!token) {
|
||
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Quick admin check on the server side before forwarding
|
||
|
|
const claims = decodeJwt(token);
|
||
|
|
const isAdmin =
|
||
|
|
String(claims?.is_admin) === "true" ||
|
||
|
|
claims?.is_admin === true ||
|
||
|
|
String(claims?.is_tenant_admin) === "true";
|
||
|
|
|
||
|
|
if (!isAdmin) {
|
||
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const res = await fetch(gatewayUrl(gatewayPath), {
|
||
|
|
method,
|
||
|
|
cache: "no-store",
|
||
|
|
headers: {
|
||
|
|
Accept: "application/json",
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!res.ok) {
|
||
|
|
const err = await res.json().catch(() => null) as { message?: string } | null;
|
||
|
|
return NextResponse.json(
|
||
|
|
{ error: err?.message ?? "Gateway error" },
|
||
|
|
{ status: res.status }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NextResponse.json({ ok: true });
|
||
|
|
}
|