fix: secure the Convex data plane + rebuild the auth template - #2
Open
ryanpacker wants to merge 4 commits into
Open
fix: secure the Convex data plane + rebuild the auth template#2ryanpacker wants to merge 4 commits into
ryanpacker wants to merge 4 commits into
Conversation
Phase 1 of the Convex data-plane auth work (see docs/convex-data-plane-auth.md). Closes two real exposures in the generated-project templates and adds a regression guard. _generated/server.js stub exposure (both templates) The committed convex/_generated/server.js aliased internalQuery/ internalMutation/internalAction to the PUBLIC generics, so internal functions registered on the public API. Convex CLI >= 1.39.0 skips rewriting server.js when it exists, so the wrong stub is what gets pushed on convex dev/deploy -- shipping to real deployments, not just fresh scaffolds. In the auth template this exposed auditLog.record (anonymous forged-audit write) and auditLog.listAll (anonymous full-table read). Replaced both with genuine codegen output mapping the internal builders to the internal generics. Verified live this session: an internal function was publicly callable with the buggy stub and "Could not find public function" after the fix. standard template data-plane lie The messages demo used raw query/mutation with no auth, so the table was world-read/write despite living behind the app login wall. Relabeled everywhere (function headers, schema, demo-page banner, R9STACK.md) as an intentionally public, unauthenticated guestbook, and pointed to the auth template for the enforced per-user pattern. Bumped convex ^1.31.2 -> ^1.42.0. Regression guard tests/guard-generated-server.mjs (wired to npm test and a new CI workflow) fails if any template's committed server.js regresses the internal aliasing. Also: re-synced standard/template.json from source and dropped the leaked ./template.json / ./template-info.json file entries (a starter->template rename left the compiler ignoring starter* but not template*, so authoring files -- including a stale self-copy of the old buggy stub -- were written into every generated project); removed the orphan auth/starter.json legacy artifact. Versions: standard 1.2.0, auth 1.0.1. The auth template still does not authenticate requests as shipped (OIDC config, isLoaded/loading bridge bug, getByWorkosId IDOR); that rebuild is Phase 2 and needs a live-deployment test before promotion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…force it
Phase 2. The auth template did not authenticate a single request as shipped,
and did not even generate a building project. This rebuilds it against Convex's
official TanStack-Start + WorkOS AuthKit reference, then adds enforcement.
Correctness (it now authenticates)
- auth.config.ts: replace the OIDC { domain, applicationID: 'convex' } providers
(which can never work — WorkOS serves no OIDC discovery doc and issues no
aud='convex') with the dual customJwt providers Convex actually validates.
Throw on missing WORKOS_CLIENT_ID instead of a placeholder that silently
builds a bogus config.
- src/lib/convex-auth.ts: the bridge destructured `isLoaded` (the SDK field is
`loading`), so Convex's "auth ready" signal was permanently wrong and it never
authenticated; and it ignored `forceRefreshToken`. Rewritten to the canonical
{ loading, user } + getAccessToken/refresh hook.
- src/start.ts: old createStartHandler API -> createStart (current TanStack).
Security
- convex/functions.ts (new): authedQuery/authedMutation/authedAction inject the
verified identity; publicQuery/publicMutation are explicit opt-outs;
requirePermission for RBAC. All function modules migrated onto them.
- ESLint bans the raw query/mutation/action builders (and the convex/server
generics), so no function can ship without an auth decision; functions.ts is
the sole exemption.
- users.getByWorkosId was a public query taking a caller-supplied workosId
(any authed user could read any user by id) -> now internalQuery.
- auditLogReader.listRecent documented as demo-only with a production gating note.
Setup that was missing / wrong
- convex.json (new): Convex zero-config authKit block (dev/preview/prod).
- README: the mandatory WorkOS Sessions JWT-template `aud` + profile-claims step
(dashboard-only; auth silently fails without it) and setting WORKOS_* on the
Convex deployment.
- Deps: @workos/authkit-tanstack-react-start ^0.11.0 (0.8.2–0.9.x carry an IDOR),
add @workos-inc/node ^10.7.0 + convex-helpers ^0.1.120, convex ^1.42.0,
Node >=22.11. Added the missing template-info.json.
Template packaging / base drift (both templates)
- Generated projects failed to build under the current @tanstack/cli base:
vite-tsconfig-paths was missing from packageAdditions; tsconfig `baseUrl` is
deprecated under TS6; the devtools `triggerImage` prop was removed. Fixed in
both standard and auth. Auth's template.json also lacked the required
`deletedFiles` field (so it never generated).
Verified: both templates generate via `@tanstack/cli create`; the generated auth
project typechecks and builds clean; the generated standard project builds clean
(its 5 tsc errors are pre-existing iron-session legacy). The ESLint ban fires on
a raw-builder import. NOT verified live: a real signed-in token resolving to a
non-null getUserIdentity() — that needs a Convex login on the personal team plus
the one dashboard-only WorkOS JWT-template step. See docs/convex-data-plane-auth.md.
Versions: auth 1.0.1 -> 2.0.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…en live check Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pen hotkey The TanStack devtools default hotkey (Shift+A) fires while typing capital A; we never open the panel by keyboard, so drop the hotkey entirely and keep the trigger hidden until hovered. Config only seeds first-run state -- existing browsers with persisted tanstack_devtools_settings in localStorage are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two phases of the Convex data-plane auth work. Full architecture, research corrections, and the one remaining open item are in
docs/convex-data-plane-auth.md.Phase 1 — secure the data plane by default
_generated/server.jsstub exposure (both templates): the committed stub aliasedinternalQuery/internalMutation/internalActionto the public generics, registering internal functions on the public API. Convex CLI ≥1.39.0 skips rewritingserver.jswhen it exists, so the wrong stub ships to real deployments. In the auth template this exposedauditLog.record(anonymous forged-audit write) andauditLog.listAll(anonymous full-table read). Fixed with genuine codegen output; proven live this session (internal fn was publicly callable before, "Could not find public function" after).messagesdemo was world-read/write behind a login page implying protection. Relabeled everywhere as an explicit public guestbook.tests/guard-generated-server.mjs(npm test+ CI workflow)../template.json/./template-info.jsonfrom standard's generated output; removed the orphanauth/starter.json.Phase 2 — make the auth template actually authenticate + enforce it
As shipped, the auth template authenticated no requests and didn't even generate a building project. Rebuilt against Convex's official reference:
auth.config.tsOIDC → dualcustomJwt(the OIDC form can't work with WorkOS); the client bridge usedisLoaded(SDK field isloading) so Convex never authenticated, and ignoredforceRefreshToken— rewritten to the canonical hook;start.tscreateStartHandler→createStart.convex/functions.ts(authedQuery/authedMutation/authedAction+ explicitpublicQuery/publicMutation+requirePermission), all modules migrated, and an ESLint ban on the raw builders (verified it fires).users.getByWorkosId(public, took a clientworkosId) →internalQuery;auditLogReader.listRecentdocumented as demo-only with a production gating note.convex.jsonzero-config authKit block; README now documents the mandatory dashboard-only WorkOS JWT-templateaudstep and settingWORKOS_*on the Convex deployment. SDK →^0.11.0(0.8.2–0.9.x carry an IDOR), added@workos-inc/node/convex-helpers,convex ^1.42.0, Node ≥22.11, created the missingtemplate-info.json.Template/base drift (both templates)
Generated projects failed to build under the current
@tanstack/clibase:vite-tsconfig-pathsmissing from packageAdditions, deprecatedbaseUrlin tsconfig, removed devtoolstriggerImageprop; auth'stemplate.jsonalso lacked the requireddeletedFiles. Fixed in both.Verified
Both templates generate via
@tanstack/cli create. Generated auth project typechecks + builds clean; generated standard builds clean (its 5 tsc errors are pre-existing iron-session legacy). ESLint ban fires on a raw-builder import.npm testguard passes.NOT in this PR (needs you)
The one unproven item: a real signed-in token resolving to a non-null
getUserIdentity()live. Blocked on (1)npx convex loginon your personal Convex team (this machine's token is the BambooHR account) and (2) the dashboard-only WorkOS JWT-templateaudstep. Negative/security cases are deterministic from code and the mechanism was proven live in Phase 1.🤖 Generated with Claude Code