Skip to content

fix(prediction): identify Auth0 callers on the public prediction route - #1095

Open
diecoscai wants to merge 3 commits into
stagingfrom
fix/prediction-optional-auth
Open

diecoscai wants to merge 3 commits into
stagingfrom
fix/prediction-optional-auth

Conversation

@diecoscai

Copy link
Copy Markdown

Why

Chats from the Chrome extension reach Langfuse with no userId. The IAS usage dashboard shows the extension as "no signed-in person since 2026-03-04".

The extension never sent a user id. It sends the user's Auth0 JWT as Authorization: Bearer. Before the Flowise 3.0.11 upgrade, the old middleware read that token on public routes (jwtCheckPublic, authRequired: false), so req.user was set. After the upgrade, whitelisted routes skip auth entirely, and /api/v1/prediction/ is whitelisted. buildChatflow gets user = undefined, so the trace gets userId = undefined.

What changes

  • verifyAAIToken gets an optional mode. With a valid Auth0 JWT it sets req.user. With no header, an API key, an expired, forged or wrong-audience token, an org outside AUTH0_ORGANIZATION_ID, or a DB error, the request continues anonymous. It never sends a 401. Strict mode (no options) is unchanged.
  • /prediction runs it after the rate limiter, at router level, the same way /auth/me does. The global whitelist is not touched.
  • createPrediction no longer scopes the chatflow lookup by req.user.activeWorkspaceId. That filter came from upstream Chore/WorkspaceID Check FlowiseAI/Flowise#5228. On a public route it guards nothing, because the same request without a token is served. It would also 404 chatflows in the caller's other workspaces, which is the bug fixed for internal-predictions in 3cddc90. A comment and a test keep a future upstream merge from adding it back.
  • isBearerJWT replaces the identical inline helper in src/index.ts.

What else changes when a user is present

All four match what Studio already does on /internal-prediction, and what /prediction did before the upgrade:

  • Billing goes to the caller instead of the chatflow owner (billedUserId = user?.id || chatflow.userId), unless OVERRIDE_CUSTOMER_ID is set.
  • The usage-limit check uses the caller's plan.
  • AAI vector stores filter by the caller's org.
  • ChatMessage.userId is set, so extension chats may show in the user's Studio history.

Merging to staging updates the release PR to production for every tenant, so billing and chat history need an OK before this merges.

Tests (all local; no workflow runs jest)

  • Unit: 10 tests for verifyAAIToken and 2 for the router. Each new test failed before the change.
  • Integration (test/integration/prediction, 9 tests): real RS256 verification against a local issuer, Postgres with the app's own migrations, real user and workspace provisioning, real chatflow lookup. Only buildChatflow and the streaming check are mocked. It runs only when TEST_PG_URL is set. Run steps are in the file header.
    • A valid token gives req.user.id equal to the user's DB id, and two requests do not duplicate the user.
    • A chatflow in a workspace that is not the active one answers 200.
    • No header, API key, expired, forged, wrong audience and disallowed org all stay anonymous, and none of them creates rows.
    • With the controller change reverted, the two signed-in cases fail with a non-200.
  • Mutation (Stryker 8.7.1, changed lines only): 27 killed, 1 timeout. Two survivors only change console.warn text, so they are equivalent. Two more are on old router lines that are not in this diff.
  • Regression: every index.test.ts suite except the root one, before and after the change. Same result, plus the new suites. services/documentstore (3 tests) and auth/middleware (does not start: circular import at AzureSSO extends SSOBase) already fail on staging.

Not covered by these tests

  • The global middleware in src/index.ts is only reached by the full-server suite. It gets checked on staging.ias: a chat from the extension, a logged-out Help Center chat, and an agent execution resumed from Studio.
  • Traces from 2026-03 to the deploy have no userId and cannot be recovered.

🤖 Generated with Claude Code

diecoscai and others added 3 commits November 10, 2026 16:00
Optional mode identifies the caller when a valid Auth0 JWT is sent and
continues anonymous otherwise, for public routes such as /prediction.
Strict mode is unchanged. isBearerJWT replaces the inline helper in index.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mount verifyAAIToken in optional mode on /prediction, after the rate
limiter, so extension chats carry the caller's User.id to Langfuse again.
Stop scoping the chatflow lookup by the caller's active workspace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real RS256 verification against a local issuer, Postgres with the app's
migrations, real user/workspace provisioning and chatflow lookup. Runs
only when TEST_PG_URL is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@diecoscai diecoscai self-assigned this Nov 10, 2026
@vercel

vercel Bot commented Nov 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
answerai-docs Building Building Preview Nov 10, 2026 7:27pm UTC
the-answerai Error Error Nov 10, 2026 7:27pm UTC

Request Review

['/', '/:id'],
getMulterStorage().array('files'),
predictionsController.getRateLimiterMiddleware,
optionalAAIAuth,

This branch had an error being deployed

1 failed and 1 active deployments
Preview – answerai-docs — 88985fc3 Deployed Nov 10, 2026 by vercel[bot]
Preview – the-answerai — 88985fc3 Deployed Nov 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants