import { clerkMiddleware, createRouteMatcher, clerkClient } from '@clerk/astro/server'; import 'dotenv/config'; declare global { namespace App { interface Locals { canAddInventory: boolean; } } } const isProtectedRoute = createRouteMatcher(['/pokemon']); const isAdminRoute = createRouteMatcher(['/admin']); const TARGET_ORG_ID = "org_3Baav9czkRLLlC7g89oJWqRRulK"; const ADMIN_ORG_IDS = new Set([ "org_3Baav9czkRLLlC7g89oJWqRRulK", "org_3ABdwuK3qD7Saq590ZMQWY7AvVz", ]); export const onRequest = clerkMiddleware(async (auth, context, next) => { const { isAuthenticated, userId, redirectToSignIn, has } = auth(); if (!isAuthenticated && isProtectedRoute(context.request)) { return redirectToSignIn(); } // ── Inventory visibility check ────────────────────────────────────────────── // Resolves to true if the user belongs to the target org OR has the feature const canAddInventory = process.env.INVENTORY_ACCESS === 'true' || ( isAuthenticated && userId && ( !!has({ permission: "org:feature:inventory_add" }) || // Clerk feature flag (await getUserOrgIds(context, userId)).includes(TARGET_ORG_ID) ) ); // Expose the flag to your Astro pages via locals context.locals.canAddInventory = Boolean(canAddInventory); // ── Admin route guard ─────────────────────────────────────────── if (isAdminRoute(context.request)) { if (!isAuthenticated || !userId) { return redirectToSignIn(); } try { const client = await clerkClient(context); const userOrgIds = await getUserOrgIds(context, userId); const matchingOrgIds = userOrgIds.filter((id) => ADMIN_ORG_IDS.has(id)); if (matchingOrgIds.length === 0) { return new Response(null, { status: 404 }); } const membershipLists = await Promise.all( matchingOrgIds.map((orgId) => client.organizations.getOrganizationMembershipList({ organizationId: orgId }) ) ); const isAdmin = membershipLists.some((list) => list.data.some( (m) => m.publicUserData?.userId === userId && m.role === "org:admin" ) ); if (!isAdmin) { return new Response(null, { status: 404 }); } } catch (e) { console.error("Clerk membership check failed:", e); return context.redirect("/"); } } return next(); }); // ── Helper: fetch all org IDs the current user belongs to ─────────────────── async function getUserOrgIds(context: any, userId: string): Promise { try { const client = await clerkClient(context); const memberships = await client.users.getOrganizationMembershipList({ userId }); return memberships.data.map((m) => m.organization.id); } catch (e) { console.error("Failed to fetch user org memberships:", e); return []; } }