feat: opt routes out of the SSR user write via routeRules#55
Draft
frederikprijck wants to merge 10 commits into
Draft
feat: opt routes out of the SSR user write via routeRules#55frederikprijck wants to merge 10 commits into
frederikprijck wants to merge 10 commits into
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
8206a43 to
1559b99
Compare
2cf237f to
7486a71
Compare
The cache guard only detects caching expressed through Nitro route rules.
When a shared cache / CDN is configured outside the app (an edge rule keyed
on path with its own TTL, no 'public'/'s-maxage' header), the guard cannot
see it and the user would be written into that route's SSR payload.
Add an explicit per-route opt-out: the SSR middleware reads an
'auth0: { ssrUser: false }' Nitro route rule (via the getRouteRules accessor
it already uses) and skips the user write. Set it on '/**' to disable the
SSR user write globally, or on specific paths for per-route control. The
middleware stays global, so it always runs and sees the route rules; only
the write is gated. The user is hydrated client-side as on cacheable routes.
- middleware: skip write when auth0.ssrUser === false (composes with the guard)
- types: internal d.ts augmenting NitroRouteConfig so repo sources/fixtures typecheck
- tests: middleware unit tests (skip on false, write on true) + e2e proving a
non-cacheable opted-out route renders anonymous SSR HTML
- docs: README + EXAMPLES, incl. a copy-paste augmentation snippet for consumers
The auth0.ssrUser route rule already supports the inverse model: disable the
SSR user write globally with '/**': { auth0: { ssrUser: false } } and opt back
in on specific routes with { auth0: { ssrUser: true } }. This works because
Nitro merges overlapping route rules by specificity, so the more specific
rule wins — no code change needed.
Add an e2e fixture + test guarding that merge behaviour (opted-in route
server-renders the user; a non-opted route stays anonymous), and document the
pattern in README and EXAMPLES.
…CVE-2026-53721) The middleware-level fix for the route-rule case-sensitivity bypass lives on the issue-52 base (it re-checks the lowercased path). This commit extends the coverage to the auth0.ssrUser opt-out that this branch adds: - unit: an opt-out rule reachable only via the lowercased path still skips the write - e2e: /Opted-Out renders anonymous SSR HTML (the opt-out is not bypassed by casing)
The cache guard is best-effort: it only sees caching expressed through Nitro
route rules. It cannot detect a Cache-Control header set imperatively
(setHeader in a server route/middleware) or a shared cache / CDN configured
outside the app, so those routes still write the user into cacheable SSR HTML.
Document this limitation explicitly and recommend the fail-safe posture for
apps behind a shared cache / CDN: disable the SSR user write globally with
'/**': { auth0: { ssrUser: false } } and opt in per route where the response
is never shared-cached. No route serves the user in cacheable HTML unless
explicitly allowed.
The 'caching' and 'optin' fixtures both exercised the same feature — controlling
the SSR user write — but lived apart because they needed opposite global
defaults. Merge them into one 'ssr-user' fixture: the on-by-default routes
(/private, /cacheable, /opted-out) sit alongside an '/optin/**' subtree that is
off-by-default with '/optin/dashboard' opted back in. Using a subtree instead of
a literal '/**' lets both directions coexist in one routeRules block.
The two test files become one 'ssr-user.test.ts' with two describe blocks
('on by default' and 'off by default with per-route opt-in'). No coverage lost:
all cache-guard, opt-out, CVE case-variant, and opt-in cases are retained.
ssrUser: true overrides a broader ssrUser: false but cannot force the user into shared-cacheable HTML — the cache guard still skips the write and the client hydrates. Note this explicitly in README and EXAMPLES so the 'more specific rule wins' phrasing isn't read as an absolute override.
The $fetch e2e proves a shared-cacheable route's raw SSR HTML is anonymous (the leak surface). This adds the complementary browser test: despite that anonymous SSR HTML, the auth.client plugin fetches /auth/profile after hydration and the account UI ends up reflecting the logged-in user — the client-hydration loop, which only a real browser can exercise. Seeds the encrypted stateless session cookie on the browser context before navigating, so /auth/profile resolves getUser() offline (no Auth0 call).
Rename the fixture user (pii-victim -> user) and rewrite the test descriptions/comments to describe the feature — controlling the SSR user write and hydrating client-side — rather than narrating a payload leak and its fix. The CVE-2026-53721 references for the case-sensitivity re-check are kept, since they explain why the lowercased lookup exists. No coverage change: 9 unit + 9 e2e still pass, lint clean.
) Adds a module-level `auth0: { ssrUser: false }` option that disables the SSR user write for every route with no route rule required. Unlike the automatic cache guard (which only detects route-rule-based caching) this is the cache-mechanism-agnostic escape hatch: it also covers caching the guard cannot see ahead of render (imperative headers, CDN-side config). Precedence: a per-route `auth0.ssrUser` rule overrides the module option, which overrides the default (true). `ssrUser: true` never overrides the cache guard, so a shared-cacheable route always stays anonymous. Enables "disable globally, opt specific routes back in."
f3ad73b to
ed0acca
Compare
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.
Summary
Adds a per-route (and global) opt-out for the SSR user write, addressing point 3 of #52 ("config to disable / route-scope the global middleware").
This lets a consumer opt a route (or pattern, or everything) out of the SSR user write:
On a route with
auth0: { ssrUser: false }, the middleware skips the user write even when the route is not shared-cacheable; the user is hydrated client-side from the profile endpoint instead (same path the cache guard already uses).Why this design
global: true, so it always runs and always sees the route rules; only the write is gated. (Notably, Nuxt's nativeappMiddleware: { auth0: false }route rule does not work here —appMiddlewarecan only remove named middleware, not a global one. An e2e test was written that demonstrated this, which is why the opt-out is implemented as an explicit rule the middleware reads.)NitroRouteConfigis a closed interface, so a custom route-rule key needs a type augmentation. We ship an internal.d.tsso this repo's sources/fixtures typecheck, and document a copy-paste augmentation snippet for consumers who type-check theirnuxt.config— rather than generating types at build time.Tests
ssrUser: false, writes onssrUser: true.caching.test.ts): a non-cacheable/opted-outroute withauth0: { ssrUser: false }renders anonymous SSR HTML (nosub/emailin the payload), proving the opt-out works independently of the cache guard.Merge order
Based on
issue-52because it builds on that branch's client-hydration plugin and profile endpoint.