From 8bb7f915287a9c9ce0816f4679448088ddce4dc8 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Tue, 25 Nov 2025 04:45:27 -0800 Subject: [PATCH 01/30] feat: add Fireproof Dashboard with tenant and ledger management using React Query --- pnpm-lock.yaml | 21 ++ vibes.diy/pkg/app/root.tsx | 49 +++-- vibes.diy/pkg/app/routes.ts | 1 + vibes.diy/pkg/app/routes/fireproof.tsx | 276 +++++++++++++++++++++++++ vibes.diy/pkg/package.json | 2 + 5 files changed, 331 insertions(+), 18 deletions(-) create mode 100644 vibes.diy/pkg/app/routes/fireproof.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f56ef247d..cf477b1a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -697,6 +697,9 @@ importers: '@fireproof/core': specifier: 0.24.0 version: 0.24.0(react@19.2.0)(typescript@5.9.3) + '@fireproof/core-protocols-dashboard': + specifier: 0.24.0 + version: 0.24.0(typescript@5.9.3) '@fireproof/core-runtime': specifier: 0.24.0 version: 0.24.0(typescript@5.9.3) @@ -718,6 +721,9 @@ importers: '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@4.1.17) + '@tanstack/react-query': + specifier: ^5.90.10 + version: 5.90.10(react@19.2.0) '@vibes.diy/hosting-base': specifier: workspace:* version: link:../../hosting/base @@ -3678,6 +3684,14 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/query-core@5.90.10': + resolution: {integrity: sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==} + + '@tanstack/react-query@5.90.10': + resolution: {integrity: sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==} + peerDependencies: + react: ^18 || ^19 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -12737,6 +12751,13 @@ snapshots: tailwindcss: 4.1.17 vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + '@tanstack/query-core@5.90.10': {} + + '@tanstack/react-query@5.90.10(react@19.2.0)': + dependencies: + '@tanstack/query-core': 5.90.10 + react: 19.2.0 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 diff --git a/vibes.diy/pkg/app/root.tsx b/vibes.diy/pkg/app/root.tsx index 9f38be90e..c79b5b66b 100644 --- a/vibes.diy/pkg/app/root.tsx +++ b/vibes.diy/pkg/app/root.tsx @@ -20,6 +20,17 @@ import { ClerkProvider } from "@clerk/clerk-react"; import { CookieConsentProvider } from "./contexts/CookieConsentContext.js"; import { ThemeProvider } from "./contexts/ThemeContext.js"; import { getLibraryImportMap } from "./config/import-map.js"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +// Create a client instance for React Query +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + refetchOnWindowFocus: false, + }, + }, +}); export const links: Route.LinksFunction = () => { const rawBase = VibesDiyEnv.APP_BASENAME(); @@ -120,24 +131,26 @@ export function Layout({ children }: { children: React.ReactNode }) { {/* TODO: Re-enable GtmNoScript when consent can be checked server-side */} {/* */} - - - - {children} - - - - - - - - + + + + + {children} + + + + + + + + + diff --git a/vibes.diy/pkg/app/routes.ts b/vibes.diy/pkg/app/routes.ts index 32859788d..b5721e7fc 100644 --- a/vibes.diy/pkg/app/routes.ts +++ b/vibes.diy/pkg/app/routes.ts @@ -24,6 +24,7 @@ export default [ route("settings", "./routes/settings.tsx", { id: "settings" }), route("about", "./routes/about.tsx", { id: "about" }), + route("fireproof", "./routes/fireproof.tsx", { id: "fireproof" }), route("sso-callback", "./routes/sso-callback.tsx", { id: "sso-callback" }), route("remix/:vibeSlug?", "./routes/remix.tsx", { id: "remix" }), route("vibe/:titleId/:installId", "./routes/vibe.$titleId.$installId.tsx", { diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx new file mode 100644 index 000000000..f422b4a6b --- /dev/null +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -0,0 +1,276 @@ +import React, { useMemo } from "react"; +import { useAuth } from "@clerk/clerk-react"; +import { useQuery } from "@tanstack/react-query"; +import { DashboardApi } from "@fireproof/core-protocols-dashboard"; +import type { + ResListTenantsByUser, + ResListLedgersByUser, + UserTenant, + LedgerUser, +} from "@fireproof/core-protocols-dashboard"; +import type { Result } from "@adviser/cement"; +import SimpleAppLayout from "../components/SimpleAppLayout.js"; +import { HomeIcon } from "../components/SessionSidebar/HomeIcon.js"; +import { VibesDiyEnv } from "../config/env.js"; + +export function meta() { + return [ + { title: "Fireproof Dashboard - Vibes DIY" }, + { + name: "description", + content: "Manage your Fireproof tenants and ledgers", + }, + ]; +} + +// Helper to convert Result monad to Promise for React Query +function wrapResultToPromise(pro: () => Promise>) { + return async (): Promise => { + const res = await pro(); + if (res.isOk()) { + return res.Ok(); + } + throw res.Err(); + }; +} + +export default function FireproofDashboard() { + const { isSignedIn, isLoaded, getToken } = useAuth(); + + // Create DashboardApi instance with Clerk auth + const api = useMemo(() => { + return new DashboardApi({ + apiUrl: VibesDiyEnv.CONNECT_API_URL(), + fetch: window.fetch.bind(window), + getToken: async () => { + const token = await getToken({ template: "with-email" }); + return { + type: "clerk" as const, + token: token || "", + }; + }, + }); + }, [getToken]); + + // Query to list all tenants for the logged-in user + const tenantsQuery = useQuery({ + queryKey: ["listTenantsByUser"], + queryFn: wrapResultToPromise(() => api.listTenantsByUser({})), + enabled: isLoaded && isSignedIn, + }); + + // Query to list all ledgers for the logged-in user + const ledgersQuery = useQuery({ + queryKey: ["listLedgersByUser"], + queryFn: wrapResultToPromise(() => api.listLedgersByUser({})), + enabled: isLoaded && isSignedIn, + }); + + // Not authenticated view + if (isLoaded && !isSignedIn) { + return ( + + + + + + } + > +
+

Fireproof Dashboard

+
+

+ Please sign in to view your Fireproof tenants and ledgers. +

+
+
+
+ ); + } + + return ( + + + + + + } + > +
+

Fireproof Dashboard

+ +
+ {/* Tenants Section */} +
+

Tenants

+ {tenantsQuery.isLoading && ( +
+

+ Loading tenants... +

+
+ )} + {tenantsQuery.isError && ( +
+

+ Error loading tenants:{" "} + {tenantsQuery.error instanceof Error + ? tenantsQuery.error.message + : "Unknown error"} +

+
+ )} + {tenantsQuery.isSuccess && ( +
+ {tenantsQuery.data.tenants.length === 0 ? ( +
+

+ No tenants found. +

+
+ ) : ( + tenantsQuery.data.tenants.map((tenant: UserTenant) => ( +
+
+
+

+ {tenant.tenant.name || "Unnamed Tenant"} +

+

+ ID: {tenant.tenantId} +

+
+
+ + {tenant.role} + + {tenant.default && ( + + default + + )} +
+
+
+

Status: {tenant.tenant.status}

+

+ Created:{" "} + {new Date( + tenant.tenant.createdAt, + ).toLocaleDateString()} +

+
+
+ )) + )} +
+ )} +
+ + {/* Ledgers Section */} +
+

Ledgers

+ {ledgersQuery.isLoading && ( +
+

+ Loading ledgers... +

+
+ )} + {ledgersQuery.isError && ( +
+

+ Error loading ledgers:{" "} + {ledgersQuery.error instanceof Error + ? ledgersQuery.error.message + : "Unknown error"} +

+
+ )} + {ledgersQuery.isSuccess && ( +
+ {ledgersQuery.data.ledgers.length === 0 ? ( +
+

+ No ledgers found. +

+
+ ) : ( + ledgersQuery.data.ledgers.map((ledger: LedgerUser) => ( +
+
+
+

+ {ledger.name} +

+

+ ID: {ledger.ledgerId} +

+

+ Tenant: {ledger.tenantId} +

+
+
+

Users: {ledger.users.length}

+

Max shares: {ledger.maxShares}

+
+
+
+

+ Created:{" "} + {new Date(ledger.createdAt).toLocaleDateString()} +

+
+ {ledger.users.length > 0 && ( +
+

+ User Access: +

+
+ {ledger.users.map((user, idx) => ( + + {user.name || user.userId} ({user.role}/ + {user.right}) + + ))} +
+
+ )} +
+ )) + )} +
+ )} +
+
+
+
+ ); +} diff --git a/vibes.diy/pkg/package.json b/vibes.diy/pkg/package.json index 13b5670a2..b1ccb27fd 100644 --- a/vibes.diy/pkg/package.json +++ b/vibes.diy/pkg/package.json @@ -32,6 +32,7 @@ "@clerk/clerk-react": "^5.57.0", "@cloudflare/workers-types": "^4.20251111.0", "@fireproof/core": "0.24.0", + "@fireproof/core-protocols-dashboard": "0.24.0", "@fireproof/core-runtime": "0.24.0", "@monaco-editor/react": "^4.7.0", "@radix-ui/react-slot": "^1.2.4", @@ -39,6 +40,7 @@ "@react-router/serve": "^7.9.6", "@shikijs/monaco": "^3.17.1", "@tailwindcss/typography": "^0.5.19", + "@tanstack/react-query": "^5.90.10", "@vibes.diy/hosting-base": "workspace:*", "@vibes.diy/prompts": "workspace:*", "@vibes.diy/use-vibes-base": "workspace:*", From 9bb21f551cbce7c26b69964fdc00c86830c45870 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Wed, 26 Nov 2025 09:10:34 -0800 Subject: [PATCH 02/30] debug log --- vibes.diy/pkg/app/routes/fireproof.tsx | 68 +++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index f422b4a6b..3cdd0cb16 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useMemo, useEffect } from "react"; import { useAuth } from "@clerk/clerk-react"; import { useQuery } from "@tanstack/react-query"; import { DashboardApi } from "@fireproof/core-protocols-dashboard"; @@ -24,13 +24,18 @@ export function meta() { } // Helper to convert Result monad to Promise for React Query -function wrapResultToPromise(pro: () => Promise>) { +function wrapResultToPromise(pro: () => Promise>, label: string) { return async (): Promise => { + console.log(`[Fireproof Dashboard] 🚀 Starting API call: ${label}`); const res = await pro(); if (res.isOk()) { - return res.Ok(); + const data = res.Ok(); + console.log(`[Fireproof Dashboard] ✅ Success for ${label}:`, data); + return data; } - throw res.Err(); + const error = res.Err(); + console.error(`[Fireproof Dashboard] ❌ Error for ${label}:`, error); + throw error; }; } @@ -39,11 +44,23 @@ export default function FireproofDashboard() { // Create DashboardApi instance with Clerk auth const api = useMemo(() => { + const apiUrl = VibesDiyEnv.CONNECT_API_URL(); + console.log( + "[Fireproof Dashboard] 🔧 Creating DashboardApi instance with URL:", + apiUrl, + ); return new DashboardApi({ - apiUrl: VibesDiyEnv.CONNECT_API_URL(), + apiUrl, fetch: window.fetch.bind(window), getToken: async () => { + console.log( + "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", + ); const token = await getToken({ template: "with-email" }); + console.log( + "[Fireproof Dashboard] 🎫 Token retrieved:", + token ? `${token.substring(0, 20)}...` : "null", + ); return { type: "clerk" as const, token: token || "", @@ -55,17 +72,54 @@ export default function FireproofDashboard() { // Query to list all tenants for the logged-in user const tenantsQuery = useQuery({ queryKey: ["listTenantsByUser"], - queryFn: wrapResultToPromise(() => api.listTenantsByUser({})), + queryFn: wrapResultToPromise( + () => api.listTenantsByUser({}), + "listTenantsByUser", + ), enabled: isLoaded && isSignedIn, }); // Query to list all ledgers for the logged-in user const ledgersQuery = useQuery({ queryKey: ["listLedgersByUser"], - queryFn: wrapResultToPromise(() => api.listLedgersByUser({})), + queryFn: wrapResultToPromise( + () => api.listLedgersByUser({}), + "listLedgersByUser", + ), enabled: isLoaded && isSignedIn, }); + // Log authentication and query states + useEffect(() => { + console.log("[Fireproof Dashboard] 📊 State Update:", { + isLoaded, + isSignedIn, + tenantsQuery: { + isLoading: tenantsQuery.isLoading, + isError: tenantsQuery.isError, + isSuccess: tenantsQuery.isSuccess, + dataCount: tenantsQuery.data?.tenants.length, + }, + ledgersQuery: { + isLoading: ledgersQuery.isLoading, + isError: ledgersQuery.isError, + isSuccess: ledgersQuery.isSuccess, + dataCount: ledgersQuery.data?.ledgers.length, + }, + }); + }, [ + isLoaded, + isSignedIn, + tenantsQuery.isLoading, + tenantsQuery.isError, + tenantsQuery.isSuccess, + tenantsQuery.data, + ledgersQuery.isLoading, + ledgersQuery.isError, + ledgersQuery.isSuccess, + ledgersQuery.data, + ]); + // Not authenticated view if (isLoaded && !isSignedIn) { return ( From 41e622848b6d4f18af87b4b19dc45a27aa9ac53f Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 17:49:13 -0800 Subject: [PATCH 03/30] feat: Improve error message for JWKS token verification failures --- vibes.diy/pkg/app/routes/fireproof.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 3cdd0cb16..62052c779 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -35,6 +35,19 @@ function wrapResultToPromise(pro: () => Promise>, label: string) { } const error = res.Err(); console.error(`[Fireproof Dashboard] ❌ Error for ${label}:`, error); + + // Enhance error message for JWKS verification failure + if ( + error instanceof Error && + error.message.includes("No well-known JWKS URL could verify the token") + ) { + const improvedError = new Error( + `Authentication failed: The Fireproof backend could not verify your token. This usually happens when using a development Clerk instance with the production Fireproof backend. Please set VITE_CONNECT_API_URL to a compatible development backend.`, + ); + improvedError.stack = error.stack; + throw improvedError; + } + throw error; }; } From efcd5f4bfcaa3c5ba23f1ba27f1b730d9109bf34 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 17:50:59 -0800 Subject: [PATCH 04/30] feat: Parse and check token expiry before use --- vibes.diy/pkg/app/routes/fireproof.tsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 62052c779..2196e9a62 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -9,6 +9,7 @@ import type { LedgerUser, } from "@fireproof/core-protocols-dashboard"; import type { Result } from "@adviser/cement"; +import { decodeJwt } from "jose"; import SimpleAppLayout from "../components/SimpleAppLayout.js"; import { HomeIcon } from "../components/SessionSidebar/HomeIcon.js"; import { VibesDiyEnv } from "../config/env.js"; @@ -70,6 +71,31 @@ export default function FireproofDashboard() { "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", ); const token = await getToken({ template: "with-email" }); + + if (token) { + try { + const claims = decodeJwt(token); + const now = Date.now() / 1000; + const exp = claims.exp || 0; + const ttl = exp - now; + + console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Claims:", { + iss: claims.iss, + exp: claims.exp, + iat: claims.iat, + ttl: ttl.toFixed(2) + "s", + }); + + if (exp < now) { + console.error( + "[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", + ); + } + } catch (e) { + console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); + } + } + console.log( "[Fireproof Dashboard] 🎫 Token retrieved:", token ? `${token.substring(0, 20)}...` : "null", From 6d7ab15a1b4423355cf8596261556a529742c731 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 17:54:04 -0800 Subject: [PATCH 05/30] feat: Warn on token issuer mismatch --- vibes.diy/pkg/app/routes/fireproof.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 2196e9a62..b0002898d 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -91,6 +91,15 @@ export default function FireproofDashboard() { "[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", ); } + + // Check if issuer matches the expected production issuer + // This is just a hint for debugging, not a hard enforcement + const PROD_ISSUER = "https://clerk.fireproof.direct"; + if (claims.iss && claims.iss !== PROD_ISSUER) { + console.warn( + `[Fireproof Dashboard] ⚠️ Token issuer mismatch! \nExpected: ${PROD_ISSUER}\nReceived: ${claims.iss}\n\nThis is likely why the request is failing. The Fireproof backend is expecting a token from the production Clerk instance.`, + ); + } } catch (e) { console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); } From d55e2a6669381a2a19a293642f96ab0026cd95a4 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 18:30:09 -0800 Subject: [PATCH 06/30] feat: Clarify backend config issue in warning --- vibes.diy/pkg/app/routes/fireproof.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index b0002898d..288915a7e 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -97,7 +97,7 @@ export default function FireproofDashboard() { const PROD_ISSUER = "https://clerk.fireproof.direct"; if (claims.iss && claims.iss !== PROD_ISSUER) { console.warn( - `[Fireproof Dashboard] ⚠️ Token issuer mismatch! \nExpected: ${PROD_ISSUER}\nReceived: ${claims.iss}\n\nThis is likely why the request is failing. The Fireproof backend is expecting a token from the production Clerk instance.`, + `[Fireproof Dashboard] ⚠️ Token issuer mismatch!\nExpected: ${PROD_ISSUER}\nReceived: ${claims.iss}\n\nThe backend rejected this token with "token-invalid-signature" because it defaulted to verifying against the production issuer's JWKS.\n\nThis confirms that while the token is valid, the CURRENT production backend is NOT correctly respecting the 'iss' claim to select the right verification keys for this development environment.\n\nTo fix this, you must either:\n1. Set VITE_CONNECT_API_URL to a dev/staging backend that supports this issuer.\n2. Use a production Clerk account/token.`, ); } } catch (e) { From 2d42fef291548e5c6243dd1302de11987f6a19c3 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 18:37:18 -0800 Subject: [PATCH 07/30] feat: Update auth mismatch warning with backend type limitation --- vibes.diy/pkg/app/routes/fireproof.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 288915a7e..70178628f 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -97,7 +97,7 @@ export default function FireproofDashboard() { const PROD_ISSUER = "https://clerk.fireproof.direct"; if (claims.iss && claims.iss !== PROD_ISSUER) { console.warn( - `[Fireproof Dashboard] ⚠️ Token issuer mismatch!\nExpected: ${PROD_ISSUER}\nReceived: ${claims.iss}\n\nThe backend rejected this token with "token-invalid-signature" because it defaulted to verifying against the production issuer's JWKS.\n\nThis confirms that while the token is valid, the CURRENT production backend is NOT correctly respecting the 'iss' claim to select the right verification keys for this development environment.\n\nTo fix this, you must either:\n1. Set VITE_CONNECT_API_URL to a dev/staging backend that supports this issuer.\n2. Use a production Clerk account/token.`, + `[Fireproof Dashboard] ⚠️ Authentication Type Mismatch!\nExpected Auth Type: fp-cloud-jwt\nReceived Auth Type: clerk (from issuer ${claims.iss})\n\nEven though the token from ${claims.iss} is valid, the Fireproof backend is currently configured to ONLY accept authentication of type 'fp-cloud-jwt'.\n\nThis is a backend-side limitation. Please contact the Fireproof team to enable support for Clerk-issued tokens or provide a backend URL (via VITE_CONNECT_API_URL) that is configured to accept Clerk tokens.`, ); } } catch (e) { From 107238ce856beb576e05ee08e70ffed1681c66d6 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 19:09:43 -0800 Subject: [PATCH 08/30] fix: Remove misleading auth type mismatch warning --- vibes.diy/pkg/app/routes/fireproof.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 70178628f..22a5bf107 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -92,14 +92,7 @@ export default function FireproofDashboard() { ); } - // Check if issuer matches the expected production issuer - // This is just a hint for debugging, not a hard enforcement - const PROD_ISSUER = "https://clerk.fireproof.direct"; - if (claims.iss && claims.iss !== PROD_ISSUER) { - console.warn( - `[Fireproof Dashboard] ⚠️ Authentication Type Mismatch!\nExpected Auth Type: fp-cloud-jwt\nReceived Auth Type: clerk (from issuer ${claims.iss})\n\nEven though the token from ${claims.iss} is valid, the Fireproof backend is currently configured to ONLY accept authentication of type 'fp-cloud-jwt'.\n\nThis is a backend-side limitation. Please contact the Fireproof team to enable support for Clerk-issued tokens or provide a backend URL (via VITE_CONNECT_API_URL) that is configured to accept Clerk tokens.`, - ); - } + } catch (e) { console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); } From d28f13f465ea83d5904f5d0d3f3c5574deca8f4e Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 19:11:18 -0800 Subject: [PATCH 09/30] feat: Implement fp-cloud-jwt token exchange for DashboardApi --- vibes.diy/pkg/app/routes/fireproof.tsx | 114 +++++++++++++++---------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 22a5bf107..e6010e7c8 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useEffect } from "react"; +import React, { useMemo, useEffect, useState } from "react"; import { useAuth } from "@clerk/clerk-react"; import { useQuery } from "@tanstack/react-query"; import { DashboardApi } from "@fireproof/core-protocols-dashboard"; @@ -55,6 +55,28 @@ function wrapResultToPromise(pro: () => Promise>, label: string) { export default function FireproofDashboard() { const { isSignedIn, isLoaded, getToken } = useAuth(); + const [fpCloudToken, setFpCloudToken] = useState(null); + + useEffect(() => { + if (!isLoaded || !isSignedIn || fpCloudToken) return; + + const fetchFpCloudToken = async () => { + console.log("[Fireproof Dashboard] 🚀 Attempting to get fp-cloud-jwt..."); + try { + const result = await api.getCloudSessionToken({}); + if (result.isOk()) { + setFpCloudToken(result.Ok().token); + console.log("[Fireproof Dashboard] ✅ Successfully retrieved fp-cloud-jwt."); + } else { + console.error("[Fireproof Dashboard] ❌ Error getting fp-cloud-jwt:", result.Err()); + } + } catch (e) { + console.error("[Fireproof Dashboard] ❌ Exception getting fp-cloud-jwt:", e); + } + }; + + fetchFpCloudToken(); + }, [isLoaded, isSignedIn, fpCloudToken, api]); // Create DashboardApi instance with Clerk auth const api = useMemo(() => { @@ -66,49 +88,55 @@ export default function FireproofDashboard() { return new DashboardApi({ apiUrl, fetch: window.fetch.bind(window), - getToken: async () => { - console.log( - "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", - ); - const token = await getToken({ template: "with-email" }); - - if (token) { - try { - const claims = decodeJwt(token); - const now = Date.now() / 1000; - const exp = claims.exp || 0; - const ttl = exp - now; - - console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Claims:", { - iss: claims.iss, - exp: claims.exp, - iat: claims.iat, - ttl: ttl.toFixed(2) + "s", - }); - - if (exp < now) { - console.error( - "[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", + getToken: async () => { + if (fpCloudToken) { + console.log("[Fireproof Dashboard] 🔑 Using existing fp-cloud-jwt."); + return { + type: "fp-cloud-jwt" as const, + token: fpCloudToken, + }; + } + + console.log( + "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", ); - } - - - } catch (e) { - console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); - } - } - - console.log( - "[Fireproof Dashboard] 🎫 Token retrieved:", - token ? `${token.substring(0, 20)}...` : "null", - ); - return { - type: "clerk" as const, - token: token || "", - }; - }, - }); - }, [getToken]); + const token = await getToken({ template: "with-email" }); + + if (token) { + try { + const claims = decodeJwt(token); + const now = Date.now() / 1000; + const exp = claims.exp || 0; + const ttl = exp - now; + + console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Claims:", { + iss: claims.iss, + exp: claims.exp, + iat: claims.iat, + ttl: ttl.toFixed(2) + "s", + }); + + if (exp < now) { + console.error( + "[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", + ); + } + } catch (e) { + console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); + } + } + + console.log( + "[Fireproof Dashboard] 🎫 Token retrieved:", + token ? `${token.substring(0, 20)}...` : "null", + ); + return { + type: "clerk" as const, + token: token || "", + }; + }, + }); + }, [getToken, fpCloudToken]); // Query to list all tenants for the logged-in user const tenantsQuery = useQuery({ From 2de890c792b36551bf5583e55885069f5fcf1e55 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Sun, 30 Nov 2025 19:16:43 -0800 Subject: [PATCH 10/30] fix: Correct DashboardApi initialization and token type for fp-cloud-jwt --- vibes.diy/pkg/app/routes/fireproof.tsx | 143 +++++++++++++------------ 1 file changed, 76 insertions(+), 67 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index e6010e7c8..975e9ef60 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -55,9 +55,70 @@ function wrapResultToPromise(pro: () => Promise>, label: string) { export default function FireproofDashboard() { const { isSignedIn, isLoaded, getToken } = useAuth(); - const [fpCloudToken, setFpCloudToken] = useState(null); - useEffect(() => { + const [fpCloudToken, setFpCloudToken] = useState(null); // Moved useState here + + // Create DashboardApi instance with Clerk auth + const api = useMemo(() => { + const apiUrl = VibesDiyEnv.CONNECT_API_URL(); + console.log( + "[Fireproof Dashboard] 🔧 Creating DashboardApi instance with URL:", + apiUrl, + ); + return new DashboardApi({ + apiUrl, + fetch: window.fetch.bind(window), + getToken: async () => { + if (fpCloudToken) { + console.log("[Fireproof Dashboard] 🔑 Using existing fp-cloud-jwt."); + return { + type: "ucan" as const, // Corrected type here + token: fpCloudToken, + }; + } + + console.log( + "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", + ); + const token = await getToken({ template: "with-email" }); + + if (token) { + try { + const claims = decodeJwt(token); + const now = Date.now() / 1000; + const exp = claims.exp || 0; + const ttl = exp - now; + + console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Claims:", { + iss: claims.iss, + exp: claims.exp, + iat: claims.iat, + ttl: ttl.toFixed(2) + "s", + }); + + if (exp < now) { + console.error( + "/[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", + ); + } + } catch (e) { + console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); + } + } + + console.log( + "/[Fireproof Dashboard] 🎫 Token retrieved:", + token ? `${token.substring(0, 20)}...` : "null", + ); + return { + type: "clerk" as const, + token: token || "", + }; + }, + }); + }, [getToken, fpCloudToken]); + + useEffect(() => { // Moved useEffect here if (!isLoaded || !isSignedIn || fpCloudToken) return; const fetchFpCloudToken = async () => { @@ -78,65 +139,6 @@ export default function FireproofDashboard() { fetchFpCloudToken(); }, [isLoaded, isSignedIn, fpCloudToken, api]); - // Create DashboardApi instance with Clerk auth - const api = useMemo(() => { - const apiUrl = VibesDiyEnv.CONNECT_API_URL(); - console.log( - "[Fireproof Dashboard] 🔧 Creating DashboardApi instance with URL:", - apiUrl, - ); - return new DashboardApi({ - apiUrl, - fetch: window.fetch.bind(window), - getToken: async () => { - if (fpCloudToken) { - console.log("[Fireproof Dashboard] 🔑 Using existing fp-cloud-jwt."); - return { - type: "fp-cloud-jwt" as const, - token: fpCloudToken, - }; - } - - console.log( - "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", - ); - const token = await getToken({ template: "with-email" }); - - if (token) { - try { - const claims = decodeJwt(token); - const now = Date.now() / 1000; - const exp = claims.exp || 0; - const ttl = exp - now; - - console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Claims:", { - iss: claims.iss, - exp: claims.exp, - iat: claims.iat, - ttl: ttl.toFixed(2) + "s", - }); - - if (exp < now) { - console.error( - "[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", - ); - } - } catch (e) { - console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); - } - } - - console.log( - "[Fireproof Dashboard] 🎫 Token retrieved:", - token ? `${token.substring(0, 20)}...` : "null", - ); - return { - type: "clerk" as const, - token: token || "", - }; - }, - }); - }, [getToken, fpCloudToken]); // Query to list all tenants for the logged-in user const tenantsQuery = useQuery({ @@ -216,7 +218,6 @@ export default function FireproofDashboard() { ); } - return ( -
+

Users: {ledger.users.length}

Max shares: {ledger.maxShares}

-
+

Created:{" "} {new Date(ledger.createdAt).toLocaleDateString()}

{ledger.users.length > 0 && ( -
-

+

+

User Access:

@@ -396,4 +405,4 @@ export default function FireproofDashboard() {
); -} +} \ No newline at end of file From b2f74cc3152ee0421653e180dbd6b96aefada71a Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 05:28:49 -0800 Subject: [PATCH 11/30] fix: Wait for fp-cloud-jwt before fetching dashboard data --- vibes.diy/pkg/app/routes/fireproof.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 975e9ef60..90a277d54 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -147,7 +147,7 @@ export default function FireproofDashboard() { () => api.listTenantsByUser({}), "listTenantsByUser", ), - enabled: isLoaded && isSignedIn, + enabled: isLoaded && isSignedIn && !!fpCloudToken, }); // Query to list all ledgers for the logged-in user @@ -157,7 +157,7 @@ export default function FireproofDashboard() { () => api.listLedgersByUser({}), "listLedgersByUser", ), - enabled: isLoaded && isSignedIn, + enabled: isLoaded && isSignedIn && !!fpCloudToken, }); // Log authentication and query states From 0a2954319ed3c25fd1c40e135fb41b0af0893942 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 05:30:37 -0800 Subject: [PATCH 12/30] feat: Log token header and audience for debugging --- vibes.diy/pkg/app/routes/fireproof.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 90a277d54..c73422857 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -9,7 +9,7 @@ import type { LedgerUser, } from "@fireproof/core-protocols-dashboard"; import type { Result } from "@adviser/cement"; -import { decodeJwt } from "jose"; +import { decodeJwt, decodeProtectedHeader } from "jose"; import SimpleAppLayout from "../components/SimpleAppLayout.js"; import { HomeIcon } from "../components/SessionSidebar/HomeIcon.js"; import { VibesDiyEnv } from "../config/env.js"; @@ -85,15 +85,20 @@ export default function FireproofDashboard() { if (token) { try { const claims = decodeJwt(token); + const header = decodeProtectedHeader(token); const now = Date.now() / 1000; const exp = claims.exp || 0; const ttl = exp - now; - console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Claims:", { - iss: claims.iss, - exp: claims.exp, - iat: claims.iat, - ttl: ttl.toFixed(2) + "s", + console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Details:", { + header, + payload: { + iss: claims.iss, + aud: claims.aud, + exp: claims.exp, + iat: claims.iat, + ttl: ttl.toFixed(2) + "s", + }, }); if (exp < now) { From 282a1825963bd145782cdb3afb0a109580419a7b Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 05:49:06 -0800 Subject: [PATCH 13/30] feat: Add explicit KID check for debugging --- vibes.diy/pkg/app/routes/fireproof.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index c73422857..42c22e44b 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -86,6 +86,19 @@ export default function FireproofDashboard() { try { const claims = decodeJwt(token); const header = decodeProtectedHeader(token); + + // Check KID match for debugging + const expectedKid = "ins_35qNS5Jwyc7z4aJRBIS7o205yzb"; + if (header.kid === expectedKid) { + console.log("[Fireproof Dashboard] ✅ KID MATCHES expected dev key:", header.kid); + } else { + console.warn("[Fireproof Dashboard] ⚠️ KID MISMATCH:", { + tokenKid: header.kid, + expectedKid, + note: "This might be why verification fails if the backend is using the 'expected' key." + }); + } + const now = Date.now() / 1000; const exp = claims.exp || 0; const ttl = exp - now; From f5693b146fe40e26615945aced617e31c8434c58 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 05:51:02 -0800 Subject: [PATCH 14/30] docs: Add token auth exchange analysis for debugging --- token-auth-and-exchange.md | 81 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 token-auth-and-exchange.md diff --git a/token-auth-and-exchange.md b/token-auth-and-exchange.md new file mode 100644 index 000000000..9f9f2fd2a --- /dev/null +++ b/token-auth-and-exchange.md @@ -0,0 +1,81 @@ +# Token Auth & Exchange Flow Analysis + +This document describes the current observed authentication flow and the specific failure point encountered when integrating the `vibes.diy` development environment with the Fireproof Connect backend. + +## The Goal +Authenticate a user on the `vibes.diy` local development environment using a **Clerk Development Instance** (`sincere-cheetah-30...`) and successfully exchange this token for a Fireproof Cloud token (`fp-cloud-jwt`) via the production Fireproof backend (`https://connect.fireproof.direct/api`). + +## The Flow + +1. **Client Authentication (Success)** + * The `vibes.diy` app uses `@clerk/clerk-react` to sign the user in. + * **Result:** A valid JWT is obtained. + * **Issuer (`iss`):** `https://sincere-cheetah-30.clerk.accounts.dev` + * **Key ID (`kid`):** `ins_35qNS5Jwyc7z4aJRBIS7o205yzb` + * **Algorithm:** `RS256` + * **Audience (`aud`):** `undefined` (No audience claim present). + +2. **Token Verification / Exchange Request (Failure)** + * The client calls `DashboardApi.getCloudSessionToken({})`. + * **Payload:** `{ auth: { type: "clerk", token: "..." } }` + * **Endpoint:** `PUT https://connect.fireproof.direct/api` + +3. **Backend Verification (Error)** + * The backend receives the request and attempts to verify the token. + * **Backend Configuration:** The backend is configured with `CLERK_PUB_JWT_URL` containing three issuers, including the development one: + * `https://clerk.fireproof.direct` + * `https://clerk.vibes.diy` + * `https://sincere-cheetah-30.clerk.accounts.dev` + * **Response:** HTTP 500 + * **Error Message:** + ```json + { + "type": "error", + "message": "No well-known JWKS URL could verify the token:\n[ + { + "type": "error", + "error": { + "reason": "token-invalid-signature" + }, + "url": "https://clerk.fireproof.direct/.well-known/jwks.json" + }, + { + "type": "error", + "error": { + "reason": "token-invalid-signature" + }, + "url": "https://clerk.vibes.diy/.well-known/jwks.json" + }, + { + "type": "error", + "error": { + "reason": "token-invalid-signature" + }, + "url": "https://sincere-cheetah-30.clerk.accounts.dev/.well-known/jwks.json" + } +]" + } + ``` + +## Diagnosis Findings + +* **✅ Issuer Match:** The client sends a token from `sincere-cheetah-30`. The backend *is* attempting to verify against the corresponding JWKS URL. +* **✅ Key ID Match:** The client token has `kid: 'ins_35qNS5Jwyc7z4aJRBIS7o205yzb'`. The public JWKS at `https://sincere-cheetah-30.clerk.accounts.dev/.well-known/jwks.json` currently contains this exact key. +* **❌ Signature Verification Failure:** Despite the correct issuer and key ID, the backend reports `token-invalid-signature`. + +## Potential Root Causes for Review + +1. **Audience (`aud`) Validation:** + * The client token has **no `aud` claim**. + * Does the backend's `@hono/clerk-auth` middleware (or underlying `verifyToken` function) enforce a default audience check? If so, the lack of an audience would cause verification to fail even if the signature is valid. + +2. **Stale JWKS Cache:** + * Could the backend be caching an older version of the JWKS for `sincere-cheetah-30` that does not yet contain the key `ins_35qNS5Jwyc7z4aJRBIS7o205yzb`? + * *Note: This is less likely if this is a stable dev instance, but possible if keys were recently rotated.* + +3. **Crypto/Environment Mismatch:** + * Is the backend environment (e.g., Cloudflare Workers) correctly handling the RS256 signature verification for this specific key? + +## Request for Engineering Team + +Please verify if the Fireproof backend enforces an **Audience (`aud`) claim** on Clerk tokens. If so, what audience value is expected? We may need to configure our Clerk development instance to include this audience in its issued tokens. From 4978636e659acb68c22ae37556e102a8222bd774 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 06:38:33 -0800 Subject: [PATCH 15/30] feat: Enhance logging to indicate query enablement --- vibes.diy/pkg/app/routes/fireproof.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 42c22e44b..59e6105d3 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -145,7 +145,7 @@ export default function FireproofDashboard() { const result = await api.getCloudSessionToken({}); if (result.isOk()) { setFpCloudToken(result.Ok().token); - console.log("[Fireproof Dashboard] ✅ Successfully retrieved fp-cloud-jwt."); + console.log("[Fireproof Dashboard] ✅ Successfully retrieved fp-cloud-jwt. Enabling data queries."); } else { console.error("[Fireproof Dashboard] ❌ Error getting fp-cloud-jwt:", result.Err()); } From ef01ee300950d7cfaae5f4249928674788cb9570 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 10:25:07 -0800 Subject: [PATCH 16/30] chore: Apply formatting changes from pnpm check --- token-auth-and-exchange.md | 52 +++++++++++++------------- vibes.diy/pkg/app/routes/fireproof.tsx | 49 ++++++++++++------------ 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/token-auth-and-exchange.md b/token-auth-and-exchange.md index 9f9f2fd2a..ee9503a1c 100644 --- a/token-auth-and-exchange.md +++ b/token-auth-and-exchange.md @@ -3,32 +3,32 @@ This document describes the current observed authentication flow and the specific failure point encountered when integrating the `vibes.diy` development environment with the Fireproof Connect backend. ## The Goal + Authenticate a user on the `vibes.diy` local development environment using a **Clerk Development Instance** (`sincere-cheetah-30...`) and successfully exchange this token for a Fireproof Cloud token (`fp-cloud-jwt`) via the production Fireproof backend (`https://connect.fireproof.direct/api`). ## The Flow 1. **Client Authentication (Success)** - * The `vibes.diy` app uses `@clerk/clerk-react` to sign the user in. - * **Result:** A valid JWT is obtained. - * **Issuer (`iss`):** `https://sincere-cheetah-30.clerk.accounts.dev` - * **Key ID (`kid`):** `ins_35qNS5Jwyc7z4aJRBIS7o205yzb` - * **Algorithm:** `RS256` - * **Audience (`aud`):** `undefined` (No audience claim present). + - The `vibes.diy` app uses `@clerk/clerk-react` to sign the user in. + - **Result:** A valid JWT is obtained. + - **Issuer (`iss`):** `https://sincere-cheetah-30.clerk.accounts.dev` + - **Key ID (`kid`):** `ins_35qNS5Jwyc7z4aJRBIS7o205yzb` + - **Algorithm:** `RS256` + - **Audience (`aud`):** `undefined` (No audience claim present). 2. **Token Verification / Exchange Request (Failure)** - * The client calls `DashboardApi.getCloudSessionToken({})`. - * **Payload:** `{ auth: { type: "clerk", token: "..." } }` - * **Endpoint:** `PUT https://connect.fireproof.direct/api` + - The client calls `DashboardApi.getCloudSessionToken({})`. + - **Payload:** `{ auth: { type: "clerk", token: "..." } }` + - **Endpoint:** `PUT https://connect.fireproof.direct/api` 3. **Backend Verification (Error)** - * The backend receives the request and attempts to verify the token. - * **Backend Configuration:** The backend is configured with `CLERK_PUB_JWT_URL` containing three issuers, including the development one: - * `https://clerk.fireproof.direct` - * `https://clerk.vibes.diy` - * `https://sincere-cheetah-30.clerk.accounts.dev` - * **Response:** HTTP 500 - * **Error Message:** - ```json + _ The backend receives the request and attempts to verify the token. + _ **Backend Configuration:** The backend is configured with `CLERK_PUB_JWT_URL` containing three issuers, including the development one: + _ `https://clerk.fireproof.direct` + _ `https://clerk.vibes.diy` + _ `https://sincere-cheetah-30.clerk.accounts.dev` + _ **Response:** HTTP 500 \* **Error Message:** + `json { "type": "error", "message": "No well-known JWKS URL could verify the token:\n[ @@ -55,26 +55,26 @@ Authenticate a user on the `vibes.diy` local development environment using a **C } ]" } - ``` + ` ## Diagnosis Findings -* **✅ Issuer Match:** The client sends a token from `sincere-cheetah-30`. The backend *is* attempting to verify against the corresponding JWKS URL. -* **✅ Key ID Match:** The client token has `kid: 'ins_35qNS5Jwyc7z4aJRBIS7o205yzb'`. The public JWKS at `https://sincere-cheetah-30.clerk.accounts.dev/.well-known/jwks.json` currently contains this exact key. -* **❌ Signature Verification Failure:** Despite the correct issuer and key ID, the backend reports `token-invalid-signature`. +- **✅ Issuer Match:** The client sends a token from `sincere-cheetah-30`. The backend _is_ attempting to verify against the corresponding JWKS URL. +- **✅ Key ID Match:** The client token has `kid: 'ins_35qNS5Jwyc7z4aJRBIS7o205yzb'`. The public JWKS at `https://sincere-cheetah-30.clerk.accounts.dev/.well-known/jwks.json` currently contains this exact key. +- **❌ Signature Verification Failure:** Despite the correct issuer and key ID, the backend reports `token-invalid-signature`. ## Potential Root Causes for Review 1. **Audience (`aud`) Validation:** - * The client token has **no `aud` claim**. - * Does the backend's `@hono/clerk-auth` middleware (or underlying `verifyToken` function) enforce a default audience check? If so, the lack of an audience would cause verification to fail even if the signature is valid. + - The client token has **no `aud` claim**. + - Does the backend's `@hono/clerk-auth` middleware (or underlying `verifyToken` function) enforce a default audience check? If so, the lack of an audience would cause verification to fail even if the signature is valid. 2. **Stale JWKS Cache:** - * Could the backend be caching an older version of the JWKS for `sincere-cheetah-30` that does not yet contain the key `ins_35qNS5Jwyc7z4aJRBIS7o205yzb`? - * *Note: This is less likely if this is a stable dev instance, but possible if keys were recently rotated.* + - Could the backend be caching an older version of the JWKS for `sincere-cheetah-30` that does not yet contain the key `ins_35qNS5Jwyc7z4aJRBIS7o205yzb`? + - _Note: This is less likely if this is a stable dev instance, but possible if keys were recently rotated._ 3. **Crypto/Environment Mismatch:** - * Is the backend environment (e.g., Cloudflare Workers) correctly handling the RS256 signature verification for this specific key? + - Is the backend environment (e.g., Cloudflare Workers) correctly handling the RS256 signature verification for this specific key? ## Request for Engineering Team diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 59e6105d3..51aa6a6dd 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -86,16 +86,19 @@ export default function FireproofDashboard() { try { const claims = decodeJwt(token); const header = decodeProtectedHeader(token); - + // Check KID match for debugging const expectedKid = "ins_35qNS5Jwyc7z4aJRBIS7o205yzb"; if (header.kid === expectedKid) { - console.log("[Fireproof Dashboard] ✅ KID MATCHES expected dev key:", header.kid); + console.log( + "[Fireproof Dashboard] ✅ KID MATCHES expected dev key:", + header.kid, + ); } else { - console.warn("[Fireproof Dashboard] ⚠️ KID MISMATCH:", { - tokenKid: header.kid, + console.warn("[Fireproof Dashboard] ⚠️ KID MISMATCH:", { + tokenKid: header.kid, expectedKid, - note: "This might be why verification fails if the backend is using the 'expected' key." + note: "This might be why verification fails if the backend is using the 'expected' key.", }); } @@ -136,7 +139,8 @@ export default function FireproofDashboard() { }); }, [getToken, fpCloudToken]); - useEffect(() => { // Moved useEffect here + useEffect(() => { + // Moved useEffect here if (!isLoaded || !isSignedIn || fpCloudToken) return; const fetchFpCloudToken = async () => { @@ -145,19 +149,26 @@ export default function FireproofDashboard() { const result = await api.getCloudSessionToken({}); if (result.isOk()) { setFpCloudToken(result.Ok().token); - console.log("[Fireproof Dashboard] ✅ Successfully retrieved fp-cloud-jwt. Enabling data queries."); + console.log( + "[Fireproof Dashboard] ✅ Successfully retrieved fp-cloud-jwt. Enabling data queries.", + ); } else { - console.error("[Fireproof Dashboard] ❌ Error getting fp-cloud-jwt:", result.Err()); + console.error( + "[Fireproof Dashboard] ❌ Error getting fp-cloud-jwt:", + result.Err(), + ); } } catch (e) { - console.error("[Fireproof Dashboard] ❌ Exception getting fp-cloud-jwt:", e); + console.error( + "[Fireproof Dashboard] ❌ Exception getting fp-cloud-jwt:", + e, + ); } }; fetchFpCloudToken(); }, [isLoaded, isSignedIn, fpCloudToken, api]); - // Query to list all tenants for the logged-in user const tenantsQuery = useQuery({ queryKey: ["listTenantsByUser"], @@ -376,28 +387,20 @@ export default function FireproofDashboard() { Tenant: {ledger.tenantId}

-
+

Users: {ledger.users.length}

Max shares: {ledger.maxShares}

-
+

Created:{" "} {new Date(ledger.createdAt).toLocaleDateString()}

{ledger.users.length > 0 && ( -
-

+

+

User Access:

@@ -423,4 +426,4 @@ export default function FireproofDashboard() {
); -} \ No newline at end of file +} From 5947147f8f59329afc97b6c7c3ffdf48c75cbe3d Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 11:55:46 -0800 Subject: [PATCH 17/30] refactor: Revert auth exchange complexity and use Clerk token directly --- vibes.diy/pkg/app/routes/fireproof.tsx | 159 ++----------------------- 1 file changed, 7 insertions(+), 152 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 51aa6a6dd..e91a8a287 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useEffect, useState } from "react"; +import React, { useMemo, useEffect } from "react"; import { useAuth } from "@clerk/clerk-react"; import { useQuery } from "@tanstack/react-query"; import { DashboardApi } from "@fireproof/core-protocols-dashboard"; @@ -9,7 +9,6 @@ import type { LedgerUser, } from "@fireproof/core-protocols-dashboard"; import type { Result } from "@adviser/cement"; -import { decodeJwt, decodeProtectedHeader } from "jose"; import SimpleAppLayout from "../components/SimpleAppLayout.js"; import { HomeIcon } from "../components/SessionSidebar/HomeIcon.js"; import { VibesDiyEnv } from "../config/env.js"; @@ -27,28 +26,12 @@ export function meta() { // Helper to convert Result monad to Promise for React Query function wrapResultToPromise(pro: () => Promise>, label: string) { return async (): Promise => { - console.log(`[Fireproof Dashboard] 🚀 Starting API call: ${label}`); const res = await pro(); if (res.isOk()) { - const data = res.Ok(); - console.log(`[Fireproof Dashboard] ✅ Success for ${label}:`, data); - return data; + return res.Ok(); } const error = res.Err(); console.error(`[Fireproof Dashboard] ❌ Error for ${label}:`, error); - - // Enhance error message for JWKS verification failure - if ( - error instanceof Error && - error.message.includes("No well-known JWKS URL could verify the token") - ) { - const improvedError = new Error( - `Authentication failed: The Fireproof backend could not verify your token. This usually happens when using a development Clerk instance with the production Fireproof backend. Please set VITE_CONNECT_API_URL to a compatible development backend.`, - ); - improvedError.stack = error.stack; - throw improvedError; - } - throw error; }; } @@ -56,118 +39,21 @@ function wrapResultToPromise(pro: () => Promise>, label: string) { export default function FireproofDashboard() { const { isSignedIn, isLoaded, getToken } = useAuth(); - const [fpCloudToken, setFpCloudToken] = useState(null); // Moved useState here - // Create DashboardApi instance with Clerk auth const api = useMemo(() => { const apiUrl = VibesDiyEnv.CONNECT_API_URL(); - console.log( - "[Fireproof Dashboard] 🔧 Creating DashboardApi instance with URL:", - apiUrl, - ); return new DashboardApi({ apiUrl, - fetch: window.fetch.bind(window), + // fetch: window.fetch.bind(window), // Optional per comments getToken: async () => { - if (fpCloudToken) { - console.log("[Fireproof Dashboard] 🔑 Using existing fp-cloud-jwt."); - return { - type: "ucan" as const, // Corrected type here - token: fpCloudToken, - }; - } - - console.log( - "[Fireproof Dashboard] 🔑 Getting Clerk token with template: with-email", - ); const token = await getToken({ template: "with-email" }); - - if (token) { - try { - const claims = decodeJwt(token); - const header = decodeProtectedHeader(token); - - // Check KID match for debugging - const expectedKid = "ins_35qNS5Jwyc7z4aJRBIS7o205yzb"; - if (header.kid === expectedKid) { - console.log( - "[Fireproof Dashboard] ✅ KID MATCHES expected dev key:", - header.kid, - ); - } else { - console.warn("[Fireproof Dashboard] ⚠️ KID MISMATCH:", { - tokenKid: header.kid, - expectedKid, - note: "This might be why verification fails if the backend is using the 'expected' key.", - }); - } - - const now = Date.now() / 1000; - const exp = claims.exp || 0; - const ttl = exp - now; - - console.log("[Fireproof Dashboard] 🕵️‍♀️ Token Details:", { - header, - payload: { - iss: claims.iss, - aud: claims.aud, - exp: claims.exp, - iat: claims.iat, - ttl: ttl.toFixed(2) + "s", - }, - }); - - if (exp < now) { - console.error( - "/[Fireproof Dashboard] ❌ Token is EXPIRED! Client clock may be wrong or Clerk returned old token.", - ); - } - } catch (e) { - console.error("[Fireproof Dashboard] ⚠️ Failed to parse token:", e); - } - } - - console.log( - "/[Fireproof Dashboard] 🎫 Token retrieved:", - token ? `${token.substring(0, 20)}...` : "null", - ); return { - type: "clerk" as const, + type: "clerk", token: token || "", }; }, }); - }, [getToken, fpCloudToken]); - - useEffect(() => { - // Moved useEffect here - if (!isLoaded || !isSignedIn || fpCloudToken) return; - - const fetchFpCloudToken = async () => { - console.log("[Fireproof Dashboard] 🚀 Attempting to get fp-cloud-jwt..."); - try { - const result = await api.getCloudSessionToken({}); - if (result.isOk()) { - setFpCloudToken(result.Ok().token); - console.log( - "[Fireproof Dashboard] ✅ Successfully retrieved fp-cloud-jwt. Enabling data queries.", - ); - } else { - console.error( - "[Fireproof Dashboard] ❌ Error getting fp-cloud-jwt:", - result.Err(), - ); - } - } catch (e) { - console.error( - "[Fireproof Dashboard] ❌ Exception getting fp-cloud-jwt:", - e, - ); - } - }; - - fetchFpCloudToken(); - }, [isLoaded, isSignedIn, fpCloudToken, api]); + }, [getToken]); // Query to list all tenants for the logged-in user const tenantsQuery = useQuery({ @@ -176,7 +62,7 @@ export default function FireproofDashboard() { () => api.listTenantsByUser({}), "listTenantsByUser", ), - enabled: isLoaded && isSignedIn && !!fpCloudToken, + enabled: isLoaded && isSignedIn, }); // Query to list all ledgers for the logged-in user @@ -186,40 +72,9 @@ export default function FireproofDashboard() { () => api.listLedgersByUser({}), "listLedgersByUser", ), - enabled: isLoaded && isSignedIn && !!fpCloudToken, + enabled: isLoaded && isSignedIn, }); - // Log authentication and query states - useEffect(() => { - console.log("[Fireproof Dashboard] 📊 State Update:", { - isLoaded, - isSignedIn, - tenantsQuery: { - isLoading: tenantsQuery.isLoading, - isError: tenantsQuery.isError, - isSuccess: tenantsQuery.isSuccess, - dataCount: tenantsQuery.data?.tenants.length, - }, - ledgersQuery: { - isLoading: ledgersQuery.isLoading, - isError: ledgersQuery.isError, - isSuccess: ledgersQuery.isSuccess, - dataCount: ledgersQuery.data?.ledgers.length, - }, - }); - }, [ - isLoaded, - isSignedIn, - tenantsQuery.isLoading, - tenantsQuery.isError, - tenantsQuery.isSuccess, - tenantsQuery.data, - ledgersQuery.isLoading, - ledgersQuery.isError, - ledgersQuery.isSuccess, - ledgersQuery.data, - ]); - // Not authenticated view if (isLoaded && !isSignedIn) { return ( From ff7144a28e8440578f4111c02ebaf19cc2e501b6 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 11:59:12 -0800 Subject: [PATCH 18/30] fix: Restore required fetch prop in DashboardApi config --- vibes.diy/pkg/app/routes/fireproof.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index e91a8a287..738d0b560 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -44,7 +44,7 @@ export default function FireproofDashboard() { const apiUrl = VibesDiyEnv.CONNECT_API_URL(); return new DashboardApi({ apiUrl, - // fetch: window.fetch.bind(window), // Optional per comments + fetch: window.fetch.bind(window), // Optional per comments getToken: async () => { const token = await getToken({ template: "with-email" }); return { From c50641f22fded4b6d9d04c4e215e230a11eff2c3 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:03:05 -0800 Subject: [PATCH 19/30] fix: Attempt to set audience to 'fireproof' for Clerk token --- vibes.diy/pkg/app/routes/fireproof.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 738d0b560..7cfd64597 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -46,7 +46,8 @@ export default function FireproofDashboard() { apiUrl, fetch: window.fetch.bind(window), // Optional per comments getToken: async () => { - const token = await getToken({ template: "with-email" }); + // Attempt to set an audience to satisfy backend validation + const token = await getToken({ template: "with-email", audience: "fireproof" }); return { type: "clerk", token: token || "", From f20db4dd0a97ade3a151b230ed88e487ce09ac4b Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:12:17 -0800 Subject: [PATCH 20/30] feat: Temporarily log Clerk JWT for debugging --- vibes.diy/pkg/app/routes/fireproof.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 7cfd64597..4ec1633e6 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -48,6 +48,7 @@ export default function FireproofDashboard() { getToken: async () => { // Attempt to set an audience to satisfy backend validation const token = await getToken({ template: "with-email", audience: "fireproof" }); + console.log("[Fireproof Dashboard] Clerk JWT (for verification):", token); return { type: "clerk", token: token || "", From 449b91ad6554dc7ba33bf700c73ef77232630e22 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:14:31 -0800 Subject: [PATCH 21/30] chore: Remove temporary debug logging --- vibes.diy/pkg/app/routes/fireproof.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 4ec1633e6..7cfd64597 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -48,7 +48,6 @@ export default function FireproofDashboard() { getToken: async () => { // Attempt to set an audience to satisfy backend validation const token = await getToken({ template: "with-email", audience: "fireproof" }); - console.log("[Fireproof Dashboard] Clerk JWT (for verification):", token); return { type: "clerk", token: token || "", From a9e8786b5de22de8e7faf4170c0063a539ef2908 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:16:36 -0800 Subject: [PATCH 22/30] format --- vibes.diy/pkg/app/routes/fireproof.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 7cfd64597..9d8cf1ce0 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -47,7 +47,10 @@ export default function FireproofDashboard() { fetch: window.fetch.bind(window), // Optional per comments getToken: async () => { // Attempt to set an audience to satisfy backend validation - const token = await getToken({ template: "with-email", audience: "fireproof" }); + const token = await getToken({ + template: "with-email", + audience: "fireproof", + }); return { type: "clerk", token: token || "", From 6182f83963deff00e008ba2215a08fb0d6d1378d Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:17:00 -0800 Subject: [PATCH 23/30] remove audience --- vibes.diy/pkg/app/routes/fireproof.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 9d8cf1ce0..578b2964c 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -49,7 +49,6 @@ export default function FireproofDashboard() { // Attempt to set an audience to satisfy backend validation const token = await getToken({ template: "with-email", - audience: "fireproof", }); return { type: "clerk", From ff46a54c5deeb9e981cc735e31c12136f8ca92be Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:17:24 -0800 Subject: [PATCH 24/30] refactor: Simplify fireproof route to only call ensureUser --- vibes.diy/pkg/app/routes/fireproof.tsx | 211 ++++--------------------- 1 file changed, 33 insertions(+), 178 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 578b2964c..2e4a874a4 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -3,10 +3,7 @@ import { useAuth } from "@clerk/clerk-react"; import { useQuery } from "@tanstack/react-query"; import { DashboardApi } from "@fireproof/core-protocols-dashboard"; import type { - ResListTenantsByUser, - ResListLedgersByUser, - UserTenant, - LedgerUser, + ResEnsureUser, } from "@fireproof/core-protocols-dashboard"; import type { Result } from "@adviser/cement"; import SimpleAppLayout from "../components/SimpleAppLayout.js"; @@ -49,6 +46,7 @@ export default function FireproofDashboard() { // Attempt to set an audience to satisfy backend validation const token = await getToken({ template: "with-email", + audience: "fireproof", }); return { type: "clerk", @@ -58,22 +56,12 @@ export default function FireproofDashboard() { }); }, [getToken]); - // Query to list all tenants for the logged-in user - const tenantsQuery = useQuery({ - queryKey: ["listTenantsByUser"], + // Query to ensure the user exists and is active + const ensureUserQuery = useQuery({ + queryKey: ["ensureUser"], queryFn: wrapResultToPromise( - () => api.listTenantsByUser({}), - "listTenantsByUser", - ), - enabled: isLoaded && isSignedIn, - }); - - // Query to list all ledgers for the logged-in user - const ledgersQuery = useQuery({ - queryKey: ["listLedgersByUser"], - queryFn: wrapResultToPromise( - () => api.listLedgersByUser({}), - "listLedgersByUser", + () => api.ensureUser({}), + "ensureUser", ), enabled: isLoaded && isSignedIn, }); @@ -98,13 +86,14 @@ export default function FireproofDashboard() {

Fireproof Dashboard

- Please sign in to view your Fireproof tenants and ledgers. + Please sign in to view your Fireproof dashboard.

); } + return (

Fireproof Dashboard

-
- {/* Tenants Section */} -
-

Tenants

- {tenantsQuery.isLoading && ( -
-

- Loading tenants... -

-
- )} - {tenantsQuery.isError && ( -
-

- Error loading tenants:{" "} - {tenantsQuery.error instanceof Error - ? tenantsQuery.error.message - : "Unknown error"} -

-
- )} - {tenantsQuery.isSuccess && ( -
- {tenantsQuery.data.tenants.length === 0 ? ( -
-

- No tenants found. -

-
- ) : ( - tenantsQuery.data.tenants.map((tenant: UserTenant) => ( -
-
-
-

- {tenant.tenant.name || "Unnamed Tenant"} -

-

- ID: {tenant.tenantId} -

-
-
- - {tenant.role} - - {tenant.default && ( - - default - - )} -
-
-
-

Status: {tenant.tenant.status}

-

- Created:{" "} - {new Date( - tenant.tenant.createdAt, - ).toLocaleDateString()} -

-
-
- )) - )} -
- )} -
- - {/* Ledgers Section */} -
-

Ledgers

- {ledgersQuery.isLoading && ( -
-

- Loading ledgers... -

-
- )} - {ledgersQuery.isError && ( -
-

- Error loading ledgers:{" "} - {ledgersQuery.error instanceof Error - ? ledgersQuery.error.message - : "Unknown error"} -

-
- )} - {ledgersQuery.isSuccess && ( -
- {ledgersQuery.data.ledgers.length === 0 ? ( -
-

- No ledgers found. -

-
- ) : ( - ledgersQuery.data.ledgers.map((ledger: LedgerUser) => ( -
-
-
-

- {ledger.name} -

-

- ID: {ledger.ledgerId} -

-

- Tenant: {ledger.tenantId} -

-
-
-

Users: {ledger.users.length}

-

Max shares: {ledger.maxShares}

-
-
-
-

- Created:{" "} - {new Date(ledger.createdAt).toLocaleDateString()} -

-
- {ledger.users.length > 0 && ( -
-

- User Access: -

-
- {ledger.users.map((user, idx) => ( - - {user.name || user.userId} ({user.role}/ - {user.right}) - - ))} -
-
- )} -
- )) - )} -
- )} -
+ {ensureUserQuery.isLoading && ( +
+

+ Ensuring user presence... +

+
+ )} + {ensureUserQuery.isError && ( +
+

+ Error ensuring user:{" "} + {ensureUserQuery.error instanceof Error + ? ensureUserQuery.error.message + : "Unknown error"} +

+
+ )} + {ensureUserQuery.isSuccess && ensureUserQuery.data?.user && ( +
+

+ User {ensureUserQuery.data.user.userId} is active. +

+
+ )}
From 9e4ddf0eb00972cd137ea738f6bc9f97da445428 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:19:33 -0800 Subject: [PATCH 25/30] fix: Remove unsupported 'audience' from Clerk getToken call --- vibes.diy/pkg/app/routes/fireproof.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index 2e4a874a4..e6b223fec 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -43,11 +43,7 @@ export default function FireproofDashboard() { apiUrl, fetch: window.fetch.bind(window), // Optional per comments getToken: async () => { - // Attempt to set an audience to satisfy backend validation - const token = await getToken({ - template: "with-email", - audience: "fireproof", - }); + const token = await getToken({ template: "with-email" }); return { type: "clerk", token: token || "", From 3e7395d07ba13d91d357ff70f1e6c46ccf2723a3 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 12:20:17 -0800 Subject: [PATCH 26/30] fix: Remove unused useEffect import --- vibes.diy/pkg/app/routes/fireproof.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index e6b223fec..aec4c6e70 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -1,10 +1,8 @@ -import React, { useMemo, useEffect } from "react"; +import React, { useMemo } from "react"; import { useAuth } from "@clerk/clerk-react"; import { useQuery } from "@tanstack/react-query"; import { DashboardApi } from "@fireproof/core-protocols-dashboard"; -import type { - ResEnsureUser, -} from "@fireproof/core-protocols-dashboard"; +import type { ResEnsureUser } from "@fireproof/core-protocols-dashboard"; import type { Result } from "@adviser/cement"; import SimpleAppLayout from "../components/SimpleAppLayout.js"; import { HomeIcon } from "../components/SessionSidebar/HomeIcon.js"; @@ -55,10 +53,7 @@ export default function FireproofDashboard() { // Query to ensure the user exists and is active const ensureUserQuery = useQuery({ queryKey: ["ensureUser"], - queryFn: wrapResultToPromise( - () => api.ensureUser({}), - "ensureUser", - ), + queryFn: wrapResultToPromise(() => api.ensureUser({}), "ensureUser"), enabled: isLoaded && isSignedIn, }); From 1c47ff1af434f31ec65b170306190c42d84731d2 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 13:34:13 -0800 Subject: [PATCH 27/30] fix: Update CONNECT_API_URL to handle production and development environments --- vibes.diy/pkg/app/config/env.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/vibes.diy/pkg/app/config/env.ts b/vibes.diy/pkg/app/config/env.ts index 57c1a4164..793c816d1 100644 --- a/vibes.diy/pkg/app/config/env.ts +++ b/vibes.diy/pkg/app/config/env.ts @@ -48,11 +48,17 @@ class vibesDiyEnv { this.env().get("VITE_CONNECT_URL") ?? "https://connect.fireproof.direct/token", ); - readonly CONNECT_API_URL = Lazy( - () => - this.env().get("VITE_CONNECT_API_URL") ?? - "https://connect.fireproof.direct/api", - ); + readonly CONNECT_API_URL = Lazy(() => { + const envUrl = this.env().get("VITE_CONNECT_API_URL"); + if (envUrl) { + return envUrl; + } + const isProduction = + runtimeFn().isBrowser && window.location.hostname === "vibes.diy"; + return isProduction + ? "https://connect.fireproof.direct/api" + : "https://dev.connect.fireproof.direct/api"; + }); readonly CLOUD_SESSION_TOKEN_PUBLIC_KEY = Lazy( () => this.env().get("VITE_CLOUD_SESSION_TOKEN_PUBLIC") ?? From fc2e6b4dd69925a2229a7d75e27117a45ad3b75d Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Mon, 1 Dec 2025 15:05:51 -0800 Subject: [PATCH 28/30] fix: Address PR feedback for fireproof dashboard route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add loading state for Clerk auth initialization (!isLoaded) - Add userId to React Query keys for proper cache scoping - Keep using global fetch (not window.fetch) - Simplify to just ensureUser and show success Addresses feedback from @mabels on PR #659 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- vibes.diy/pkg/app/routes/fireproof.tsx | 36 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index aec4c6e70..edcbdb601 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -32,14 +32,14 @@ function wrapResultToPromise(pro: () => Promise>, label: string) { } export default function FireproofDashboard() { - const { isSignedIn, isLoaded, getToken } = useAuth(); + const { isSignedIn, isLoaded, getToken, userId } = useAuth(); // Create DashboardApi instance with Clerk auth const api = useMemo(() => { const apiUrl = VibesDiyEnv.CONNECT_API_URL(); return new DashboardApi({ apiUrl, - fetch: window.fetch.bind(window), // Optional per comments + fetch: fetch, // Use global fetch getToken: async () => { const token = await getToken({ template: "with-email" }); return { @@ -52,13 +52,41 @@ export default function FireproofDashboard() { // Query to ensure the user exists and is active const ensureUserQuery = useQuery({ - queryKey: ["ensureUser"], + queryKey: ["ensureUser", userId], queryFn: wrapResultToPromise(() => api.ensureUser({}), "ensureUser"), enabled: isLoaded && isSignedIn, }); + // Loading state while Clerk initializes + if (!isLoaded) { + return ( + + + + +
+ } + > +
+

Fireproof Dashboard

+
+

+ Loading... +

+
+
+ + ); + } + // Not authenticated view - if (isLoaded && !isSignedIn) { + if (!isSignedIn) { return ( Date: Mon, 1 Dec 2025 15:07:45 -0800 Subject: [PATCH 29/30] fix: Wrap fetch in arrow function to preserve context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes 'Illegal invocation' error by wrapping fetch call in an arrow function to maintain proper this binding when passed to DashboardApi. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- vibes.diy/pkg/app/routes/fireproof.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index edcbdb601..d251032cc 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -39,7 +39,7 @@ export default function FireproofDashboard() { const apiUrl = VibesDiyEnv.CONNECT_API_URL(); return new DashboardApi({ apiUrl, - fetch: fetch, // Use global fetch + fetch: (input, init) => fetch(input, init), // Wrap fetch to preserve context getToken: async () => { const token = await getToken({ template: "with-email" }); return { From ebd9e03f5dd33b0cb366beab22240834913553b6 Mon Sep 17 00:00:00 2001 From: J Chris Anderson Date: Tue, 2 Dec 2025 05:33:24 -0800 Subject: [PATCH 30/30] fix: Update FireproofDashboard to include userId in ensureUser query and improve loading state UI --- vibes.diy/pkg/app/routes/fireproof.tsx | 33 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/vibes.diy/pkg/app/routes/fireproof.tsx b/vibes.diy/pkg/app/routes/fireproof.tsx index d251032cc..d4ca64710 100644 --- a/vibes.diy/pkg/app/routes/fireproof.tsx +++ b/vibes.diy/pkg/app/routes/fireproof.tsx @@ -37,13 +37,31 @@ export default function FireproofDashboard() { // Create DashboardApi instance with Clerk auth const api = useMemo(() => { const apiUrl = VibesDiyEnv.CONNECT_API_URL(); + console.log("[Fireproof Dashboard] API URL:", apiUrl); return new DashboardApi({ apiUrl, - fetch: (input, init) => fetch(input, init), // Wrap fetch to preserve context + fetch: window.fetch.bind(window), getToken: async () => { + // Use with-email template to get email claims in params const token = await getToken({ template: "with-email" }); + if (token) { + // Decode JWT header to log details + try { + const [headerB64, payloadB64] = token.split("."); + const header = JSON.parse(atob(headerB64)); + const payload = JSON.parse(atob(payloadB64)); + console.log("[Fireproof Dashboard] JWT kid:", header.kid); + console.log("[Fireproof Dashboard] JWT iss:", payload.iss); + console.log("[Fireproof Dashboard] JWT sub:", payload.sub); + console.log("[Fireproof Dashboard] Full token:", token); + } catch (e) { + console.error("[Fireproof Dashboard] Failed to decode JWT:", e); + } + } else { + console.warn("[Fireproof Dashboard] No token available from getToken()"); + } return { - type: "clerk", + type: "clerk" as const, token: token || "", }; }, @@ -139,11 +157,16 @@ export default function FireproofDashboard() { )} {ensureUserQuery.isError && (
-

- Error ensuring user:{" "} +

+ Error ensuring user: +

+
                 {ensureUserQuery.error instanceof Error
                   ? ensureUserQuery.error.message
-                  : "Unknown error"}
+                  : JSON.stringify(ensureUserQuery.error, null, 2)}
+              
+

+ Check browser console for JWT details (kid, iss, sub, full token)

)}