feat(auth): add dual auth system for Studio vs API#17722
Conversation
Adds StudioConfig to Mastra configuration allowing separate authentication providers for Studio UI (internal team) vs API (external customers). Key changes: - Add StudioConfig type with auth, rbac, and fga options - Add mastra.getStudio() accessor for studio-specific config - Add MASTRA_AUTH_MODE_KEY constant to track which auth provider was used - Update checkRouteAuth() to route requests based on x-mastra-client-type header - Make all auth routes studio-aware (/auth/capabilities, /auth/me, SSO, etc.) - Update all server adapters (Hono, Express, Fastify, Koa) to support dual auth Security: When studio header is present but studio.auth is not configured, returns 401 instead of falling back to server auth (prevents header spoofing). Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
🦋 Changeset detectedLatest commit: a391177 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Studio-specific authentication and authorization configuration to Mastra, routes requests between Studio and server auth based on ChangesStudio Auth Configuration and Request Routing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
PR triageLinked issue check skipped for core contributor @rphansen91. PR complexity score
Applied label: Changed test gateNo changed test files were detected. Label: |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
.claude/skills/auth-refactor-test/results/CODE_REVIEW.md (1)
1-179:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReview write-up is for a different change and gives a misleading merge signal.
This review analyzes PR
#17142and concludes “no behavioral changes,” which does not match#17722’s dual-auth routing/authz behavior changes. Please replace this with a review scoped to#17722so the verdict reflects the actual risk surface.
Based on learnings from the provided PR summary, this PR introduces behavior-level auth changes that this report does not evaluate.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.claude/skills/auth-refactor-test/results/CODE_REVIEW.md around lines 1 - 179, The review file incorrectly documents PR `#17142` and claims “no behavioral changes” while this review should target PR `#17722` (which introduces dual-auth routing/authz behavior changes); update the CODE_REVIEW.md header and all PR references to `#17722` and change the Branch line to the correct branch for `#17722`, then revise the Summary/Architectural Changes/Code Quality Assessment to explicitly evaluate behavior-level changes (inspect CompositeAuth, routing/authz flows, and any changes to getMigrations in better-auth) and update the Verdict to reflect the actual risk (include note to re-run smoke tests covering dual-auth routing and authz scenarios) so the file accurately represents PR `#17722`’s scope and risk.packages/server/src/server/handlers/auth.ts (1)
77-97:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDo not fall back to server auth for Studio-classified auth endpoints.
At Line 79 and Line 87, Studio requests without
studio.authsilently fall back toserver.auth. That breaks Studio/API auth isolation and undermines the dual-auth contract.Suggested fix
function getAuthProvider(mastra: any, isStudio?: boolean): MastraAuthProvider | null { // If this is a Studio request, try studio auth first if (isStudio) { const studioConfig = mastra.getStudio?.(); if (studioConfig?.auth && typeof studioConfig.auth.authenticateToken === 'function') { return studioConfig.auth as MastraAuthProvider; } + // Studio-classified request with no studio auth: do not fall back to server auth + return null; } // Fall back to server auth const serverConfig = mastra.getServer?.();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/server/src/server/handlers/auth.ts` around lines 77 - 97, The getAuthProvider function currently falls back to server auth for Studio requests; change its logic so that when isStudio is true and either mastra.getStudio() is missing or studioConfig.auth is absent/not a provider, the function returns null immediately and does not consult mastra.getServer(); update getAuthProvider (and its checks around mastra.getStudio, studioConfig.auth, and the existing serverConfig path) to enforce strict Studio-only auth for isStudio requests.server-adapters/hono/src/index.ts (1)
527-544:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHono has the same Studio permission bypass.
Both permission blocks still hinge on
this.mastra.getServer()?.auth. That means a request authenticated viastudio.authcan passcheckRouteAuth()and still skipcheckRoutePermission()completely wheneverserver.authis unset, which defeatsrequiresPermissionfor Studio-only deployments.As per coding guidelines, “For routes with
requiresPermissionconfigured, the permission check must only enforce permissions when RBAC provider is configured. Without RBAC,requiresPermissionon routes is silently ignored.”Also applies to: 645-663
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server-adapters/hono/src/index.ts` around lines 527 - 544, The permission check currently only runs when this.mastra.getServer()?.auth is set, which lets Studio-authenticated requests bypass checkRoutePermission when server.auth is unset; change the logic to base permission enforcement solely on the RBAC provider flag returned by loadHasPermission() (call await loadHasPermission() regardless of this.mastra.getServer()?.auth and only run checkRoutePermission when hasPermission is truthy), keep using requestContext.get('userPermissions') and route for the check, and apply the same fix to the other identical block (the one currently at lines 645-663) so requiresPermission is only ignored when RBAC is not configured.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/auth-refactor-test/results/initial-findings.md:
- Around line 4-127: The report was generated against the wrong branch
(wardpeet/auth-rework-takeover / PR `#17142`) instead of the target PR (`#17722`);
regenerate the runtime and build verification against the correct branch/PR and
update the findings to explicitly verify Studio-vs-API auth routing and
permission behavior (e.g., test AUTH_PROVIDER flows, Studio UI login, API token
vs session routing, and FGA/RBAC enforcement across packages/_internals/auth,
auth/workos/src/auth-provider.ts, auth/workos/src/fga-provider.ts and core
re-exports in packages/core/src/auth/); ensure logs mention the exact branch/PR
under test and include pass/fail outcomes for each scenario so the report can be
used as validation evidence for PR `#17722`.
In @.claude/skills/auth-refactor-test/results/SUMMARY.md:
- Around line 3-59: The summary document is stale (it reports PR `#17142`) and
must be regenerated for the active PR (`#17722`): rerun the dual-auth feature
tests and update the summary to reflect the correct Branch/Date/Tester, Test
Results, and Conclusion; ensure the regenerated report explicitly includes the
required auth-path validations — check Studio header handling, route auth
defaults and enforcement, and RBAC behavior by mode (e.g., admin/viewer roles
under each auth mode) — and mark any blocked providers (and migration errors)
separately so the conclusions and recommendations apply to PR `#17722`.
In @.claude/skills/auth-refactor-test/SKILL.md:
- Around line 3-93: Update the test plan in SKILL.md to target PR `#17722` (dual
Studio/API auth) instead of `#17142` and add explicit dual-auth assertions: verify
Studio header behavior (presence/validation of X-Stagehand-Studio or similar
header used by Studio flows), ensure the /api auth registration path and runtime
auth checks are exercised (auth registration functions and middleware invoked
for API routes), and add tests for custom route auth defaults and middleware
fallback behavior (examples/agent routes, AUTH_PROVIDER variations), plus update
Success Criteria and Testing Commands to include Stagehand-driven Studio header
flows and API vs Studio auth separation.
In `@packages/core/src/mastra/index.ts`:
- Around line 316-326: Update the Studio docstring for the studio-specific auth
block to accurately describe the fallback behavior: state that if studio.auth is
not configured the server will reject requests that include the
x-mastra-client-type: studio header (returning 401) rather than making Studio
publicly accessible, and note that an unauthenticated "no auth required" mode is
only for local/dev use when explicitly enabled; reference the studio.auth and
server.auth symbols and update the comment above the Studio auth configuration
in the mastra/index.ts docblock accordingly.
In `@packages/server/src/server/handlers/auth.ts`:
- Around line 102-104: The isStudioRequest function performs a case-sensitive
comparison on request.headers.get('x-mastra-client-type'), causing inconsistent
routing; update isStudioRequest to normalize the header value (e.g., safely read
the header, toLowerCase() it or use case-insensitive comparison) and handle
null/undefined before comparing to 'studio' so it matches the adapter-level
classification consistently.
In `@packages/server/src/server/server-adapter/index.ts`:
- Around line 474-497: getEffectiveAuthConfig currently treats any request with
isStudioClientTypeHeader(MASTRA_CLIENT_TYPE_HEADER) as allowed to bypass auth
when mastra.getStudio()?.auth is missing, enabling unauthenticated access;
update getEffectiveAuthConfig to never return null for studio-classified
requests unconditionally—instead, if studio auth is missing only allow a
development bypass when a gated flag (e.g., process.env.MASTRA_DEV === 'true')
is set and the route is marked as non-protected, otherwise require server-side
auth or return an explicit authMode that forces authentication; reference
getEffectiveAuthConfig, isStudioClientTypeHeader, mastra.getStudio()?.auth,
mastra.getServer()?.auth and ensure the later auth-check logic (the code that
interprets null at the auth-decision site) treats the new explicit values
correctly so headers alone cannot bypass protected routes.
In `@server-adapters/express/src/index.ts`:
- Around line 549-554: The code contains a stale call and duplicate declaration
of permissionError that reads userPermissions before it's declared; remove the
earlier lines that call this.checkRoutePermission(route, userPermissions,
hasPermission) and the duplicate declaration so only the correct call remains:
compute userPermissions from requestContext
(requestContext.get('userPermissions')) and then call
this.checkRoutePermission(route, userPermissions, hasPermission, requestContext)
once. Do the same cleanup for the duplicate block handling custom routes (the
block around loadHasPermission / userPermissions / checkRoutePermission) so
there are no out-of-order reads or duplicate permissionError declarations.
- Around line 547-548: The code currently gates RBAC checks on
this.mastra.getServer()?.auth which prevents Studio-only requests from running
requiresPermission; update the logic in the blocks around the checks (the ones
currently using this.mastra.getServer()?.auth) to instead use the effective auth
provider from the request context (e.g., requestContext.effectiveAuth or
whatever field/method on requestContext reflects the active auth/RBAC selection)
or simply call checkRoutePermission() unconditionally after checkRouteAuth();
ensure checkRoutePermission() is implemented to no-op when no RBAC provider is
configured so permissions are only enforced when an RBAC provider exists
(references: this.mastra.getServer()?.auth, requestContext, checkRouteAuth(),
checkRoutePermission(), requiresPermission).
In `@server-adapters/fastify/src/index.ts`:
- Around line 639-644: The code currently calls checkRoutePermission twice and
uses userPermissions before it's declared, causing a duplicate declaration of
permissionError and a TS compile error; remove the stale/incorrect call so you
only declare userPermissions first (via requestContext.get('userPermissions') as
string[] | undefined) and then call this.checkRoutePermission(route,
userPermissions, hasPermission, requestContext) once, eliminating the earlier
duplicate permissionError declaration; apply the same fix to the duplicate
custom-route block that mirrors this logic.
- Around line 637-638: The permission checks are incorrectly gated on
this.mastra.getServer()?.auth, which causes Studio RBAC to be skipped when only
studio.auth is present; update the guards in the blocks that read const
authConfig = this.mastra.getServer()?.auth; if (authConfig) { ... } (and the
similar guard around lines 774-775) to instead detect an RBAC provider
specifically (e.g., check authConfig?.provider === 'rbac' or a helper like
isRbacProvider(authConfig)) and only enforce requiresPermission when that RBAC
provider is configured; ensure checkRouteAuth() continues to use studio.auth for
authentication but that permission enforcement is conditional on RBAC provider
presence rather than on any auth config.
---
Outside diff comments:
In @.claude/skills/auth-refactor-test/results/CODE_REVIEW.md:
- Around line 1-179: The review file incorrectly documents PR `#17142` and claims
“no behavioral changes” while this review should target PR `#17722` (which
introduces dual-auth routing/authz behavior changes); update the CODE_REVIEW.md
header and all PR references to `#17722` and change the Branch line to the correct
branch for `#17722`, then revise the Summary/Architectural Changes/Code Quality
Assessment to explicitly evaluate behavior-level changes (inspect CompositeAuth,
routing/authz flows, and any changes to getMigrations in better-auth) and update
the Verdict to reflect the actual risk (include note to re-run smoke tests
covering dual-auth routing and authz scenarios) so the file accurately
represents PR `#17722`’s scope and risk.
In `@packages/server/src/server/handlers/auth.ts`:
- Around line 77-97: The getAuthProvider function currently falls back to server
auth for Studio requests; change its logic so that when isStudio is true and
either mastra.getStudio() is missing or studioConfig.auth is absent/not a
provider, the function returns null immediately and does not consult
mastra.getServer(); update getAuthProvider (and its checks around
mastra.getStudio, studioConfig.auth, and the existing serverConfig path) to
enforce strict Studio-only auth for isStudio requests.
In `@server-adapters/hono/src/index.ts`:
- Around line 527-544: The permission check currently only runs when
this.mastra.getServer()?.auth is set, which lets Studio-authenticated requests
bypass checkRoutePermission when server.auth is unset; change the logic to base
permission enforcement solely on the RBAC provider flag returned by
loadHasPermission() (call await loadHasPermission() regardless of
this.mastra.getServer()?.auth and only run checkRoutePermission when
hasPermission is truthy), keep using requestContext.get('userPermissions') and
route for the check, and apply the same fix to the other identical block (the
one currently at lines 645-663) so requiresPermission is only ignored when RBAC
is not configured.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1092cb68-17f2-44da-9437-cfbc44148bd1
📒 Files selected for processing (14)
.claude/skills/auth-refactor-test/SKILL.md.claude/skills/auth-refactor-test/results/CODE_REVIEW.md.claude/skills/auth-refactor-test/results/SUMMARY.md.claude/skills/auth-refactor-test/results/initial-findings.mdpackages/core/src/mastra/index.tspackages/core/src/server/index.tspackages/core/src/server/types.tspackages/server/src/server/constants.tspackages/server/src/server/handlers/auth.tspackages/server/src/server/server-adapter/index.tsserver-adapters/express/src/index.tsserver-adapters/fastify/src/index.tsserver-adapters/hono/src/index.tsserver-adapters/koa/src/index.ts
| Branch: `wardpeet/auth-rework-takeover` (based on Ward's PR #17142) | ||
|
|
||
| ## Build Results | ||
|
|
||
| ### ✅ Core Packages Built Successfully | ||
| - `@internal/auth` - New internal auth package (cache hit) | ||
| - `@internal/voice` - New internal voice package (cache hit) | ||
| - `@mastra/auth-workos` - WorkOS provider (cache hit) | ||
| - `@mastra/core` - Core package (built successfully) | ||
| - `@mastra/server` - Server package (built successfully) | ||
| - `@internal/playground` - Playground (built successfully) | ||
| - `mastra` CLI - CLI package (built successfully) | ||
|
|
||
| ### ❌ Docs Build Failed | ||
| - `mastra-docs` - Failed due to browserslist/autoprefixer issue (unrelated to auth refactor) | ||
| - Error: `BrowserslistError: Unknown browser query` | ||
|
|
||
| ## Runtime Test Results | ||
|
|
||
| ### WorkOS Auth Provider (`AUTH_PROVIDER=workos`) | ||
|
|
||
| **Server Startup:** ✅ PASS | ||
| - Server starts successfully | ||
| - Logs show `[Auth] Using WorkOS authentication` | ||
| - Server ready in ~1.6s | ||
|
|
||
| **Unauthenticated API Access:** ✅ PASS | ||
| - `curl http://localhost:4111/api/agents` returns `{"error":"Invalid or expired token"}` | ||
| - Correct 401 response for unauthenticated requests | ||
|
|
||
| **Studio Login UI:** ✅ PASS | ||
| - Login page renders correctly at http://localhost:4111 | ||
| - Shows "Sign in to continue" with WorkOS branding | ||
| - Sign in button present | ||
|
|
||
| **SSO Flow:** ⏳ NOT TESTED | ||
| - Sign in button clicked but redirect timing uncertain | ||
| - Need persistent server to fully test SSO callback | ||
|
|
||
| --- | ||
|
|
||
| ### Simple Auth Provider (`AUTH_PROVIDER=simple`) | ||
|
|
||
| **Server Startup:** ✅ PASS | ||
| - Server starts successfully | ||
| - Logs show `[Auth] Using SimpleAuth (token-based) authentication` | ||
| - Server ready in ~1.6-2.4s | ||
|
|
||
| **Unauthenticated API Access:** ✅ PASS | ||
| - `curl http://localhost:4111/api/agents` returns `{"error":"Invalid or expired token"}` | ||
| - Correctly returns 401 Unauthorized for unauthenticated requests | ||
|
|
||
| **Authenticated API Access:** ✅ PASS | ||
| - `curl -H "Authorization: Bearer test-token" http://localhost:4111/api/agents` returns full agents list (16+ agents) | ||
| - Token-based auth working correctly | ||
|
|
||
| **Studio Login UI:** ✅ PASS | ||
| - Login page renders correctly at http://localhost:4111 | ||
| - Shows "Sign in to continue" with Mastra branding | ||
| - Sign in button present | ||
|
|
||
| **Overall:** SimpleAuth working correctly on Ward's auth-rework branch | ||
|
|
||
| ## Issues Found | ||
|
|
||
| ### 1. SQLite Lock Contention (Unrelated to Auth) | ||
| - `SQLITE_BUSY_SNAPSHOT: database is locked` errors | ||
| - Affects workflow event processing | ||
| - Pre-existing issue, not caused by auth refactor | ||
|
|
||
| ### 2. Server Timeout in Shell | ||
| - Dev server terminates when shell command times out | ||
| - Need persistent background process for browser testing | ||
|
|
||
| ## Issues Resolved | ||
|
|
||
| ### SimpleAuth Authentication - WORKS CORRECTLY ✅ | ||
| - Initially appeared to fail due to testing timing issues | ||
| - Re-tested and confirmed working: | ||
| - Unauthenticated: Returns `{"error":"Invalid or expired token"}` (401) | ||
| - Authenticated with `test-token`: Returns full agents list (200) | ||
|
|
||
| ## Auth Provider Configuration | ||
|
|
||
| The example app supports multiple auth providers via `AUTH_PROVIDER` env var: | ||
|
|
||
| ```bash | ||
| # In examples/agent/.env | ||
| AUTH_PROVIDER=workos # Currently active | ||
| ``` | ||
|
|
||
| Available providers: | ||
| - `simple` - Token-based (test-token, viewer-token) | ||
| - `workos` - Enterprise SSO + FGA ✅ TESTED | ||
| - `better-auth` - Credentials auth | ||
| - `okta` - Okta SSO + RBAC | ||
| - `auth0-okta` - Cross-provider | ||
| - `cloud` - Mastra platform OAuth | ||
| - `composite` - Multi-provider fallback | ||
| - `studio` - Platform Studio auth | ||
|
|
||
| ## Key Files in Ward's Refactor | ||
|
|
||
| ### New Internal Package | ||
| - `packages/_internals/auth/src/` - All auth internals | ||
| - `ee/interfaces/fga.ts` - FGA types | ||
| - `ee/interfaces/rbac.ts` - RBAC types | ||
| - `session/` - Session management | ||
| - `provider/` - Base auth provider | ||
|
|
||
| ### Auth Provider Updates | ||
| - `auth/workos/src/fga-provider.ts` - Now imports from `@internal/auth/ee` | ||
| - `auth/workos/src/auth-provider.ts` - Uses internal types | ||
|
|
||
| ### Core Re-exports | ||
| - `packages/core/src/auth/` - Re-export stubs for backward compatibility | ||
|
|
||
| ## Next Steps | ||
|
|
||
| 1. [ ] Test simple auth provider (no SSO flow needed) | ||
| 2. [ ] Test better-auth provider (credentials flow) | ||
| 3. [ ] Set up persistent dev server for SSO testing | ||
| 4. [ ] Run FGA tests with WorkOS | ||
| 5. [ ] Document any regressions found |
There was a problem hiding this comment.
Findings are not attributable to this PR’s implementation.
This report documents a different branch/PR (#17142), so it cannot be used as validation evidence for #17722 dual auth changes. Please regenerate findings against this PR and include explicit outcomes for the new Studio-vs-API auth routing and permission behavior.
Based on learnings from the provided PR objective metadata, the evaluated branch/PR here does not match the target change under review.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/auth-refactor-test/results/initial-findings.md around lines 4
- 127, The report was generated against the wrong branch
(wardpeet/auth-rework-takeover / PR `#17142`) instead of the target PR (`#17722`);
regenerate the runtime and build verification against the correct branch/PR and
update the findings to explicitly verify Studio-vs-API auth routing and
permission behavior (e.g., test AUTH_PROVIDER flows, Studio UI login, API token
vs session routing, and FGA/RBAC enforcement across packages/_internals/auth,
auth/workos/src/auth-provider.ts, auth/workos/src/fga-provider.ts and core
re-exports in packages/core/src/auth/); ensure logs mention the exact branch/PR
under test and include pass/fail outcomes for each scenario so the report can be
used as validation evidence for PR `#17722`.
| **Branch:** `wardpeet/auth-rework` | ||
| **Date:** June 8, 2026 | ||
| **Tester:** MastraCode | ||
|
|
||
| ## Summary | ||
|
|
||
| Ward's auth refactor (PR #17142) has been tested across 3 auth providers. The refactor extracts auth infrastructure into `@internal/auth` package and is **working correctly**. | ||
|
|
||
| ## Test Results | ||
|
|
||
| | Provider | API Auth | Browser SSO | Status | | ||
| |----------|----------|-------------|--------| | ||
| | SimpleAuth (token-based) | ✅ Working | N/A | **PASS** | | ||
| | WorkOS (SSO + FGA) | ✅ Working | ✅ Working | **PASS** | | ||
| | better-auth (credentials) | ⚠️ Not tested | ⚠️ Not tested | **BLOCKED** | | ||
|
|
||
| ## Detailed Findings | ||
|
|
||
| ### SimpleAuth (token-based) ✅ PASS | ||
| - **API Authentication:** Working correctly | ||
| - Unauthenticated requests return `{"error":"Invalid or expired token"}` | ||
| - Authenticated requests with valid `test-token` return full agents list | ||
| - **RBAC:** Working - admin token gets `admin` role, viewer token gets `viewer` role | ||
|
|
||
| ### WorkOS (SSO + FGA) ✅ PASS | ||
| - **API Authentication:** Working correctly | ||
| - Unauthenticated requests return `{"error":"Invalid or expired token"}` | ||
| - **Browser SSO:** Working correctly | ||
| - Login page displays with Mastra branding | ||
| - Clicking "Sign in" redirects to WorkOS AuthKit | ||
| - AuthKit shows email field and social login options (Google, Microsoft, GitHub, Apple) | ||
|
|
||
| ### better-auth (credentials) ⚠️ BLOCKED | ||
| - **Issue:** `getMigrations is not a function` error on startup | ||
| - **Root cause:** better-auth package API change - `getMigrations` import from `better-auth/db` is no longer valid | ||
| - **Impact:** Server fails to start with better-auth provider | ||
| - **Recommendation:** Update better-auth provider to use new migration API | ||
| - **NOT RELATED TO AUTH REFACTOR** - this is a pre-existing configuration issue | ||
|
|
||
| ## Known Issues (Unrelated to Auth Refactor) | ||
|
|
||
| ### SQLite Database Lock Errors | ||
| - **Error:** `SQLITE_BUSY_SNAPSHOT: database is locked` | ||
| - **Location:** WorkflowEventProcessor.processWorkflowStepEnd | ||
| - **Impact:** Occasional workflow processing failures | ||
| - **Root cause:** Concurrent SQLite access in workflow processor | ||
| - **Status:** Pre-existing issue, not introduced by auth refactor | ||
|
|
||
| ## Conclusion | ||
|
|
||
| Ward's auth refactor is **ready for merge**. The core auth infrastructure (authentication, RBAC, FGA) is working correctly for the tested providers. The better-auth issue is a configuration problem unrelated to the refactor. | ||
|
|
||
| ## Recommendations | ||
|
|
||
| 1. **Merge PR #17142** - The auth refactor is working correctly | ||
| 2. **Fix better-auth provider** - Update to use new migration API (separate PR) | ||
| 3. **Investigate SQLite locks** - Address concurrent database access (separate issue) |
There was a problem hiding this comment.
Summary is stale and not valid for this PR.
The branch/PR context and conclusions here are from #17142, not #17722. Please regenerate this summary from tests tied to the dual-auth feature and include the required auth-path checks (Studio header handling, route auth defaults/enforcement, RBAC behavior by mode).
As per coding guidelines, auth behavior requirements must be explicitly validated and this summary currently does not cover them for the active PR scope.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/auth-refactor-test/results/SUMMARY.md around lines 3 - 59,
The summary document is stale (it reports PR `#17142`) and must be regenerated for
the active PR (`#17722`): rerun the dual-auth feature tests and update the summary
to reflect the correct Branch/Date/Tester, Test Results, and Conclusion; ensure
the regenerated report explicitly includes the required auth-path validations —
check Studio header handling, route auth defaults and enforcement, and RBAC
behavior by mode (e.g., admin/viewer roles under each auth mode) — and mark any
blocked providers (and migration errors) separately so the conclusions and
recommendations apply to PR `#17722`.
Source: Coding guidelines
| Test Ward's auth refactor (PR #17142) which extracts auth internals into `@internal/auth` package. | ||
|
|
||
| ## Context | ||
|
|
||
| Ward's PR moves auth interfaces, providers, sessions, and EE auth helpers from `@mastra/core` into `@internal/auth`. The goal is to verify that all auth patterns still work correctly after the refactor. | ||
|
|
||
| ## Auth Patterns to Test | ||
|
|
||
| The `examples/agent` app supports multiple auth providers via `AUTH_PROVIDER` env var: | ||
|
|
||
| | Provider | Type | Features | Test Priority | | ||
| |:---------|:-----|:---------|:--------------| | ||
| | `simple` | Token-based | API keys, static RBAC | HIGH | | ||
| | `workos` | Enterprise SSO | SAML, OIDC, FGA, dynamic roles | HIGH | | ||
| | `better-auth` | Credentials | Username/password, SQLite | MEDIUM | | ||
| | `okta` | Enterprise | SSO + RBAC | MEDIUM | | ||
| | `auth0-okta` | Cross-provider | Auth0 auth + Okta RBAC | LOW | | ||
| | `cloud` | Platform OAuth | PKCE flow | LOW | | ||
| | `composite` | Multi-provider | SimpleAuth + CloudAuth fallback | LOW | | ||
| | `studio` | Platform Studio | Sealed session + Bearer token | LOW | | ||
|
|
||
| ## Test Scenarios Per Provider | ||
|
|
||
| ### 1. Build & Start | ||
| - [ ] `pnpm build` completes without errors | ||
| - [ ] `pnpm mastra:dev` starts without auth errors | ||
| - [ ] Server logs show correct auth provider initialized | ||
|
|
||
| ### 2. Unauthenticated Access | ||
| - [ ] Public routes accessible without token | ||
| - [ ] Protected routes return 401 without token | ||
| - [ ] Error message is correct format | ||
|
|
||
| ### 3. Authenticated Access | ||
| - [ ] Valid token returns 200 on protected routes | ||
| - [ ] Invalid token returns 401 | ||
| - [ ] User object is correctly populated | ||
|
|
||
| ### 4. RBAC (if configured) | ||
| - [ ] Admin role can access all routes | ||
| - [ ] Viewer role blocked from write routes | ||
| - [ ] Role derivation from user works | ||
| - [ ] Permissions checked correctly | ||
|
|
||
| ### 5. FGA (WorkOS only) | ||
| - [ ] Resource-level checks work | ||
| - [ ] Role assignments work | ||
| - [ ] Public-by-default behavior correct | ||
| - [ ] Ownership registration works | ||
|
|
||
| ## Testing Commands | ||
|
|
||
| ```bash | ||
| # Set auth provider | ||
| export AUTH_PROVIDER=simple # or workos, better-auth, etc. | ||
|
|
||
| # Start dev server | ||
| cd examples/agent && pnpm mastra:dev | ||
|
|
||
| # Test endpoints (in another terminal) | ||
| curl http://localhost:4111/api/agents | ||
| curl -H "Authorization: Bearer test-token" http://localhost:4111/api/agents | ||
| ``` | ||
|
|
||
| ## Browser Testing | ||
|
|
||
| Use Stagehand browser automation to test auth flows that require UI interaction: | ||
|
|
||
| 1. Navigate to Studio at http://localhost:4111 | ||
| 2. Check login redirect behavior | ||
| 3. Test SSO callback flow (WorkOS/Okta) | ||
| 4. Verify user session persistence | ||
| 5. Test logout and session cleanup | ||
|
|
||
| ## Files Changed in Ward's PR | ||
|
|
||
| Key files to watch for issues: | ||
| - `packages/_internals/auth/src/` - new auth package | ||
| - `auth/workos/src/` - WorkOS provider (imports from @internal/auth) | ||
| - `auth/better-auth/src/` - Better Auth provider | ||
| - `auth/okta/src/` - Okta provider | ||
| - `packages/core/src/auth/` - re-export stubs | ||
|
|
||
| ## Success Criteria | ||
|
|
||
| 1. All auth providers build without errors | ||
| 2. All providers start without runtime errors | ||
| 3. Authentication works for each provider | ||
| 4. RBAC enforcement works where configured | ||
| 5. FGA enforcement works (WorkOS) | ||
| 6. No regressions from current main branch |
There was a problem hiding this comment.
Test plan targets the wrong PR and misses required dual-auth assertions.
This skill is scoped to PR #17142 (@internal/auth extraction), but this PR is #17722 (dual Studio/API auth). As written, it won’t validate the new security contracts (e.g., studio header behavior, /api auth registration/auth checks, custom route auth defaults/middleware). Please retarget scenarios to #17722 and add explicit checks for those required behaviors before relying on results.
Based on learnings from the provided PR objectives and review stack, this change is for dual auth routing, not the earlier extraction refactor.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/auth-refactor-test/SKILL.md around lines 3 - 93, Update the
test plan in SKILL.md to target PR `#17722` (dual Studio/API auth) instead of
`#17142` and add explicit dual-auth assertions: verify Studio header behavior
(presence/validation of X-Stagehand-Studio or similar header used by Studio
flows), ensure the /api auth registration path and runtime auth checks are
exercised (auth registration functions and middleware invoked for API routes),
and add tests for custom route auth defaults and middleware fallback behavior
(examples/agent routes, AUTH_PROVIDER variations), plus update Success Criteria
and Testing Commands to include Stagehand-driven Studio header flows and API vs
Studio auth separation.
Source: Coding guidelines
Fixes build errors caused by duplicate variable declarations during merge conflict resolution. Also removes CODE_REVIEW.md that referenced the wrong PR. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
…er auth When x-mastra-client-type: studio header is present but studio.auth is not configured, return null (resulting in 401) instead of falling back to server auth. This prevents external API consumers from spoofing the studio header to bypass Studio-only authentication. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
server-adapters/koa/src/index.ts (1)
952-953: 💤 Low valueConsider using the local
requestContextvariable for consistency.The FGA check uses
ctx.state.requestContextdirectly while the permission check above (line 939) uses the localrequestContextvariable declared at line 929. For consistency with the pattern used inhandleMatchedRoute(line 489), consider using the local variable here as well.Suggested change
- const fgaError = await checkRouteFGA(server.mastra, serverRoute, ctx.state.requestContext, { + const fgaError = await checkRouteFGA(server.mastra, serverRoute, requestContext, {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server-adapters/koa/src/index.ts` around lines 952 - 953, The FGA check is using ctx.state.requestContext directly while the surrounding code and earlier permission check use the local requestContext variable; update the call to checkRouteFGA to pass the local requestContext instead of ctx.state.requestContext (i.e., call checkRouteFGA(server.mastra, serverRoute, requestContext, {...})), mirroring the pattern used in handleMatchedRoute and the prior permission check so the same request context object is used consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@server-adapters/koa/src/index.ts`:
- Around line 952-953: The FGA check is using ctx.state.requestContext directly
while the surrounding code and earlier permission check use the local
requestContext variable; update the call to checkRouteFGA to pass the local
requestContext instead of ctx.state.requestContext (i.e., call
checkRouteFGA(server.mastra, serverRoute, requestContext, {...})), mirroring the
pattern used in handleMatchedRoute and the prior permission check so the same
request context object is used consistently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 98ac807f-5756-4554-8fef-f70fc090e1bc
📒 Files selected for processing (4)
packages/server/src/server/handlers/auth.tsserver-adapters/express/src/index.tsserver-adapters/fastify/src/index.tsserver-adapters/koa/src/index.ts
💤 Files with no reviewable changes (2)
- server-adapters/fastify/src/index.ts
- server-adapters/express/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/server/src/server/handlers/auth.ts
The auth middleware sets both 'mastra__userPermissions' and 'userPermissions' for backward compatibility, but main branch uses the namespaced key. Restoring consistency with main. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
There was a problem hiding this comment.
♻️ Duplicate comments (2)
server-adapters/fastify/src/index.ts (1)
637-650:⚠️ Potential issue | 🟠 Major | ⚡ Quick winThis still skips Studio permission enforcement.
These permission blocks still depend on
this.mastra.getServer()?.auth, even thoughcheckRouteAuth()can now succeed viastudio.auth. In a Studio-only setup that keepscheckRoutePermission(...)from running, sorequiresPermissionis silently bypassed for Studio requests. Use the effective request-scoped provider fromrequestContext, or remove this legacy guard and letcheckRoutePermission(..., requestContext)decide whether RBAC applies.Also applies to: 771-798
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server-adapters/fastify/src/index.ts` around lines 637 - 650, The current guard uses this.mastra.getServer()?.auth which skips Studio-scoped auth and causes requiresPermission to be bypassed; update the permission check to use the request-scoped provider from requestContext (or remove the legacy authConfig guard) so checkRoutePermission(route, userPermissions, hasPermission, requestContext) runs for Studio requests too. Specifically, replace the authConfig conditional around the block that calls loadHasPermission() and checkRoutePermission (symbols: this.mastra.getServer()?.auth, requestContext, loadHasPermission, checkRoutePermission, mastra__userPermissions) so the code either reads the effective auth provider from requestContext before deciding or simply calls checkRoutePermission unconditionally and lets it determine RBAC applicability; apply the same change to the analogous block guarded by authConfig at the other occurrence noted in the file.server-adapters/express/src/index.ts (1)
547-560:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon’t gate permission checks on
server.auth.After
checkRouteAuth()a request may already be authenticated viastudio.auth, but these blocks still short-circuit onthis.mastra.getServer()?.auth. In a Studio-only deployment that skipscheckRoutePermission(...)entirely, sorequiresPermissionis never enforced for Studio traffic. Gate on the effective provider carried inrequestContext, or callcheckRoutePermission(..., requestContext)without the legacyserver.authguard and let it no-op when no RBAC provider is configured.Also applies to: 654-673
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server-adapters/express/src/index.ts` around lines 547 - 560, Remove the legacy gate that checks this.mastra.getServer()?.auth before running permission checks: always determine RBAC from the effective provider in requestContext (or simply call checkRoutePermission(route, userPermissions, hasPermission, requestContext) without the this.mastra.getServer()?.auth guard) so Studio-authenticated requests still enforce requiresPermission; update the block around loadHasPermission() and the similar block at the other location (the 654-673 instance) to call checkRoutePermission unconditionally (letting checkRoutePermission/no-op when no RBAC provider is configured) and use requestContext to derive the provider/permissions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@server-adapters/express/src/index.ts`:
- Around line 547-560: Remove the legacy gate that checks
this.mastra.getServer()?.auth before running permission checks: always determine
RBAC from the effective provider in requestContext (or simply call
checkRoutePermission(route, userPermissions, hasPermission, requestContext)
without the this.mastra.getServer()?.auth guard) so Studio-authenticated
requests still enforce requiresPermission; update the block around
loadHasPermission() and the similar block at the other location (the 654-673
instance) to call checkRoutePermission unconditionally (letting
checkRoutePermission/no-op when no RBAC provider is configured) and use
requestContext to derive the provider/permissions.
In `@server-adapters/fastify/src/index.ts`:
- Around line 637-650: The current guard uses this.mastra.getServer()?.auth
which skips Studio-scoped auth and causes requiresPermission to be bypassed;
update the permission check to use the request-scoped provider from
requestContext (or remove the legacy authConfig guard) so
checkRoutePermission(route, userPermissions, hasPermission, requestContext) runs
for Studio requests too. Specifically, replace the authConfig conditional around
the block that calls loadHasPermission() and checkRoutePermission (symbols:
this.mastra.getServer()?.auth, requestContext, loadHasPermission,
checkRoutePermission, mastra__userPermissions) so the code either reads the
effective auth provider from requestContext before deciding or simply calls
checkRoutePermission unconditionally and lets it determine RBAC applicability;
apply the same change to the analogous block guarded by authConfig at the other
occurrence noted in the file.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4da56929-aa58-4adc-ac6c-414116912f07
📒 Files selected for processing (4)
server-adapters/express/src/index.tsserver-adapters/fastify/src/index.tsserver-adapters/hono/src/index.tsserver-adapters/koa/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- server-adapters/hono/src/index.ts
- server-adapters/koa/src/index.ts
Dual auth is now opt-in: - If studio.auth is NOT configured: Studio uses server.auth (existing behavior) - If studio.auth IS configured: Studio uses it exclusively (no fallback) This preserves backward compatibility while allowing users to opt into strict Studio/API auth isolation. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
Response to CodeRabbit: Dual Auth Fallback BehaviorCodeRabbit flagged We're intentionally keeping the fallback for backward compatibility, but it's now opt-in:
This means:
The security concern is valid, but only applies when dual auth is explicitly enabled — and in that case, we DO enforce strict isolation with no fallback. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/server/src/server/handlers/auth.ts (2)
223-237:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPreserve
fgaon the session-refresh retry path.The first
buildCapabilities()call includes{ rbac, fga, apiPrefix }, but the retry afterrefreshSession()dropsfga. That makes/auth/capabilitiesreturn a weaker capability set only when the session had to be refreshed.Suggested fix
const refreshedCapabilities = await buildCapabilities(auth, refreshedRequest, { rbac, + fga, apiPrefix: routePrefix, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/server/src/server/handlers/auth.ts` around lines 223 - 237, The retry path after a successful refreshSession call calls buildCapabilities without passing through the fga parameter, causing weaker capabilities; update the retry call to buildCapabilities(auth, refreshedRequest, { rbac, fga, apiPrefix: routePrefix }) so it mirrors the original call that included fga (ensure the object passed to buildCapabilities in the refreshedSession branch contains fga alongside rbac and apiPrefix), locating this change around the refreshedSession / getSessionHeaders / extractCookieFromHeaders flow where refreshedRequest is constructed.
769-783:⚠️ Potential issue | 🟠 Major | ⚡ Quick winResolve role permissions from the effective auth mode.
Every other auth endpoint in this file became Studio-aware, but this one still hard-codes
getRBACProvider(mastra). In a dual-auth deployment, a Studio request can be authenticated/authorized with Studio RBAC and then have"View as role"resolved from server RBAC, which returns the wrong permission set.Suggested fix
-import { MASTRA_USER_PERMISSIONS_KEY } from '../constants'; +import { MASTRA_AUTH_MODE_KEY, MASTRA_USER_PERMISSIONS_KEY } from '../constants'; ... - const rbac = getRBACProvider(mastra); + const authMode = requestContext?.get(MASTRA_AUTH_MODE_KEY); + const rbac = getRBACProvider(mastra, authMode === 'studio'); if (!rbac?.getPermissionsForRole) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/server/src/server/handlers/auth.ts` around lines 769 - 783, The code currently always calls getRBACProvider(mastra) which ignores the requestContext/studio effective auth mode; change it to use the same Studio-aware RBAC resolution used by other handlers (i.e., resolve the RBAC provider using requestContext/effective auth mode instead of raw mastra), then validate the provider supports getPermissionsForRole and call provider.getPermissionsForRole(roleId) as before; replace the direct getRBACProvider(mastra) call with the helper that accepts requestContext (or the equivalent Studio-aware resolver used elsewhere in this file) so "View as role" permissions are resolved from the correct RBAC backend.
♻️ Duplicate comments (1)
packages/server/src/server/handlers/auth.ts (1)
107-109:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize Studio detection with the shared helper.
packages/server/src/server/constants.tsalready treatsx-mastra-client-typecase-insensitively. This check is still case-sensitive, so the same request can be classified as Studio in the adapter path and non-Studio here.Suggested fix
-import { MASTRA_USER_PERMISSIONS_KEY } from '../constants'; +import { MASTRA_CLIENT_TYPE_HEADER, MASTRA_USER_PERMISSIONS_KEY, isStudioClientTypeHeader } from '../constants'; ... function isStudioRequest(request: Request): boolean { - return request.headers.get('x-mastra-client-type') === 'studio'; + return isStudioClientTypeHeader(request.headers.get(MASTRA_CLIENT_TYPE_HEADER) ?? undefined); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/server/src/server/handlers/auth.ts` around lines 107 - 109, The isStudioRequest function currently does a case-sensitive check against the header 'x-mastra-client-type'; replace that direct comparison in isStudioRequest with the shared normalization helper exported from constants.ts (the helper that normalizes/reads the x-mastra-client-type header) so Studio detection is consistent across codepaths—import and call the shared helper inside isStudioRequest and return its Studio boolean result (or compare its normalized value to 'studio') instead of using request.headers.get(...) directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/server/src/server/handlers/auth.ts`:
- Around line 223-237: The retry path after a successful refreshSession call
calls buildCapabilities without passing through the fga parameter, causing
weaker capabilities; update the retry call to buildCapabilities(auth,
refreshedRequest, { rbac, fga, apiPrefix: routePrefix }) so it mirrors the
original call that included fga (ensure the object passed to buildCapabilities
in the refreshedSession branch contains fga alongside rbac and apiPrefix),
locating this change around the refreshedSession / getSessionHeaders /
extractCookieFromHeaders flow where refreshedRequest is constructed.
- Around line 769-783: The code currently always calls getRBACProvider(mastra)
which ignores the requestContext/studio effective auth mode; change it to use
the same Studio-aware RBAC resolution used by other handlers (i.e., resolve the
RBAC provider using requestContext/effective auth mode instead of raw mastra),
then validate the provider supports getPermissionsForRole and call
provider.getPermissionsForRole(roleId) as before; replace the direct
getRBACProvider(mastra) call with the helper that accepts requestContext (or the
equivalent Studio-aware resolver used elsewhere in this file) so "View as role"
permissions are resolved from the correct RBAC backend.
---
Duplicate comments:
In `@packages/server/src/server/handlers/auth.ts`:
- Around line 107-109: The isStudioRequest function currently does a
case-sensitive check against the header 'x-mastra-client-type'; replace that
direct comparison in isStudioRequest with the shared normalization helper
exported from constants.ts (the helper that normalizes/reads the
x-mastra-client-type header) so Studio detection is consistent across
codepaths—import and call the shared helper inside isStudioRequest and return
its Studio boolean result (or compare its normalized value to 'studio') instead
of using request.headers.get(...) directly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: be37e751-9391-47f3-b283-ad57381f0198
📒 Files selected for processing (1)
packages/server/src/server/handlers/auth.ts
RBAC permission checks now trigger when either studio.auth OR server.auth is configured. This ensures RBAC works correctly when only studio.auth is configured (dual auth opt-in scenario). Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
All server adapters (Express, Fastify, Koa, NestJS) now pass the web-standard Request object to route handlers via the handlerParams context. This allows auth handlers to access request headers and cookies for dual auth routing. - Added 'request' property to ServerContext type - Express adapter: converts req to web Request via toWebRequest() - Fastify adapter: converts request to web Request via toWebRequest() - Koa adapter: converts ctx to web Request via toWebRequest() - NestJS adapter: converts req to web Request via toWebRequest() - Hono adapter already had this via c.req.raw Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
The 'should skip API routes regardless of studioBase path' test in option-studio-base.test.ts started failing after PR #17722 added dual auth support, because the auth middleware chain now calls mastra.getStudio() unconditionally. The test mock did not include getStudio, so the call threw TypeError, the mocked errorHandler returned undefined, and app.request resolved to undefined. Add getStudio: vi.fn(() => undefined) to mockMastra so the auth middleware can short-circuit cleanly and the request reaches the agents handler as intended. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-7) <noreply@mastra.ai>
… fix deployer studioBase test (#17805) This PR fixes two unrelated failures showing up on `main` CI: --- ## 1. Restore `responseModel: ""` in two trace snapshots ### What Re-add `"responseModel": ""` to two integration-test trace snapshots: - `observability/mastra/src/__snapshots__/model-step-timing-trace.json` - `observability/mastra/src/__snapshots__/workflow-create-step-agent-trace.json` ### Why Two PRs landed within 7 minutes of each other yesterday, fixing the same regression in opposite directions and unaware of each other: 1. **#17767 → commit [af47e5a](https://github.com/mastra-ai/mastra/pull/17767/commits/af47e5a4c067fa1ab92192fb09f013601ca6586d)** ("test(observability): align trace snapshots with direct workflow engine default") Removed `"responseModel": ""` from these snapshots to match the broken runtime (which emitted the field as omitted/undefined after #17727 switched the agentic loop default to the direct workflow engine). Merged at **20:12 UTC**. 2. **#17795** ("fix(core): preserve modelId empty-string fallback in step-finish response") Fixed the runtime in `packages/core/src/stream/base/output.ts` so the `...otherMetadata` spread no longer overwrites the explicit `modelId: '`' fallback. Runtime now consistently emits `responseModel: ""` again regardless of execution path. Merged at **20:19 UTC**. Result on main: - Runtime: `responseModel: ""` - Snapshot: (omitted) - -> `Expected: undefined, Actual: ""` snapshot mismatch in both tests. This PR restores the snapshots to match the now-correct runtime behavior. --- ## 2. Fix flaky `per-thread-pubsub-multiprocess.test.ts` split-brain test ### What Add a warm-up publish loop in the `"worker threads do not split-brain when many clients recover concurrently after broker death"` test so the assertion publish only runs after every clients subscribe frame has been processed by the newly-elected broker. ### Why Surfaced on CI run [27305865390](https://github.com/mastra-ai/mastra/actions/runs/27305865390/job/80671209414?pr=17684) and reproduces locally at ~13% rate (4/30 runs) under load. **Race condition:** 1. Old broker dies. Surviving clients all race to recover. 2. The election-lock winner becomes the new broker; losers reconnect via `#connectClient`. 3. On the new brokers side, `#handleBrokerClient` registers each TCP connection in `#brokerClients` **immediately on accept** — bumping `remoteClientCount` — *before* the clients `subscribe` frame has arrived/been parsed. 4. `waitForElectedThreadBroker` considered election complete once `remoteClientCount === N`. That only confirms TCP accepts, not subscription state. 5. The test publishes immediately. The broker fans out via `client.subscriptions.has(topic)`, silently skipping clients whose `subscribe` frame hasnt been processed yet. Those clients time out on `event-received`. ### Why the fix needs no production-code change The protocol already provides a free synchronization signal: the broker only emits an `event` frame to clients whose `subscribe` frame it has processed (`unix-socket-pubsub.ts:516-517`, where it sets `client.subscriptions.add(topic)` *before* fanout in `#publishFromBroker`). So instead of adding a new test-only introspection getter on `UnixSocketPubSub`, the test now publishes `warmup-N` events in a retry loop until **every** client confirms receipt. That observation directly proves every subscription is registered on the new broker. Then the real `split-brain-check` event runs against a known-good fanout state. This is a purely test-side change. No runtime behavior or public API surface changed. ### Verification After the fix: - Split-brain test (alone): **50/50 pass** locally - Full file (6 tests, 20 runs): **20/20 pass** - `packages/core` events suite (109 tests): all green - `observability/mastra`: 917 pass / 2 skipped ``` cd packages/core && pnpm vitest run src/events/ cd observability/mastra && pnpm test ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 This PR fixes two failing CI tests: it updates snapshots so they match the runtime again, and it stabilizes a flaky pub/sub test by adding a warm-up step so all clients are actually subscribed before the test publishes events. ## Snapshot Updates Re-added `responseModel: ""` to two observability trace snapshots so they match the corrected runtime output: - observability/mastra/src/__snapshots__/model-step-timing-trace.json - observability/mastra/src/__snapshots__/workflow-create-step-agent-trace.json Background: two PRs landed in quick succession — one removed the field from snapshots to match a runtime regression; a later fix restored the runtime to emit `responseModel: ""`. These snapshot updates realign tests with the runtime. ## Split-Brain PubSub Test Stabilization Test fixed: "worker threads do not split-brain when many clients recover concurrently after broker death". Root cause: after broker re-election the broker accepted TCP connections before processing clients' `subscribe` frames; the test published immediately and some clients missed events. Fix: after re-election, the test runs a warm-up publish loop that retries publishing distinct warmup events and waits until every client reports receipt before performing the assertion publish. This leverages the broker behavior that it only emits `event` frames after a client's `subscribe` has been processed, ensuring subscriptions are registered first. Scope: test-only change; no production code or public API modifications. Verified to improve local and CI test stability. ## Other Test Changes - packages/deployer/src/server/__tests__/option-studio-base.test.ts: added a mock `mockMastra.getStudio` returning `undefined` to clarify the mocked server shape used in tests. ## Impact - No runtime or public API changes; all modifications are test-only (snapshots and tests). - Improves CI reliability by restoring snapshot expectations and reducing flakiness in the pub/sub recovery test. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- ## 3. Fix `option-studio-base.test.ts` deployer test regression ### What Add `getStudio: vi.fn(() => undefined)` to the `mockMastra` object in `packages/deployer/src/server/__tests__/option-studio-base.test.ts`. ### Why After [#17722](#17722) ("feat(auth): add dual auth system for Studio vs API"), the request auth middleware in the Hono adapter calls `this.mastra.getStudio()` unconditionally to look up `studio.auth` for dual-auth resolution. The `mockMastra` in this test never included `getStudio`, so the call threw `TypeError: this.mastra.getStudio is not a function`. The deployer's `errorHandler` is mocked with `vi.fn()` (returns `undefined`), Hono's `onError` returns that `undefined`, and `app.request('/api/agents')` resolves to `undefined` — so the test fails at `expect(response.status).toBe(200)` with `TypeError: Cannot read properties of undefined (reading 'status')`. This is a real `Mastra` API method (`packages/core/src/mastra/index.ts:3824`), so on production code paths it is always defined. The fix is in the test mock, not in production — adding defensive optional-chaining on a public method would just mask test gaps elsewhere. CI: [PR #17788 Shard 4](https://github.com/mastra-ai/mastra/actions/runs/27307427076/job/80670641695?pr=17788), [PR #17805 Shard 4](https://github.com/mastra-ai/mastra/actions/runs/27316008527/job/80681068697). --------- Co-authored-by: Mastra Code (anthropic/claude-opus-4-7) <noreply@mastra.ai>
…8111) ## Summary Adds `setStudio()` method to Mastra class, mirroring the existing `setServer()` method. This enables runtime configuration of studio auth/RBAC separately from server config. ## Why This Is Needed Platform deploy wrappers need to implement **dual auth**: - **Studio requests** (via `*.studio.mastra.cloud`): Use platform auth (`MastraAuthStudio`) - **Server requests** (via `*.server.mastra.cloud`): Open API access (no auth or user-configured auth) The dual auth flow: 1. Edge router sets `x-mastra-client-type: studio` header based on domain 2. `@mastra/server`'s `getEffectiveAuthConfig()` routes to `studio.auth` for studio requests 3. **Wrapper calls `setStudio()` to inject platform auth for studio requests** Without `setStudio()`, wrappers can only modify `server.auth` which applies to **all** requests, breaking the dual auth contract. ## Changes - Added `setStudio(studio: StudioConfig): void` method to `Mastra` class - Follows same pattern as `setServer()` ## Usage ```typescript // In platform deploy wrapper mastra.setStudio({ auth: new MastraAuthStudio(), rbac: new MastraRBACStudio({ roleMapping: { admin: ['*'] } }), }); ``` ## Related - Complements PR #17722 (dual auth system documentation) - Enables unified runtime deploys in mastra-ai/platform <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 This PR lets Mastra configure “Studio” login/security separately from your API server’s login/security. So the admin dashboard (Studio) can use one auth/RBAC setup while the server API uses another—without mixing them up. ## Summary This PR adds a new `setStudio(studio: StudioConfig): void` method to the `Mastra` class (`packages/core/src/mastra/index.ts`) to configure Studio authentication and RBAC at runtime, independently from server configuration. ### What changed - **New API:** `public setStudio(studio: StudioConfig): void` - The method stores the provided Studio configuration on the instance’s private `#studio` field, mirroring the existing `setServer()` pattern. - **Dual-auth support (opt-in):** - **Studio requests** (`*.studio.mastra.cloud`) can use Studio-specific auth (e.g., `MastraAuthStudio`) and Studio-specific RBAC. - **Server requests** (`*.server.mastra.cloud`) can continue using server auth (or open API access) without being affected by Studio settings. - An edge router sets `x-mastra-client-type: studio` so the server-side routing logic can apply `studio.auth` for Studio-marked requests. ### Tests & documentation - Added a `setStudio` test suite verifying: - a new `Mastra` instance starts with `getStudio()` unset, - `setStudio()` sets/replaces the Studio config, - updates can be “merged” by passing an object derived from `...mastra.getStudio()` while changing nested fields (e.g., updating `auth` while preserving `rbac`). - Added a changeset documenting the new `setStudio()` feature and providing an example usage. ### Example ```ts mastra.setStudio({ auth: new MastraAuthStudio(), rbac: new MastraRBACStudio({ roleMapping: { admin: ['*'] } }), }); ``` <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…y/hono/koa (#18319) ## Summary Fixes a 500-on-every-request crash for projects that deploy with `@mastra/fastify`, `@mastra/hono`, or `@mastra/koa` when the resolved `@mastra/core` is < 1.42.0 (i.e. before `Mastra#getStudio()` was added). The dual-auth system added in #17722 calls `this.mastra.getStudio()` non-optionally during the RBAC pre-check: ```ts const hasAuth = this.mastra.getStudio()?.auth || this.mastra.getServer()?.auth; ``` On older core versions that method doesn't exist on the `Mastra` class, so every request throws: ``` TypeError: this.mastra.getStudio is not a function ``` …and returns a 500 — even for projects with **no auth configured at all**. Reported by an internal user whose project regressed from a green deploy to all-500s on the next deploy without any user-side code change. ## Fix Switch all 6 call sites to optional chaining (`getStudio?.()`) so the chain harmlessly evaluates to `undefined` and the adapter falls back to server-only auth. This matches the pattern already applied in `@mastra/server` in #18075 (see `packages/server/src/server/server-adapter/index.ts` lines 488, 513, 526). | Adapter | Call sites fixed | |---|---| | `@mastra/fastify` | 2 (`registerAuthMiddleware`, `registerRBACMiddleware`) | | `@mastra/hono` | 2 (auth middleware in both API and Studio paths) | | `@mastra/koa` | 2 (`this.mastra.getStudio()` + `server.mastra.getStudio()`) | | `@mastra/express` | n/a — express adapter doesn't use Studio auth, only `getServer()?.auth` | | `@mastra/nestjs`, `@mastra/tanstack-start`, `@mastra/next` | n/a — no `getStudio()` calls | The diff is intentionally minimal — only `()` → `?.()` at the 6 affected call sites, no behavior change for projects on `@mastra/core` ≥ 1.42.0. ## Why not bump the peer range instead? Bumping the peer dep range would force every consumer to upgrade core, which is a much bigger ask and doesn't help projects already in the field that haven't pinned. The optional-chaining fix is the same hardening `@mastra/server` already shipped — adopting it here brings the three adapters in line. ## Verification - `grep` confirms zero remaining non-optional `getStudio()` calls in `server-adapters/`. - Pattern is byte-for-byte identical to the already-shipped `@mastra/server` fix from #18075. - Existing RBAC test suites in each adapter (`src/__tests__/rbac-permissions.test.ts`) exercise the exact `hasAuth` code path that was changed. - Removes the need for the user-land `Mastra.prototype.getStudio ??= () => undefined` polyfill workaround. ## Changeset `@mastra/fastify`, `@mastra/hono`, `@mastra/koa` — patch. ## Follow-ups (not in this PR) - Add a lint/CI grep rule against non-optional `mastra.getStudio()` to prevent regression. - The express adapter has no Studio-auth path at all (only `getServer()?.auth`); if dual-auth was meant to ship across all adapters, that's a separate feature gap to file. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 Mastra's server adapters (fastify, hono, koa) were crashing with 500 errors whenever someone made a request, but only if they were using an older version of the core library. The fix is simple: instead of assuming a function exists and calling it directly, the code now gently checks if it exists first before calling it—and if it doesn't exist (on older versions), the app just continues working normally without crashing. --- ## Overview This PR fixes a critical 500-error crash affecting projects using `@mastra/fastify`, `@mastra/hono`, or `@mastra/koa` adapters when deployed with `@mastra/core` versions prior to 1.42.0. ## Problem The dual-authentication system calls `this.mastra.getStudio()` during RBAC pre-checks. However, the `getStudio()` method did not exist in core versions before 1.42.0, causing every request to throw `TypeError: this.mastra.getStudio is not a function` and return a 500 error—even for projects with no authentication configured. ## Solution Applied optional chaining (`getStudio?.()`) at six call sites across the three affected adapters to safely handle the absence of the method on older core versions: - **`@mastra/fastify`**: Updated `hasAuth` computation in `registerRoute` and `registerCustomApiRoutes` to use `this.mastra.getStudio?.()?.auth` - **`@mastra/hono`**: Updated `hasAuth` computation in both `registerRoute` and `registerCustomApiRoutes` to use `this.mastra.getStudio?.()?.auth` - **`@mastra/koa`**: Updated `hasAuth` computation in `handleMatchedRoute` and `registerCustomApiRoutes` to use `getStudio?.()?.auth` When `getStudio()` is unavailable on older core versions, the optional chaining safely evaluates to `undefined`, allowing adapters to fall back to server-only authentication without crashing. ## Changes - Added changeset documenting the fix - Modified 3 adapter files (fastify, hono, koa) with minimal, targeted changes to the `hasAuth` logic This approach matches the pattern already implemented in `@mastra/server` and requires no behavior changes for projects on `@mastra/core` ≥ 1.42.0. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
Adds dual auth system to Mastra, allowing separate authentication providers for:
Why This Matters
Previously, the same auth provider handled both Studio and API requests. This caused confusion because:
Changes
Core (
@mastra/core)StudioConfigtype withauth,rbac, andfgaoptionsstudio?: StudioConfigto mainConfiginterfacemastra.getStudio()accessorServer (
@mastra/server)MASTRA_AUTH_MODE_KEYconstant to track auth provider usedgetEffectiveAuthConfig()to route based onx-mastra-client-typeheadergetEffectiveRBACProvider()andgetEffectiveFGAProvider()for dual RBAC/FGA/auth/capabilities,/auth/me, SSO, credentials, etc.)Server Adapters (Hono, Express, Fastify, Koa)
checkRoutePermission()calls to passrequestContextfor dual auth routingSecurity
When
x-mastra-client-type: studioheader is present butstudio.authis not configured, the server returns 401 Unauthorized instead of falling back to server auth. This prevents external users from spoofing the studio header to access Studio UI.Usage Example
Test Plan
Related
auth-vnextbranch commits:627370c,7ccaed8,c00bf66ELI5
Mastra now supports two separate login systems: one for internal team members using the Studio dashboard (which can use SSO), and another for external customers using the API (which can use API tokens). The server detects whether a request is from Studio or the API and uses the appropriate auth/authorization configuration.
Overview
This PR implements an opt-in dual authentication and authorization system that lets Studio UI and API traffic use separate providers for auth, RBAC, and FGA. Studio-specific configuration is supported via a new StudioConfig; when studio.auth is configured Studio requests use it exclusively, otherwise the system preserves backward compatibility by falling back to server (API) auth. The adapter plumbing tags each request with an auth mode so downstream permission checks pick the correct RBAC/FGA provider.
Changes by Package
Core (
@mastra/core)Server (
@mastra/server)Server Adapter (
@mastra/server)Server Adapter Implementations (Express, Fastify, Hono, Koa)
Security Notes
Testing & Build