Files
pokemon/src/middleware.ts

92 lines
3.0 KiB
TypeScript
Raw Normal View History

import { clerkMiddleware, createRouteMatcher, clerkClient } from '@clerk/astro/server';
import 'dotenv/config';
2026-05-28 14:42:50 -04:00
declare global {
namespace App {
interface Locals {
canAddInventory: boolean;
}
}
}
2026-05-28 14:42:50 -04:00
const isProtectedRoute = createRouteMatcher(['/pokemon']);
const isAdminRoute = createRouteMatcher(['/admin']);
2026-05-28 14:42:50 -04:00
const TARGET_ORG_ID = "org_3Baav9czkRLLlC7g89oJWqRRulK";
2026-05-28 14:42:50 -04:00
const ADMIN_ORG_IDS = new Set([
"org_3Baav9czkRLLlC7g89oJWqRRulK",
"org_3ABdwuK3qD7Saq590ZMQWY7AvVz",
]);
export const onRequest = clerkMiddleware(async (auth, context, next) => {
const { isAuthenticated, userId, redirectToSignIn, has } = auth();
2026-05-28 14:42:50 -04:00
2026-03-05 22:59:16 -05:00
if (!isAuthenticated && isProtectedRoute(context.request)) {
return redirectToSignIn();
}
2026-05-28 14:42:50 -04:00
// ── 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)
)
);
2026-05-28 14:42:50 -04:00
// Expose the flag to your Astro pages via locals
context.locals.canAddInventory = Boolean(canAddInventory);
2026-05-28 14:42:50 -04:00
2026-05-26 05:32:26 -04:00
// ── Admin route guard ───────────────────────────────────────────
if (isAdminRoute(context.request)) {
if (!isAuthenticated || !userId) {
return redirectToSignIn();
}
2026-05-28 14:42:50 -04:00
try {
const client = await clerkClient(context);
2026-05-28 14:42:50 -04:00
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 })
)
);
2026-05-28 14:42:50 -04:00
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("/");
}
}
2026-05-28 14:42:50 -04:00
return next();
});
2026-05-28 14:42:50 -04:00
// ── Helper: fetch all org IDs the current user belongs to ───────────────────
async function getUserOrgIds(context: any, userId: string): Promise<string[]> {
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 [];
}
2026-05-28 14:42:50 -04:00
}