Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ClerkProvider } from "@clerk/nextjs";
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
Expand Down Expand Up @@ -61,16 +62,18 @@ const structuredData = {

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData).replace(/</g, "\\u003c"),
}}
/>
</head>
<body className="min-h-full bg-[#05060a] text-white antialiased">{children}</body>
</html>
<ClerkProvider>
<html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData).replace(/</g, "\\u003c"),
}}
/>
</head>
<body className="min-h-full bg-[#05060a] text-white antialiased">{children}</body>
</html>
</ClerkProvider>
);
}
25 changes: 25 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions lib/gates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "server-only";

import { clerkClient } from "@clerk/nextjs/server";

const CACHE_TTL_MS = 60_000;

let cachedGate: { expiresAt: number; isGated: boolean } | undefined;
let pendingGateLookup: Promise<boolean> | undefined;

/**
* Reads this app's gate from the fleet operations organization. The module
* cache limits Clerk Backend API reads to one per minute per runtime instance.
*/
export async function isAppGated(): Promise<boolean> {
if (cachedGate && cachedGate.expiresAt > Date.now()) {
return cachedGate.isGated;
}

if (!pendingGateLookup) {
pendingGateLookup = readGate().finally(() => {
pendingGateLookup = undefined;
});
}

return pendingGateLookup;
}

async function readGate(): Promise<boolean> {
const organizationId = process.env.GATES_ORG_ID;
const appId = process.env.GATES_APP_ID;

if (!organizationId || !appId) {
throw new Error("GATES_ORG_ID and GATES_APP_ID must be configured in production.");
}

const client = await clerkClient();
const organization = await client.organizations.getOrganization({ organizationId });
const gates = organization.publicMetadata?.gates;
const isGated =
typeof gates === "object" &&
gates !== null &&
!Array.isArray(gates) &&
(gates as Record<string, unknown>)[appId] === true;

cachedGate = {
isGated,
expiresAt: Date.now() + CACHE_TTL_MS,
};

return isGated;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "bun test"
},
"dependencies": {
"@clerk/nextjs": "^7.5.20",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1",
"@react-three/postprocessing": "^3.0.4",
Expand Down
24 changes: 24 additions & 0 deletions proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { clerkMiddleware } from "@clerk/nextjs/server";
import { isAppGated } from "@/lib/gates";

export default clerkMiddleware(async (auth) => {
// Preview and local environments intentionally remain public.
if (process.env.VERCEL_ENV !== "production") {
return;
}

if (await isAppGated()) {
await auth.protect();
}
});

export const config = {
matcher: [
/*
* Match all request paths except for static files and Next.js internals.
* This avoids unnecessary Clerk work for immutable public assets.
*/
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|json|jpe?g|png|gif|svg|webp|ico|ttf|woff2?|map)).*)",
"/(api|trpc)(.*)",
],
};
Loading