-
Notifications
You must be signed in to change notification settings - Fork 36
jchris/vibes popup #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
jchris/vibes popup #676
Conversation
✅ Deploy Preview for fireproof-ai-builder canceled.
|
🎉 Hosting Preview DeploymentPreview URL: https://pr-676-vibes-hosting-v2.jchris.workers.dev Test the preview:
This preview will be automatically updated on each push to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall the changes are cohesive and line up with the documented JWT token-exchange design, but there are a few security and maintainability concerns around key management and popup origin handling. The server-side code currently reuses CLERK_SECRET_KEY for signing custom vibes JWTs, which is workable but couples responsibilities and makes rotation harder. On the client side, the token-provider popup is permissive when window.opener is missing and when origins are invalid, which should be tightened to "fail closed" for a security-sensitive cross-window messaging path. The new installs grid and icon usage look logically sound, with only a minor UX consideration around the updated "Shared with" label. Addressing the noted issues will make the auth flow more robust and reduce future debugging/security risks.
Additional notes (2)
-
Maintainability |
hosting/pkg/src/middleware/vibesTokenAuth.ts:69-76
Right now, a failure to verify the vibes token is silently ignored for short-ish tokens (vibesToken.length <= 20), which could mask malformed/empty headers or configuration mistakes. That makes debugging 3rd-party integrations harder and may result in subtle auth issues where requests unexpectedly run unauthenticated. You already avoid log spam for clearly bogus tokens, so it would be safer to at least log once for obviously invalid formats (e.g., missing sections) or always log at debug level while keeping the warning-level guard for long, likely real tokens. -
Maintainability |
vibes.diy/pkg/app/routes/auth.token-provider.tsx:30-43
The origin whitelist is hard-coded here, including specific third-party domains (strudel.cc,strudel.fp). That’s fine initially, but as you add or change consumers it requires code changes and redeploys. It also mixes configuration with logic, which makes auditing which apps are allowed more difficult.
Since this route is core to 3rd-party auth, moving the allowed-origins list into configuration (env or a shared config module) would make it easier to manage and reason about, and better match the design doc’s emphasis on security.
Summary of changes
Summary of Changes
- Updated OpenAI image generation size from
512x512to the DALL-E 3–compatible1024x1024. - Added a new
/api/auth/exchange-tokenendpoint that exchanges a Clerk-authenticated user session for a short-lived vibes JWT. - Introduced
vibesTokenMiddlewareto authenticate requests via aX-Vibes-TokenorAuthorization: BearerJWT, as a fallback to Clerk. - Wired the new auth pieces into the hosting API router, including route registration for the token exchange endpoint and middleware.
- Added detailed documentation (
notes/fix-login.md) describing the 3rd-party JWT token-exchange architecture and flow. - Added a new frontend route
/auth/token-providerthat runs in a popup, maintains a Clerk session, exchanges Clerk tokens for vibes JWTs, and posts them to the opener. - Updated the
installsroute UI to render installs as a responsive grid ofPublishedVibeCardcomponents with icon support and slightly simplified metadata display. - Updated the app routes configuration to register the new
auth/token-providerroute.
| try { | ||
| // Create signed JWT with 60 second expiry | ||
| // We use CLERK_SECRET_KEY as the signing secret for simplicity since it's already a secure secret available | ||
| const secret = new TextEncoder().encode(c.env.CLERK_SECRET_KEY); | ||
| const expiresIn = 60; // seconds | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using CLERK_SECRET_KEY as the signing key for your own vibes JWTs couples your internal token format to Clerk and effectively turns that key into a generic HMAC secret for a broader surface area. That increases blast radius if the Clerk secret is ever rotated or leaked, and makes it harder to change auth providers later. A dedicated VIBES_JWT_SECRET (or similar) gives you clearer separation of concerns and lets you rotate or scope keys independently while still keeping the current design.
Additionally, the hard-coded 60 seconds TTL is fine for now but would be easier to tune and monitor if it were driven by configuration (e.g., an env var) rather than an inline literal.
Suggestion
Consider introducing a dedicated signing secret and configurable expiry instead of hard-coding CLERK_SECRET_KEY and 60 here. For example:
const secretKey = c.env.VIBES_JWT_SECRET || c.env.CLERK_SECRET_KEY;
const secret = new TextEncoder().encode(secretKey);
const expiresIn = Number(c.env.VIBES_JWT_TTL_SECONDS ?? 60);and document that VIBES_JWT_SECRET is preferred going forward. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change wired through and documented.
| // Use CLERK_SECRET_KEY as the shared secret | ||
| const secret = new TextEncoder().encode(c.env.CLERK_SECRET_KEY); | ||
| const { payload } = await jwtVerify<VibesTokenPayload>( | ||
| vibesToken, | ||
| secret, | ||
| { | ||
| issuer: "vibes.diy", | ||
| audience: "vibes.diy-api", | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as in AuthExchangeToken, this middleware uses CLERK_SECRET_KEY as the shared secret for verifying vibes JWTs. From a security and separation-of-concerns standpoint, it's better to sign and verify these tokens with a dedicated secret so that Clerk's secret isn't directly exposed to 3rd-party-token concerns and can be rotated or scoped independently.
Changing this now is low-cost and keeps your auth surfaces cleaner.
Suggestion
Align this verifier with a dedicated vibes JWT secret:
const rawSecret = c.env.VIBES_JWT_SECRET ?? c.env.CLERK_SECRET_KEY;
if (!rawSecret) {
// If you don't want to 500 here, at least log loudly
console.error("Missing JWT signing secret (VIBES_JWT_SECRET/CLERK_SECRET_KEY)");
return next();
}
const secret = new TextEncoder().encode(rawSecret);
const { payload } = await jwtVerify<VibesTokenPayload>(
vibesToken,
secret,
{ issuer: "vibes.diy", audience: "vibes.diy-api" },
);This keeps signing and verification logic consistent and allows you to rotate VIBES_JWT_SECRET without touching Clerk. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Get parent origin for security | ||
| const origin = window.opener?.location?.origin || ""; | ||
| setParentOrigin(origin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle cross-origin parent without DOMException
The token-provider popup reads window.opener?.location?.origin to decide whether to trust the opener. When this window is opened from a different origin (the main exchange scenario), browsers throw a DOMException on that property due to cross-origin restrictions, so the effect fails before it can whitelist the origin or post a token. As a result, popups launched from strudel.cc or other third-party hosts will crash and never deliver a JWT. Guard the origin lookup with a try/catch or use a safer source like document.referrer/postMessage.
Useful? React with 👍 / 👎.
Documents architecture for supporting authenticated AI requests from 3rd-party apps (like strudel.fp) via popup-based JWT token exchange. Key design: - Persistent popup maintains Clerk session - Backend exchanges Clerk tokens for signed vibes JWTs - 30-second refresh cycle via postMessage - Backend validates vibes JWT via X-Vibes-Token header - Stateless JWT validation (no database lookup) This allows 3rd-party apps to use vibes.diy's AI endpoints without exposing Clerk tokens or requiring Clerk integration. Related to migrating from legacy Fireproof token authentication.
b0268cb to
2388bf4
Compare
Summary
This PR implements a popup-based JWT token exchange system to allow trusted 3rd-party applications to securely authenticate with the vibes.diy backend.
Problem
External applications (like Strudel) need to make authenticated requests to our API. However:
Solution
We introduce a "Token Provider" popup flow:
https://vibes.diy/auth/token-providerin a popup.postMessage.Authorizationheader (orX-Vibes-Token) for API calls.Key Changes
Backend (
hosting/pkg)POST /api/auth/exchange-tokenCLERK_SECRET_KEY.vibesTokenMiddlewareX-Vibes-TokenorAuthorization: Bearer ....index.ts.Frontend (
vibes.diy/pkg)/auth/token-providerlocalhost,strudel.cc).window.opener.postMessage.routes.ts.