mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
314fa1780e
3063 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
314fa1780e
|
Merge branch 'pr/saml-backend' into pr/saml-client | ||
|
|
90a9852c99 |
fix(test): add saml key to config.test.ts inline snapshots
Branch-level config schema now includes auth.saml.connections (default
{}), so getConfig surfaces it. Update the three inline snapshots that
serialize the full auth object to match.
|
||
|
|
f7a487f001
|
Merge branch 'pr/saml-mock-idp' into pr/saml-backend | ||
|
|
7dacbc4c08 |
fix(test): use matched cookie prefix in snapshot serializer
`${keyedCookieNamePrefixes}` interpolated the entire array, which was
harmless when the array had one entry but produced
"stack-oauth-inner-,stack-saml-inner-" once a second prefix was added,
breaking every existing OAuth set-cookie snapshot.
|
||
|
|
e3e09f5464 |
fix(saml): expose SAML SDK methods on StackClientApp interface
The implementation in client-app-impl.ts already defines signInWithSaml, signInWithSso, and getSamlConnectionForEmail, but the public StackClientApp interface in template/.../client-app.ts didn't list them. TypeScript users got no autocomplete and no type-checking for the methods, and the regenerated js/react/stack packages inherited the gap. Add the three signatures to the interface near signInWithOAuth. Run \`pnpm -w run generate-sdks\` to propagate to the downstream packages (js/react/stack). Also add a saml-sso → Building2 mapping to the docs APP_ICONS map (broke when the type added "saml-sso" to AppId). Tests: - New regression test in discover.test.ts confirming the new allowSignIn=false filter (#1396 fix) returns 404, so a disabled connection no longer leaks into the signInWithSso flow. |
||
|
|
a007318b49 |
Merge branch 'pr/saml-backend' into pr/saml-client
# Conflicts: # apps/backend/src/lib/seed-dummy-data.ts |
||
|
|
8f6ad97e40 |
fix(saml): skip allowSignIn=false connections in /auth/saml/discover
Discover is the entry point for the SDK's signInWithSso({ email }) flow.
Previously it returned every connection whose domain matched, including
ones the project admin had disabled (`allowSignIn: false`). The SDK
would then send the user through /auth/saml/login, which intentionally
403s for disabled connections — so disabling a connection was a sharp
UX cliff: domain match → branded "Sign in with Acme SSO" CTA → 403.
Treat disabled connections as if they didn't exist for discovery
purposes. Direct sign-in via signInWithSaml({ connectionId }) is still
gated separately in the login route, which is the right place for
"intentional, explicit access by ID."
|
||
|
|
75f8c69dd8 | Merge branch 'pr/saml-mock-idp' into pr/saml-backend | ||
|
|
1e912c7548 |
fix(seed): serialize SAML seed; drop overlay cast
Two cleanups in seed-dummy-data, both flagged on PR #1395: - The parallel `Promise.all` block ran `seedSamlConnections(projectId)` alongside another `overrideEnvironmentConfigOverride` write on the same project (`payments.testMode`). Both are read-modify-write, so concurrent reads of the env config can each see the stale state and the second write clobbers the first — the existing TODO at `config.ts:491` already documents the underlying race. Sequence the SAML seed after the parallel block to avoid the race until the override is wrapped in a serializable txn. - Type the SAML overlay with the target parameter type directly instead of using an `as Parameters<...>` cast, per project style. |
||
|
|
b00fc53748
|
Merge branch 'pr/saml-backend' into pr/saml-client | ||
|
|
8c45c0607b
|
Merge branch 'pr/saml-mock-idp' into pr/saml-backend | ||
|
|
aaeb8318a8 |
fix(test): drop overbroad SAML ID strip regex from snapshot serializer
The pattern \`/_[a-zA-Z][a-zA-Z0-9_.-]{8,}/g\` matched any SCREAMING_SNAKE_CASE
identifier with an underscore followed by 9+ chars — e.g.
\`STRIPE_ACCOUNT_INFO_NOT_FOUND\` became \`STRIPE<stripped SAML id>\`,
breaking unrelated Stripe / payments / OAuth e2e snapshots.
The regex isn't load-bearing today: no current SAML test snapshots a
random AuthnRequest / Response / Assertion ID. Cookie-name SAML IDs
are already covered by \`keyedCookieNamePrefixes\` ("stack-saml-inner-"),
URL path segments only carry the deterministic connection_id, and no
test snapshots raw SAML XML. If a future test ever does, follow the
precedent of the timestamp strip on the next line and anchor the
replacement to specific XML attributes (\`ID="..."\`, \`InResponseTo="..."\`)
rather than matching loose \`_<chars>\` strings everywhere.
|
||
|
|
8facb272a6 |
fix(saml): admin POST /saml-connections must write whole entry
Previously wrote per-field deep dot-keys
(`auth.saml.connections.X.displayName`, ...). When the parent record
entry didn't yet exist, normalization with `onDotIntoNonObject="ignore"`
silently dropped them — POST returned 200 but persisted nothing.
Mirror the dashboard's create dialog (commit
|
||
|
|
9cf7e8f943 |
fix(saml): use backend origin for SP base URL in login + ACS routes
The login route built the SP `callbackUrl` from `query.redirect_uri.origin`, which is the customer's app — not the backend. The IdP would then POST the assertion to e.g. `http://localhost:8103/api/v1/auth/saml/acs/acme` (the demo app), which 404s because the ACS handler only exists on the backend. Fix both login and ACS to derive `baseUrl` from the incoming request's own origin, matching what the metadata route already does. The e2e round-trip test didn't catch this because in tests the customer and backend run on the same host. |
||
|
|
82424d1be9 |
feat(saml): gate SAML SSO behind alpha-stage saml-sso app
Register a new alpha-stage `saml-sso` app rather than exposing SAML to every project. Users opt in from the App Store; the dashboard SSO page, admin CRUD, and SDK routes all 400 with `SAML_SSO_NOT_ENABLED` until the app is installed. Alpha apps stay hidden in production via the existing NODE_ENV filter in `getAllAvailableAppIds`. - Add `saml-sso` to `ALL_APPS` + `ALL_APPS_FRONTEND` (icon, store copy, nav item pointing at the existing /sso route) - Wrap SSO page with `AppEnabledGuard` for the redirect-on-disabled UX - Backend: each SAML route checks `apps.installed["saml-sso"]?.enabled` and throws the new `KnownErrors.SamlSsoNotEnabled` - E2E: setup helpers enable the app on test projects; new discover test verifies the gate fires for unconfigured projects - Seed dummy data: enable saml-sso when `STACK_SEED_ENABLE_SAML=true` so the seeded acme/globex connections work without an extra click - Fix a pre-existing indent slip in the delete-dialog updateConfig call |
||
|
|
fe8197ca8c |
fix(dashboard): drop yup.url() validator and use env-only pushable=false
Surfaced while taking screenshots for the PR description: - yup.string().url() rejects http://localhost which breaks any local or test-env IdP setup. The backend SAML wrapper validates the URL on use anyway. Drop the client-side .url() check. - The form was set to pushable=true which routes through the GitHub-pushable config dialog. SAML connection fields (cert, IdP URLs) live at the environment level (not the branch level that's pushed to GitHub) — same as OAuth client secrets in the auth-methods page. Set pushable=false to write directly via env config override. |
||
|
|
5fa9629ded |
fix(saml): use whole-entry config writes; correct test request shapes
E2E tests + dashboard SSO page were writing per-field deep dot-keys like `auth.saml.connections.X.displayName`, which the config normalizer drops because the parent record entry doesn't yet exist when the connection is being created. Match the existing auth.oauth.providers convention: write the whole connection entry as a single value on create. Also fixed two test-harness issues uncovered while running the suite: - Round-trip ACS POST was using niceBackendFetch which always JSON.stringifies the body. Switched to plain niceFetch so URLSearchParams gets sent as application/x-www-form-urlencoded. - Mock IdP /metadata returns application/xml, which makes niceFetch return ArrayBuffer; added a TextDecoder pass before regex matching. |
||
|
|
edb13f3107 |
feat(dashboard): add SAML SSO management page
Single page at /projects/[projectId]/sso for managing SAML connections. Lists existing connections (read from project.useConfig() — same source as the e2e tests and seed script use), with add + delete dialogs. Add dialog: ID, display name, optional email domain (for discovery), IdP entity ID, IdP SSO URL, and IdP signing certificate. PEM headers are stripped from the cert automatically before saving. Delete dialog warns that user accounts linked via the connection remain in the database — they just become unable to sign in until a connection with the same id is recreated. Each connection card surfaces the SP metadata URL + ACS URL so the customer's IT admin can copy them into the IdP console without manually composing the URLs. V1 limitations (planned follow-ups): - Edit happens by deleting + recreating; no in-place edit dialog yet. - No "paste IdP metadata XML" auto-fill (the backend metadata-parser exists; just needs to be wired up to a paste box). - No separate detail page; everything is on the list. |
||
|
|
36d00f2c4d |
test(e2e): add full SAML SP-initiated round-trip tests via mock IdP
apps/e2e/tests/backend/endpoints/api/v1/auth/saml/round-trip.test.ts
exercises the entire SP-initiated flow against the running mock IdP on
port 8115:
GET /auth/saml/login → IdP URL with SAMLRequest
POST mock /idp/[tenant]/login → auto-POST HTML with signed SAMLResponse
POST /auth/saml/acs → backend verifies + issues OAuth code
Five test cases:
1. Happy path: new user JIT-created, ACS responds with 303/307 + OAuth
code in the redirect.
2. Wrong audience: mock IdP misbehaves via /test-controls
{ kind: 'wrong-audience' }, backend rejects.
3. Bad signature (cross-tenant forgery): mock signs with another
tenant's key via { kind: 'bad-signature' }, backend rejects.
4. Expired assertion: NotOnOrAfter in the past via { kind: 'expired' },
backend rejects.
5. Replay: same SAMLResponse POSTed twice — second attempt rejected
because SamlOuterInfo was consumed by the first ACS call.
Fetches the mock IdP's cert at test setup time so the SAML
verification chain is real (the mock regenerates keys per startup, so
hardcoded certs would never match).
Test integrity reaffirmed: the test file imports only from helpers,
backend-helpers, and ports — NO imports from apps/backend/src/saml/.
Negative cases come from the mock deliberately misbehaving, never from
injecting bad data into the backend's own validator. Mock IdP uses
samlify; backend uses @node-saml/node-saml — different libraries on
each side mean a bug in either surfaces as a test failure rather than
canceling out.
Tests written and lint/typecheck clean; runtime verification needs the
backend + mock-saml-idp services up (CI workflow already wired).
|
||
|
|
f8093a31c1 |
test(e2e): add SAML discover, metadata, and login route tests
Three test files exercising the SAML routes that don't require a full IdP round-trip: - discover.test.ts: 5 cases for /auth/saml/discover — happy path, unknown domain (404), case-insensitivity, unknown project_id, cross-project isolation (project A's connection isn't visible from a query against project B's domain). - metadata.test.ts: 3 cases for /auth/saml/metadata — XML contains entityID + ACS URL embedded, 404 for unknown connection, 404 when connection exists but has no IdP cert (incomplete configuration). - login.test.ts: 5 cases for /auth/saml/login — JSON-mode returns the IdP redirect URL with SAMLRequest+RelayState, browser-redirect mode sets the stack-saml-inner- CSRF cookie, 404 unknown connection, 403 when allowSignIn=false, invalid client_id rejected. Test integrity: all tests drive the API only — no imports from apps/backend/src/saml/. SAML config is set via the standard config override endpoint (no test-only mutator), so the routes run through the same code path real customers would hit. Full SAML round-trip tests (login → mock IdP → ACS → session) deferred to a follow-up — they need a sequenced flow against the mock-saml-idp service that's separate from these endpoint-level tests. |
||
|
|
958407d0c3 |
feat(examples/demo): add SAML SSO demo page
examples/demo/src/app/saml-demo/page.tsx — manual end-to-end check for the SAML round-trip. Two flows: 1. Email-domain discovery: enter alice@acme.test, click "Sign in via SSO". The SDK calls getSamlConnectionForEmail then redirects via the matched connection. 2. Direct connection ID: per-tenant buttons that call signInWithSaml with explicit connectionId (the pattern most B2B login pages use when they brand each tenant separately). Page also shows the current signed-in state + an SDK snippet so a developer can see exactly what to copy. Pairs with seed-dummy-data's STACK_SEED_ENABLE_SAML=true block which pre-creates the matching acme + globex connections. |
||
|
|
ce66a1908d |
feat(sdk): add signInWithSaml, signInWithSso, getSamlConnectionForEmail
Three methods on StackClientApp that mirror signInWithOAuth:
- signInWithSaml({ connectionId, returnTo }) — explicit connection
selection. Calls /auth/saml/login/[connectionId] in stack_response_mode
=json so the SDK can intercept the redirect URL.
- signInWithSso({ email, returnTo }) — email-domain discovery via
/auth/saml/discover, then redirects through the matched connection.
Throws when no connection matches so callers can fall back to other
sign-in methods.
- getSamlConnectionForEmail(email) — pure lookup with no redirect, so
the customer's UI can render branding ("Sign in with Acme SSO")
before the user clicks.
Backed by getSamlUrl + authorizeSaml + discoverSamlConnection on
StackClientInterface (mirrors getOAuthUrl + authorizeOAuth pattern,
without provider_scope or bot challenge — SAML originates from a
corporate IdP, not a public form).
Generated via pnpm -w run generate-sdks; propagates from
packages/template into packages/js, packages/react, packages/stack.
|
||
|
|
2d1db7a56e |
fix(saml): harden route guards and accept Response-level signature
Two bugs surfaced when running the SAML e2e suite against the live backend (in a separate PR): 1. Routes accessed `tenancy.config.auth.saml.connections[id].field` without first checking that the entry exists. With strict null checks off, TS types this as always-defined and the route 500'd with a TypeError on missing connections instead of returning 404. Add an explicit `id in connections` guard at the top of each route (login, acs, metadata). 2. SAML responses signed at the Response element (samlify default, also what Okta + Azure AD emit) failed verification because the backend was configured with wantAssertionsSigned=true, wantAuthnResponseSigned=false — i.e. demanded an Assertion-level signature. Per SAML 2.0 §4.1.4.2 either is valid. Flip to wantAuthnResponseSigned=true so we accept what real-world IdPs actually send. |
||
|
|
b4bc68750e |
feat(backend): add admin CRUD for SAML connections
Three endpoints under /api/v1/saml-connections — admin-only thin REST wrappers around the JSON-config storage so the dashboard SSO pages don't compose key paths manually: - GET /saml-connections list all (omits cert) - POST /saml-connections upsert by id - DELETE /saml-connections delete by id - GET /saml-connections/[id] full detail (includes cert) User accounts linked via a deleted connection remain in the DB; they just become unable to sign in until a connection with the same id is recreated. (Dashboard delete UX should warn on this.) Underlying storage is the same overrideEnvironmentConfigOverride / resetEnvironmentConfigOverrideKeys flow used by the seed script and the e2e tests, so behavior is identical across all surfaces. |
||
|
|
191ad700bd |
feat(backend): add SAML login + ACS routes with OAuth2 integration
Two routes that complete the SAML SP-initiated round trip: - GET /api/v1/auth/saml/login/[connection_id] Receives the same Stack Auth OAuth client params as /auth/oauth/authorize (client_id, redirect_uri, scope, state, etc.), builds an AuthnRequest, persists the OAuth context + AuthnRequest ID in SamlOuterInfo, sets a CSRF cookie keyed to the request ID, and redirects to the IdP. Honors stack_response_mode=json so the SDK can intercept programmatically. V1 scope: SP-initiated only, no signed AuthnRequests, no link/upgrade flow. - POST /api/v1/auth/saml/acs/[connection_id] Receives the IdP's POST. Parses InResponseTo from the response WITHOUT verifying the signature, looks up SamlOuterInfo to recover tenancy/connection (this is necessary because the connection ID alone doesn't index a tenancy in the JSON-config storage model). Validates CSRF cookie, then runs node-saml's full validatePostResponseAsync (signature + audience + clock skew + InResponseTo). Defense-in-depth re-checks InResponseTo and cross-connection mismatch (the latter handles 'assertion sent to the wrong ACS endpoint' forgery, e2e test #10). On success, runs find-existing / link / create via the saml-account.tsx helpers, then hands off to oauthServer.authorize so Stack Auth issues a customer-facing OAuth code (mirrors the oauth/callback pattern). Deletes SamlOuterInfo at the end for replay protection. Adds extractInResponseTo helper to saml/saml.tsx for the pre-validation parse described above. Routes typecheck and lint clean. Runtime untested — needs the e2e test matrix (task #15) to exercise the round-trip end-to-end against the mock IdP. |
||
|
|
189a543a31 |
feat(stack-shared): add SAML connection config to project schema
Adds tenancy.config.auth.saml — mirrors the auth.oauth shape:
- branchAuthSchema gains saml.{accountMergeStrategy, connections}
with non-sensitive per-connection fields (displayName, allowSignIn,
domain). domain feeds /auth/saml/discover.
- environmentConfigSchema extends saml.connections with IdP-side
fields (idpEntityId, idpSsoUrl, idpCertificate, attributeMapping).
These belong at the environment level — different per IdP deployment
even though the cert is technically a public key — same way
oauth.providers splits clientId/clientSecret out of branch config.
- Defaults block adds an empty saml block; per-connection defaults set
allowSignIn=true and a placeholder displayName so partial configs
validate cleanly.
Also drops the temporary unknown-cast workaround in saml-account.tsx
(handleSamlEmailMergeStrategy) and updates the metadata + discover
routes to construct SamlConnectionConfig from the typed config record
(injecting the connection ID since it's stored as the record key).
Adds matching coverage in schema-fuzzer.test.ts so the fuzzed config
shape includes a sample SAML connection.
|
||
|
|
11239b4687 |
feat(backend): add SAML metadata + discovery HTTP routes
Two of the four planned SAML routes — the public-fetchable / read-only ones with no OAuth2-server integration: - GET /api/v1/auth/saml/metadata/[connection_id]?project_id=... SP metadata XML for the IdP admin to paste into their IdP console. Includes the project_id query param because connection IDs alone don't identify a tenancy (config lives in JSON, not a Prisma table). - GET /api/v1/auth/saml/discover?email=...&project_id=... Email-domain → connection lookup for the SDK's signInWithSso flow. Returns 404 (not 200 with null) when no connection matches so the SDK can fall back to other sign-in methods on status alone. login + acs routes are the next chunk. They need to mirror the OAuth callback's oauthServer.authorize integration so the customer's app receives a Stack Auth OAuth code on success — that's a meaningful copy-from-pattern job and is left for the next commit so it can be reviewed and tested in isolation. |
||
|
|
0e542f72f5 |
feat(backend): add SAML protocol wrapper around @node-saml/node-saml
Three modules under apps/backend/src/saml/:
- saml.tsx — buildSamlClient (per-connection SAML instance), build
AuthnRequestUrl (returns URL + extracted requestId for replay
protection), parseAndVerifyAssertion (signature + audience + clock-skew
+ InResponseTo are all enforced by node-saml), getSpMetadataXml.
Defines SamlConnectionConfig locally so the wrapper doesn't depend on
the project-config schema work.
- metadata-parser.tsx — pulls entityId, ssoUrl, and the signing X509
certificate out of pasted IdP metadata XML. Uses xmldom + xpath rather
than regex so it handles attribute-order variations across IdPs.
- discovery.tsx — email-domain to connection lookup for the
signInWithSso({ email }) flow. Iterates the project's connections and
returns the first whose `domain` matches.
The clock-skew tolerance is set to 60s, matching the e2e test matrix
item #16. The 'wantAssertionsSigned: true' default means an unsigned
assertion is rejected even if the response itself is signed — which is
the safer default per OWASP SAML guidance.
|
||
|
|
c1b7bed261 |
feat(backend): extract email-merge helper and add SAML account helpers
Splits the email-merge strategy out of oauth.tsx into a small shared external-auth.tsx so the upcoming SAML ACS handler can reuse the same contact-channel lookup + link_method/raise_error/allow_duplicates switch without duplicating it. Also adds saml-account.tsx with the SAML-side parallel of OAuth's findExisting / link / create user-linking helpers, operating on ProjectUserSamlAccount and SamlAuthMethod. Each helper is keyed by (tenancyId, samlConnectionId, nameId), so a NameID arriving from a different connection is treated as a separate identity — connection isolation is enforced at the DB level. Schema strategy fallback: handleSamlEmailMergeStrategy reads tenancy.config.auth.saml.accountMergeStrategy if set, otherwise falls back to the OAuth strategy. The SAML config field will be added with the project config schema work. Adds @xmldom/xmldom and xpath as direct backend deps for the upcoming SAML protocol wrapper (currently transitive through @node-saml/node-saml). |
||
|
|
cbd2e3fca3 |
feat(backend): add SAML SSO Prisma models + migration
Adds three tables to back per-user SAML accounts and the in-flight AuthnRequest temp store: - ProjectUserSamlAccount (mirrors ProjectUserOAuthAccount): one row per (tenancy, samlConnectionId, NameID). The unique constraint on (tenancyId, samlConnectionId, nameId) is what enforces multi-tenant connection isolation at the DB level — the same NameID from a different connection is treated as a distinct identity. - SamlAuthMethod (mirrors OAuthAuthMethod): connects an AuthMethod to a ProjectUserSamlAccount via composite FK. - SamlOuterInfo (mirrors OAuthOuterInfo): keyed by AuthnRequest ID so the ACS handler can look up the original context when the IdP POSTs the assertion back via the browser. ID is TEXT (not UUID) because SAML AuthnRequest IDs are XML xs:ID strings. Per-connection config (entity ID, IdP cert, ACS URL, attribute mapping, domain) is intentionally NOT a Prisma model — it lives in tenancy.config.auth.saml.connections JSON, matching how OAuth provider config (clientId/clientSecret) is stored. |
||
|
|
0e59570bc9 |
fix(lockfile): regenerate after removing body-parser dep from mock-saml-idp
mock-saml-idp originally depended on body-parser for the parser middleware, but switched to using express.urlencoded()/express.json() directly. The package.json dep was removed but the lockfile entry remained, breaking 'pnpm install --frozen-lockfile' in CI. |
||
|
|
4949a9cfc2 |
fix(seed): use whole-entry config writes for SAML connections
Deep dot-keys like `auth.saml.connections.X.field` get dropped by config normalization with onDotIntoNonObject=ignore when the parent record entry doesn't yet exist. Match the existing convention from auth.oauth.providers and write the whole connection entry as a single value. (Bug surfaced when running the SAML e2e tests against a live backend in a separate PR. Applied here so the seed function works on its own without requiring downstream PRs.) |
||
|
|
6c7b14b3bc |
feat: wire mock-saml-idp into CI, snapshots, and seed dummy data
Three smaller pieces that unlock e2e testing: - .github/workflows/e2e-api-tests.yaml: starts mock-saml-idp on port 8115 alongside mock-oauth-server, with /idp as the readiness probe. Root package.json adds start:mock-saml-idp script and includes the mock in dev:basic. - apps/e2e/tests/snapshot-serializer.ts: strips SAMLRequest / SAMLResponse / RelayState query+form params, adds stack-saml-inner- to keyed cookie name prefixes (so the per-AuthnRequest CSRF cookie doesn't reroll snapshots), and adds regex replacements for SAML xs:ID identifiers and IssueInstant/NotBefore/NotOnOrAfter timestamps. - apps/backend/src/lib/seed-dummy-data.ts: STACK_SEED_ENABLE_SAML=true pre-creates acme + globex SAML connections on the dummy project, fetching the IdP metadata from the running mock at seed time so the seeded cert matches what the mock generated at startup. The mock regenerates keys per restart, so re-seed if you restart it. Mock URL configurable via STACK_MOCK_SAML_URL (default localhost:8115). |
||
|
|
d4d25f6255 |
feat(mock-saml-idp): scaffold mock SAML 2.0 IdP for SAML SSO testing
Adds apps/mock-saml-idp, a multi-tenant SAML 2.0 Identity Provider mock
mirroring apps/mock-oauth-server. Each tenant has its own RSA keypair
and self-signed cert generated at startup, so one mock service can back
many SamlConnection rows in tests and exercise per-connection isolation.
Uses samlify deliberately because the upcoming backend SAML wrapper will
use @node-saml/node-saml. Different libraries on each side means a bug
in either library's signature canonicalization surfaces as a test
failure instead of being masked by both sides agreeing.
Endpoints:
- GET /idp/:tenant/metadata IdP metadata XML
- GET /idp/:tenant/sso AuthnRequest receiver, renders login form
- POST /idp/:tenant/login builds and auto-POSTs signed assertion
- POST /idp/:tenant/test-controls queues misbehaviors (bad-signature,
expired, wrong-audience, replay, etc.)
- GET /idp introspection
Also adds @node-saml/node-saml to apps/backend deps for the upcoming
backend SAML protocol wrapper.
|
||
|
|
e831972c4c
|
Move internal MCP server to backend, use Mintlify MCP for docs tools (#1389)
## Summary - Move the `/api/internal/[transport]` MCP route from the docs app to the backend, so the public `ask_stack_auth` MCP tool is served from the same origin as the AI query API it proxies to. - Replace the bespoke docs-tools HTTP client in `apps/backend/src/lib/ai/tools/docs.ts` with an `@ai-sdk/mcp` client that talks to Mintlify's generated MCP server. The backend AI agent now consumes Mintlify's lower-level search/fetch tools directly instead of going through the docs app. - Swap `STACK_DOCS_INTERNAL_BASE_URL` for `STACK_MINTLIFY_MCP_URL` (defaults to the Mintlify-hosted MCP URL). - Move the `@vercel/mcp-adapter` dependency from `docs` to `apps/backend`. ## Test plan - [ ] `pnpm typecheck` - [ ] `pnpm lint` - [ ] e2e: new `apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts` covers `tools/list` and validation on `tools/call` - [ ] Manual: hit `POST /api/internal/mcp` on the backend and confirm `ask_stack_auth` is listed and callable - [ ] Manual: confirm backend AI agent docs tools resolve via the Mintlify MCP URL <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Backend docs tooling now uses a Mintlify MCP server for documentation tools and discovery. * **Chores** * Development environment variables updated to point to the Mintlify MCP endpoint. * Backend dependency added to support MCP integration; docs package dependency removed. * **Tests** * Added end-to-end tests for the internal MCP endpoint and tool validation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
ed8961069c
|
fix(dashboard): UI bug fixes (#1377)
## Summary Rolling PR for dashboard UI bug fixes. Each fix is appended to the **Fix log** below with before/after screenshots. This PR stays open until we batch-merge or split. --- ## Fix log ### 1. Hide Alpha/Beta stage badges in onboarding "Select apps" tooltip **Bug:** On the new-project onboarding, hovering an app card showed an "Alpha" or "Beta" stage badge next to the app name in the tooltip. These shouldn't be surfaced on the onboarding step. **Fix:** Removed the stage badge from the onboarding app-card tooltip only. The "Required" badge is preserved, and stage badges on other surfaces (app management, app store, command palette) are unchanged. #### Before / After — Beta (Payments) | Before | After | | --- | --- | |  |  | #### Before / After — Alpha (Onboarding) | Before | After | | --- | --- | |  |  | --- ### 2. Eliminate full-page flash when advancing onboarding steps **Bug:** Moving between onboarding steps (e.g. Configure authentication → Select email theme) briefly blanked out the entire page — only the navbar remained visible for roughly two seconds — before the next step rendered. It felt like a complete browser reload. **Fix:** Contained the suspension inside the wizard. A local Suspense boundary around the onboarding page means that when any data cache refresh fires during the step advance, the suspension no longer bubbles up to the site-wide loading indicator. The step-advance state update is also marked as a React transition, so the current step stays rendered until the next step is ready to commit. Net effect: the previous step is visible throughout the save, then the next step swaps in without a blank frame. #### Before — full blank flash mid-transition | Auth step (start) | Mid-transition (blank) | Email theme step (end) | | --- | --- | --- | |  |  |  | #### After — previous step stays visible, no blank frame | Auth step (start) | Mid-transition (auth stays visible) | Email theme step (end) | | --- | --- | --- | |  |  |  | --- ### 3. Add a subtle back arrow to the onboarding timeline **Bug:** The only way to return to a previous step in the new-project onboarding was to click one of the tiny completed-step dots at the bottom of the page — not discoverable, and easy to miss. **Fix:** Added a small muted left-arrow next to the timeline dots. Clicking it advances back one step. It's absolute-positioned so the dots stay perfectly centered, and it hides itself on the first step (where there's nothing to go back to). #### Before / After — Select apps step | Before — dots only | After — back arrow next to the dots | | --- | --- | |  |  | ### 4. Unify onboarding step styling — cards everywhere, no glassmorphism **Bug:** Step-to-step styling in the onboarding was inconsistent. The Config and Email-theme steps used a glassmorphic surround (`backdrop-blur`, translucent whites) while the other steps used solid cards. Advancing from auth to email made it look like the visual language had changed mid-flow. **Fix:** Dropped the glassmorphic variants from the onboarding wizard. The config-choice option cards, the email-theme container, and the `ModeNotImplementedCard` surround all now use the same solid card treatment (`bg-white/90` light, `bg-white/[0.06]` dark, with subtle ring). One consistent surface across every step. #### Before / After — Config choice step | Before — glassmorphic | After — solid card | | --- | --- | |  |  | #### Before / After — Email theme step | Before — glassmorphic | After — solid card | | --- | --- | |  |  | ### 5. Add "Copy prompt" button on the project setup page **Bug:** The post-project-creation setup page surfaces a terminal command for every framework (Next.js, React, JS, Python), but there was no one-click handoff for users who drive their setup through an AI agent. Users had to manually copy the command, figure out whether the Stack Auth MCP server got registered, and add it themselves if not. **Fix:** Added a compact **✦ Copy prompt** button at the top-right above the steps list. Clicking it copies a framework-aware prompt to the clipboard — the prompt tells the user's AI agent to run the install command for the currently-selected framework, then verify the Stack Auth MCP server (`stack-auth`, transport `http`, `https://mcp.stack-auth.com/`) is registered in its client config and add it manually if the install didn't. #### Before / After — Project setup page | Before — no AI handoff | After — "Copy prompt" at the top-right | | --- | --- | |  |  | ### 6. Disable email theme cards while the onboarding step is saving **Bug:** On the "Select an email theme" step, the theme cards stayed clickable after clicking Continue. Because we keep the previous step visible during the step-advance transition (fix #2), users could click through to a different theme mid-save — the server would then commit whatever selection was active at click time, not the one on screen when Continue was pressed. **Fix:** Added `disabled={saving}` to the email theme buttons, matching the same pattern the config-choice, apps-selection, and auth-setup steps already follow. Added `disabled:cursor-not-allowed disabled:opacity-60` so users get a clear visual signal that the cards are locked while the save is in flight. --- <!-- Append new fixes above this line. Template: ### N. <title> **Bug:** … **Fix:** … #### Before / After | Before | After | | --- | --- | |  |  | --> ## Test plan - [ ] Load the new-project onboarding "Select apps" step and hover every app card — no Alpha/Beta badge appears. - [ ] Hover a required app — "Required" badge still appears. - [ ] Confirm app management tooltips, app store detail page, and command palette still show stage badges (out of scope for this PR). - [ ] Drive the onboarding from Configure authentication to Select email theme — the auth panel stays rendered throughout the save phase and the email panel swaps in without the site-wide loading indicator or a blank content area. - [ ] Repeat for other step transitions (Config → Apps, Apps → Auth, Email → Domain, Domain → Payments) — same seamless behavior. - [ ] From any step after Config, the back arrow appears to the left of the dots. Clicking it goes back one step. On the first step, the arrow is not rendered. - [ ] Walk through every onboarding step. Container surface is visually consistent across steps — no glassmorphic/card mismatch between Config, Apps, Auth, Email Theme, Payments. - [ ] On the project setup page, the "Copy prompt" button appears above the steps (top-right). Clicking it copies the prompt for the currently-selected framework (Next.js / React / JS / Python) and shows a success toast. - [ ] On the "Select an email theme" step, click Continue — the three theme cards become visibly dimmed (`opacity-60`, `cursor-not-allowed`) for the duration of the save and don't respond to clicks. Once the next step renders they stop being visible anyway. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added back navigation to onboarding wizard steps. * Added "Copy prompt" button for framework-aware terminal commands with MCP verification. * Added loading indicator during asynchronous operations. * **UI/UX Improvements** * Updated card styling for unselected options. * Disabled email theme selection during save operations. * Removed stage badges (Alpha/Beta) from app cards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
e2dc5f5ee0
|
[codex] fix OAuth redirect contract (#1393)
## Summary - Route browser OAuth redirects through the configured `redirectMethod` instead of hardcoded `window.location` calls. - Keep OAuth redirect APIs pending after navigation starts, including custom redirect methods. - Add `cliAuthConfirm` handler URL metadata and custom-page prompt coverage. - Update SDK spec text for browser OAuth callback and `returnTo` behavior. ## Root Cause OAuth helpers previously combined URL construction with direct browser navigation. That bypassed configured redirect methods and made it too easy for public redirect APIs to resolve after navigation started. ## Impact Browser SDK consumers get consistent redirect behavior across built-in and custom navigation methods. `returnTo` is handled as the post-callback destination while the OAuth callback URL remains fixed to the configured handler route. ## Validation - `pnpm test run packages/template/src/lib/auth.test.ts` - `pnpm test run apps/e2e/tests/js/oauth.test.ts` - `pnpm -C packages/template lint` - `pnpm -C apps/e2e lint` - `pnpm -C packages/template typecheck` - `pnpm -C apps/e2e typecheck` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added CLI authorization confirmation page/flow for terminal-based auth. * Added optional returnTo parameter for OAuth to control post-auth redirects. * Exposed configurable redirect behavior so apps follow the chosen redirect method. * **Bug Fixes** * OAuth callback now uses app navigation/queued redirects and shows a fallback link instead of forcing location.assign. * **Tests** * Added unit and e2e tests covering OAuth URL generation, scope handling, and CLI auth confirmation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
5e5cfdec4f
|
[Dashboard][Backend][SDK] - Adds sharable session replay ids. (#1294)
# Shareable Session Replay Links Adds the ability to share individual session replays via unique, direct URLs. https://www.loom.com/share/1e3298a19b114fc38af4bc43dcd5ec48 ## What changed - New admin endpoint — GET /api/v1/internal/session-replays/:id - Fetches a single session replay by ID with user metadata (display name, primary email) and chunk/event counts - Returns 404 if the replay doesn't exist - Admin-only access, consistent with the existing list endpoint ## New standalone replay page — /projects/:projectId/analytics/replays/:replayId - Thin server page wrapper that passes the replay ID to the existing PageClient - PageClient detects standalone mode via initialReplayId prop and fetches replay metadata directly instead of loading the full session list - Sidebar is hidden; the replay viewer takes the full width - "Back to all replays" link shown under the page title ## Copy link button - Moved from per-session sidebar items to the replay viewer header (next to the settings gear) - Copies a direct URL to the currently selected replay ## SDK plumbing - AdminGetSessionReplayResponse type in stack-shared - getSessionReplay() on StackAdminInterface, StackAdminApp interface, and _StackAdminAppImplIncomplete ## Tests - Happy path: fetch single replay by ID with inline snapshot - 404 for nonexistent replay ID - 401 for non-admin access (client and server) ## Test plan - [ ] Open /analytics/replays, select a replay, click the link icon in the header — verify URL is copied to clipboard - [ ] Paste that URL in a new tab — verify the standalone replay page loads and plays the correct replay - [ ] Verify "Back to all replays" link navigates back to the list page - [ ] Verify the original /analytics/replays list page still works as before (selecting, filtering, pagination) - [ ] Run pnpm test run session-replays <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Backend: internal endpoint to fetch a single session replay with user info, millisecond timestamps, and chunk/event counts. * Admin SDK/App: added response type and admin method to retrieve a single session replay; admin app maps response into the app model. * Dashboard: standalone session-replay page, UI adjustments for standalone mode, and a “copy replay link” button. * **Tests** * Added end-to-end tests for retrieval, not-found, and access-control scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
0207721f68
|
fix(dashboard): improve analytics replay replayer lifecycle (#1349)
## Summary Improves reliability of the session replay viewer on the project analytics replays page by tracking replayer staleness, coordinating pause/restart with effects, and cleaning up instances to avoid leaks. ## Changes - Add `isReplayerStale` and wire replayer lifecycle into `executeEffects` so playback and pause stay in sync with the replayer state. - Pause/restart and teardown when the replayer becomes stale or unmounts. ## Test plan - [ ] Open a project’s **Analytics → Replays**, load a replay, scrub timeline, pause/resume, and switch replays; confirm no stuck playback or console errors. - [ ] `pnpm lint` / `pnpm typecheck` on touched packages if CI does not cover. ## Notes Small `CLAUDE.md` tweak included in the same commit. Made with [Cursor](https://cursor.com) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Disabled automatic session recording in the dashboard. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
9d1eee8ab8
|
Add useCliAuthConfirmation hook and customizable cliAuthConfirm URL target (#1388)
## Summary - Extract CLI auth confirmation into a `useCliAuthConfirmation()` hook (status / error / isLoading / authorize / retry) so custom pages don't have to reimplement the protocol; `CliAuthConfirmation` now consumes the hook. - Make `cliAuthConfirm` a first-class handler URL target — resolved via `resolveHandlerUrls`, customizable per project, and used by `promptCliLogin` through a new `buildCliAuthConfirmUrl()` helper. - Move `StackContext` to its own module so the hook can be unit-tested with a test double without tripping the client-version sentinel; register `cliAuthConfirm` in custom-page prompts and the dev-tool components tab; export the hook + types from `@stackframe/stack`. ## Test plan - [ ] `pnpm typecheck` - [ ] `pnpm lint` - [ ] `pnpm --filter @stackframe/stack test cli-auth-confirm url-targets` - [ ] Manually verify default `/handler/cli-auth-confirm` flow + a project with a custom `cliAuthConfirm` URL <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Adds a CLI authentication confirmation page with clear states (invalid, authorizing, redirecting, success, error), retry action, and flows for signed-in and anonymous users. * CLI login URL generation now derives from the configured handler target and app base, improving reliability. * CLI confirmation page exposed in the components/dev UI for previewing. * **Tests** * End-to-end and unit tests covering confirmation behaviors and URL generation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
a82097db62
|
refactor(dashboard): use getEnabledAppIds on metrics page (#1394)
## Summary Uses the shared `getEnabledAppIds` helper from `@/lib/apps-utils` instead of manually filtering installed apps with `typedEntries` on the project metrics page. ## Why Keeps enabled-app logic consistent with other dashboard code paths and slightly reduces duplication. ## Test plan - [ ] Smoke: open project metrics / overview and confirm installed app-dependent UI (e.g. analytics) still behaves as before. Made with [Cursor](https://cursor.com) |
||
|
|
b3d0ab66cc
|
fix(stack-shared): make process.env access browser-safe (#1391)
## Summary - Bare `process.env.X` accesses in `stack-shared` throw `ReferenceError: process is not defined` when the package is bundled into a browser app without a `process` shim (e.g. a plain Vite app). The most reachable offenders are in `StackAssertionError`'s constructor and `schema-fields.ts`'s Neon Basic-auth validator, both of which can run on the client during normal sign-in flows with `@stackframe/react`. - Extracted a zero-dependency `getProcessEnv` helper at `packages/stack-shared/src/utils/process-env.tsx` and routed the bare references through it. Returns `undefined` when `process` is not defined; otherwise behaves like a normal `process.env[name]` read, so Next.js/webpack inlining is unchanged on the server. - Touched: `schema-fields.ts:884` (`STACK_INTEGRATION_CLIENTS_CONFIG`), `utils/errors.tsx:81` (`NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR`), `utils/promises.tsx` (`NODE_ENV` in `runAsynchronouslyWithAlert`), `utils/esbuild.tsx:16` (`NODE_ENV`, also reordered the `typeof process` guard so the env access is unreachable in browsers). ## Why a separate helper module `utils/env.tsx` already exists but its `getEnvVariable` explicitly throws in the browser, so it can't be reused here. The new module has zero imports so it can be safely consumed from low-level utilities like `errors.tsx` without creating a cycle (env.tsx ↔ errors.tsx). ## Test plan - [x] `pnpm lint` passes - [x] `pnpm typecheck` passes - [ ] Reproduced the original failure in a Vite + `@stackframe/react` app: sign-in flow logged `ReferenceError: process is not defined` from `StackAssertionError`, plus `clientSecret must not be empty` cascading from the same path - [ ] Verify the same flow in a Vite app no longer throws once `@stackframe/react` is rebuilt against this `stack-shared` change - [ ] Confirm Next.js consumer behavior is unchanged (env vars still inlined at build time for `NEXT_PUBLIC_*`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Refactor** * Improved environment variable handling across shared utilities for enhanced browser compatibility and safety. Introduced a new utility for dynamic, browser-safe environment variable access that prevents errors in non-Node.js environments. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
65d87a4836
|
Dashboard: DataGrid refactor + layout (stacked on overview-revamp) (#1338)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary Stacked on `overview-revamp` (now rebased against `dev`). Introduces a first-class `DataGrid` component in `@stackframe/dashboard-ui-components`, migrates every dashboard table off the legacy `DesignDataTable` / hand-rolled `<Table>` pattern to it, and ships a matching dashboard design guide. Since the last writeup the `DataGrid` runtime has been substantially rewritten: the virtualizer now supports `rowHeight="auto"` with `estimatedRowHeight`, every column can opt into `cellOverflow: "wrap"`, the toolbar + header stick under a configurable `stickyTop`, and the seeded dummy data has been fleshed out so the migrated surfaces render with realistic density. The AI-analytics prompt was also extended with full schema docs for the auth / team / email / payments tables so natural-language queries produce better SQL. **Base:** `dev` → **Head:** `ui-fixes-minor` **Scope:** 39 files, ~+6.5k / -2.4k ## Screenshots Captured against the seeded Demo Project on the local dashboard (`admin@example.com` via mock GitHub OAuth). Viewport: **1920×1200** (standard) and **2560×1440** (widescreen). Assets hosted in [this gist](https://gist.github.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9). ### Overview — revamped metrics + line chart | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Users — DataGrid with seeded rows | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Transactions — new DataGridToolbar + sticky chrome | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Teams | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Email Outbox | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Payments — Customers | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Sticky behaviour — scrolled views Grids scrolled down ~600px. The page header is still pinned, and the `DataGrid` toolbar + column header row stay put under it (backdrop-blur + `stickyTop` offset) while the virtualized body rows scroll past. Compare the scrolled view against the top-of-page view above. | Page | Light | Dark | | --- | --- | --- | | Users |  |  | | Teams |  |  | | Transactions |  |  | | Payments Customers |  |  | | Email Outbox |  |  | | Analytics Tables |  |  | ### Other migrated surfaces | Page | Light | Dark | | --- | --- | --- | | Analytics Tables |  |  | | Emails |  |  | | Email Sent |  |  | | Domains |  |  | | Webhooks |  |  | | External DB Sync |  |  | ## What's new ### `DataGrid` in `@stackframe/dashboard-ui-components` A new, fully-typed, fully-controlled grid component under `packages/dashboard-ui-components/src/components/data-grid/`. Single source of truth for tabular UI across the dashboard. Package files: - `data-grid.tsx` — main grid renderer (virtualized rows, sticky toolbar + header) - `data-grid-toolbar.tsx` — built-in toolbar (search, columns, density, export) - `data-grid-sizing.ts` — column width / flex / min-width resolution - `state.ts` — state helpers (`createDefaultDataGridState`, sort / select / paginate utilities, `exportToCsv`, date formatters) - `strings.ts` — i18n string table + `resolveDataGridStrings` - `types.ts` — public types (`DataGridColumnDef`, `DataGridProps`, `DataGridState`, `DataGridDataSource`, etc.) - `use-data-source.ts` — `useDataSource` hook with `client` / `server` / `infinite` modes - `index.ts` — package entrypoint Features: - Controlled state (`state` + `onChange`) covering sorting, pagination, column visibility, column widths, column pinning, selection, date-display mode, and quick search. - Column definitions with `string` / `number` / `date` / `dateTime` / `boolean` / `singleSelect` / `custom` types, custom `renderCell`, custom sort comparators, per-column `parseValue` / `dateFormat`, pinning, align, flex / min / max width. - **Cell overflow control** — new `cellOverflow: "truncate" | "wrap"` per column. `"wrap"` + `rowHeight="auto"` lets rows grow to fit multi-line content. - **Dynamic row heights** — `rowHeight` now accepts `"auto"` with an `estimatedRowHeight` hint for the virtualizer, eliminating scroll-position jank while rows are still being measured. - **Sticky chrome with `stickyTop`** — the toolbar and header stick under a caller-provided offset (matching the page header height) with a proper blur backdrop. See the _Sticky behaviour — scrolled views_ section above for the visual. - Client-side sort + quick-search + pagination via `useDataSource` — consumer never pre-sorts / paginates. - Server-side and async-generator data sources for streaming / cursor pagination. - Paginated and infinite-scroll UI modes. - CSV export + clipboard copy. - Row single / multi selection with shift-range anchor. - Row + cell click / double-click callbacks. - Pluggable toolbar / footer / empty / loading states and i18n strings. ### Dashboard design guide New `apps/dashboard/DESIGN-GUIDE.md`: prescriptive, AI-readable source of truth for dashboard UI. Documents when to use each `design-components` primitive, the `DataGrid` canonical pattern, color / typography / spacing / motion rules, route-specific guidance, and the migration priority. Now also documents the new `cellOverflow` and dynamic-`rowHeight` patterns, and marks `DesignDataTable` as deprecated in favor of `DataGrid` + `useDataSource` + `createDefaultDataGridState`. ### Overview page revamp `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx` — line chart rewritten on top of the shared `AnalyticsChart` / `DonutChartDisplay` primitives, feeding the revamped Overview. ### Data-table migrations Every shared table under `apps/dashboard/src/components/data-table/` has been rewritten on top of `DataGrid`: - `api-key-table.tsx` - `payment-product-table.tsx` - `permission-table.tsx` - `team-member-search-table.tsx` - `team-member-table.tsx` - `team-search-table.tsx` - `team-table.tsx` - `transaction-table.tsx` — now also wires in `DataGridToolbar` with search / column visibility - `user-search-picker.tsx` - `user-table.tsx` — extracted `USER_TABLE_COLUMNS` for readability / reuse ### Page adoption Page-level tables migrated to `DataGrid` (or the new `useDataSource` + `createDefaultDataGridState` pattern): - `(overview)/line-chart.tsx` - `analytics/tables/query-data-grid.tsx` (now with sticky header) - `domains/page-client.tsx` - `email-drafts/[draftId]/page-client.tsx` - `email-outbox/page-client.tsx` (with `DataGridToolbar`) - `email-sent/page-client.tsx`, `grouped-email-table.tsx`, `sent-emails-view.tsx` - `emails/page-client.tsx` - `external-db-sync/page-client.tsx` - `payments/layout.tsx`, `payments/customers/page-client.tsx`, `payments/products/[productId]/page-client.tsx` - `users/[userId]/page-client.tsx` - `webhooks/page-client.tsx`, `webhooks/[endpointId]/page-client.tsx` - `design-language/page-client.tsx`, `design-language/realistic-demo/page-client.tsx` - `playground/page-client.tsx` ### Backend & supporting changes - `apps/backend/src/lib/ai/prompts.ts` — extends the AI-analytics prompt with detailed schema docs for `contact_channels`, `teams`, `team_member_profiles`, `team_permissions`, `team_invitations`, `email_outboxes`, `project_permissions`, `notification_preferences`, `refresh_tokens`, and `connected_accounts`, so natural-language queries have richer context to compile against. - `apps/backend/src/lib/seed-dummy-data.ts` — additional OAuth providers on seed users, improving dummy-data coverage for the migrated tables (visible on the Users grid). - `apps/dashboard/src/app/globals.css` — adds `--data-grid-sticky-top` token used to derive the grid's sticky offset under the page header. - `packages/template/src/dev-tool/dev-tool-core.ts` — persist the "closed" state when the user closes the dev-tool panel so it doesn't reopen on next load. ## Notes for reviewers - Rebased onto latest `dev`; conflict in `api-key-table.tsx` resolved by keeping the `DataGrid` implementation (consistent with the other migrated tables). - `DesignDataTable` is still in the codebase but marked deprecated in the design guide — new code must use `DataGrid`. - `DataGrid` is fully controlled: consumers must pass state + onChange, must feed `rows` from `useDataSource` (never raw arrays), and must define columns outside the component or via `useMemo`. The guide's §4.12 spells this out. - `rowHeight="auto"` is opt-in; the default fixed-height virtualization path is unchanged and remains the fast path for dense, single-line grids (users, transactions, etc.). - Screenshots are JPEG this round — the local capture tooling's PNG path was producing blank frames, so the new set is `.jpg` end-to-end. Same viewports, same seeded project. ## Test plan - [ ] `pnpm lint` passes - [ ] `pnpm typecheck` passes - [ ] Load the dashboard and verify every migrated surface renders, sorts, searches, paginates, and handles row-click navigation: - [ ] Overview (line chart + donut metrics) - [ ] Users list + user detail (teams, sessions, permissions, API keys) - [ ] Teams list + team detail (members, permissions) - [ ] Domains - [ ] Emails, email-sent, email-outbox, email-drafts - [ ] Webhooks list + endpoint detail - [ ] Payments customers, product detail, transactions (new toolbar) - [ ] External DB sync - [ ] Analytics query table (sticky header) - [ ] Verify infinite-scroll surfaces (domains, etc.) load additional rows on scroll - [ ] Verify sticky header stays below the page header in light and dark themes - [ ] Verify CSV export produces correct output on a representative table - [ ] Verify column resize, visibility toggle, and sort work across themes - [ ] Verify `cellOverflow: "wrap"` rows grow to fit when `rowHeight="auto"` and clip when `rowHeight` is numeric - [ ] Spot-check AI analytics queries against the new schema context (contact_channels, teams, email_outboxes, …) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Unified table components across dashboard with improved infinite pagination and quick search. * **Improvements** * Enhanced table performance with sticky headers and better row height handling. * Improved sorting, filtering, and data loading with consistent state management. * Better visual consistency across all data grids and table layouts. * **UI/Styling** * Refined table styling for better text truncation and content wrapping. * Optimized layout spacing and alignment across dashboard tables. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Developing-Gamer <maxcodes11110@gmail.com> Co-authored-by: Armaan Jain <84474476+Developing-Gamer@users.noreply.github.com> Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com> |
||
|
|
5423d774a2
|
feat(stack-cli): auto-install emulator deps on pull (#1384)
## Summary - `stack emulator pull` now preflights VM dependencies (QEMU binaries, socat/curl/nc/lsof/openssl/zstd, and aarch64 UEFI firmware on arm64) before downloading. - Missing deps are listed, then installed with user confirmation via `brew` on macOS (bootstrapping Homebrew itself if absent) or `sudo apt-get` on Linux. - Skipped when `--skip-snapshot` is passed, since that path never boots the VM. - `gh` / `GITHUB_TOKEN` are intentionally excluded from the auto-install set. ## Test plan - [ ] `node packages/stack-cli/dist/index.js emulator pull` on a machine with all deps present → no prompt, proceeds as before. - [ ] Unlink a dep (e.g. `brew unlink zstd`) and rerun → missing dep listed, decline prompt → exits with a clear error; accept prompt → brew install runs and pull continues. - [ ] `emulator pull --skip-snapshot` still bypasses the dep check. - [ ] Linux path: missing binaries trigger `sudo apt-get update && sudo apt-get install -y …`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Emulator pull now detects missing host dependencies and, on macOS/Linux with an interactive terminal and supported package tools, shows a proposed install plan, prompts for confirmation, and can auto-install required packages (including optional ARM64 firmware). Homebrew will be bootstrapped if absent. * Use --skip-snapshot to bypass the interactive dependency check and installation. * **Behavior** * In non-interactive or unsupported environments, the tool falls back to the prior preflight behavior instead of attempting installation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
3b8667d5f8
|
cli add back init options (#1379)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a "create-cloud" mode to the CLI init flow. * New interactive project creation flow that can prompt for display name and select/create a team-backed project. * **Behavior Changes** * Init now resolves mode from flags, config, or interactive prompts; prompts to choose linking vs creating when inputs are missing. * Non-interactive runs now error when required inputs are absent; cloud linking offers auto-create in interactive mode. * **Refactor** * Centralized auth, project-creation, and env key writing for clearer, safer linking and creation flows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: aadesh18 <110230993+aadesh18@users.noreply.github.com> |
||
|
|
04d57d91ed
|
fix(emulator): move mock OAuth off 8114 to avoid pnpm dev conflict (#1385)
## Summary
- The emulator's mock OAuth server bound to `${PORT_PREFIX}14` (8114)
inside the VM and the host forwarded the same port, colliding with `pnpm
dev`'s mock-oauth-server on 8114.
- Moves the emulator's mock OAuth to `EMULATOR_MOCK_OAUTH_PORT` (default
`26704`, joining the existing `267xx` host port block) and binds the
VM-internal mock to the same port. Same port on both sides keeps the
OIDC issuer URL (`http://localhost:26704`) resolvable identically from
the browser and from the backend inside the VM.
- Plumbed via `runtime-config.iso` as
`STACK_EMULATOR_MOCK_OAUTH_HOST_PORT`, read by cloud-init into
`STACK_OAUTH_MOCK_URL` + new `STACK_OAUTH_MOCK_PORT`;
`mock-oauth-server` now prefers `STACK_OAUTH_MOCK_PORT` so `pnpm dev`
(which doesn't set it) stays on 8114.
## Files
- `docker/local-emulator/qemu/run-emulator.sh` — new
`EMULATOR_MOCK_OAUTH_PORT`, hostfwd/ensure_ports_free/runtime.env
updates
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` — reads the
host port, sets `STACK_OAUTH_MOCK_URL` + `STACK_OAUTH_MOCK_PORT`
- `apps/mock-oauth-server/src/index.ts` — honors `STACK_OAUTH_MOCK_PORT`
- `packages/stack-cli/src/commands/emulator.ts` — default + runtime.env
entry
## Test plan
- [ ] `pnpm emulator:build` succeeds and new snapshot boots
- [ ] `stack emulator start` with `pnpm dev` running on 8114 — no port
collision
- [ ] OAuth sign-in via mock provider completes end-to-end in the
emulator
- [ ] `pnpm dev` mock OAuth unchanged (still 8114)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* The mock OAuth server port is now configurable in the local emulator
with a sensible default, allowing custom port assignments via
environment variable.
* **Improvements**
* Updated port forwarding and environment variable handling to ensure
consistent mock OAuth endpoint configuration across host and guest
systems in the emulator.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
2f719903b1
|
Redesign Email Server settings + managed domain flow (#1373)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary Rewrites the **Email Server** section of the project email settings page and the managed-domain setup flow. Replaces the dropdown + conditional-fields layout with a visual four-card picker, a clearer unsaved-state model, a stepper dialog for managed-domain onboarding, and a consistent tracked-domains list. Also fixes two data-correctness bugs in the managed-domain backend. ## Walkthrough (2×, dead-frames trimmed)  ## Before The saved state was a minimal dropdown, but choosing Custom SMTP / Resend revealed a long conditional form with a hidden gear toggle for server config, no clear "what is saved" signal, and a separate dialog pattern for managed domains. | Saved (Managed) | Custom SMTP selected | |---|---| |  |  | ## After — Provider cards Four visual cards (Stack Shared, Managed Domain, Resend, Custom SMTP) with updated copy. The saved provider shows a green **Current** pill; the card the user is previewing shows an amber dashed **Draft** pill. An amber unsaved-changes banner appears between the picker and the form when state diverges from saved, so it is unambiguous that a click is not yet committed. | Saved state | Previewing a different provider | |---|---| |  |  | Copy changes: - **Stack Shared** — "Only default emails — no custom templates, themes, or sender identity." (was: "Shared (noreply@stackframe.co)") - **Managed Domain** — "Bring your own domain. You add DNS records; we handle signing & delivery." (was: "Managed (via managed domain setup)") - **Resend** uses the official Resend brand mark (light/dark variants in `apps/dashboard/public/assets/`) ## After — Managed domain list + stepper dialog Selecting **Managed Domain** immediately shows the tracked-domain list with an **Add domain** button. Each row reflects real status (Active / Verified / Waiting for DNS / Verifying / Failed). Exactly one domain can be **Active** — the one matching the saved email config; every other verified/applied domain shows a **Use this domain** button so switching is always possible. Adding a domain opens a 3-stage dialog with a horizontal stepper (Verify is right-aligned for the final step). Stage 2 replaces the old bare NS-list with a proper **Type / Name / Content** DNS records table with per-row copy buttons. | Tracked domains list | DNS records table | |---|---| |  |  | ## Bug fixes - **Backend: applying a managed domain did not demote previously-applied ones.** Multiple rows could end up with status `APPLIED` even though only one could be in the saved config. New helper `demoteOtherAppliedManagedEmailDomains({ tenancyId, keepId })` runs inside `applyManagedEmailProvider` to demote all other applied rows in the tenancy back to `VERIFIED` before marking the new one. - **Frontend: "Use this domain" only appeared for `status === verified`.** A domain that had been applied then replaced could never be re-applied from the UI. Button now appears for any `verified` or `applied` row that is not currently in use; the **Active** label is derived from config match instead of DB status. - **Dev mock onboarding now mirrors production timing.** `shouldUseMockManagedEmailOnboarding()` used to insert domains as `verified` synchronously. Now the domain is created as `pending_verification`, and a fire-and-forget `runAsynchronously(() => wait(1000))` updates it to `verified` — mirroring the real Resend webhook flow so the UI states (pending → verifying → verified) are exercised in local dev. ## Test plan - [ ] Cards: clicking each card shows `Draft` pill + amber banner; Discard restores; Save commits and flips `Current` to the new card - [ ] Managed: Add domain → stage 1 input → stage 2 DNS table + copy → Check verification flips to stage 3 → Use this domain sets it Active and demotes the previously-active domain in the list - [ ] Managed: clicking **Use this domain** on a non-active verified row makes it Active and the previously-active row back to Verified - [ ] Shared / Resend / SMTP: existing save + test-email flows still work (logic preserved verbatim) - [ ] `pnpm typecheck` (dashboard + backend) and `pnpm lint` pass <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Redesigned email domain setup flow with multi-step verification dialog * Added copy-to-clipboard for DNS records * Enhanced provider selection interface with improved visual presentation * Onboarding now shows initial "pending verification" state and completes verification asynchronously * **Bug Fixes** * Ensures only one managed domain becomes active when applying a domain * Improved error handling for email configuration saves * **Tests** * Updated end-to-end tests to reflect async verification timing <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
d1751a7634
|
fix(init-prompt): require StackProvider for all frameworks (#1374)
## Summary - The init prompt marked the `StackProvider` step as *React only* and placed it after the `StackHandler` step. Following it on a Next.js project produced a layout with no provider, so `StackHandler` crashed at runtime with `useStackApp must be used within a StackProvider`. - Make the provider step unconditional and move it ahead of the handler step so the dependency order matches the instruction order. Also quote the exact error message so the model won't skip it. ## Test plan - [ ] Run `npx @stackframe/stack-cli init` (or the web flow) against a fresh Next.js app and confirm `/handler/[...stack]` renders without the `useStackApp` error. - [ ] Re-run against a Vite/React app to confirm the reordered instructions still produce a working setup. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added explicit global MCP config file path guidance for several coding agents. * Documented required provider configuration across supported frameworks. * Clarified where to place provider wrappers in root app layouts (including Next.js app/layout). * Reordered setup steps to surface the required "Wrap your app in a Stack provider" step and updated step numbering. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
4a2595d9f7
|
Classify ClickHouse NO_COMMON_TYPE (386) as unsafe (#1380)
## Summary - Add ClickHouse error code `386` (`NO_COMMON_TYPE`) to `UNSAFE_CLICKHOUSE_ERROR_CODES` in `apps/backend/src/lib/clickhouse-errors.ts`. This stops the Sentry `StackAssertionError` (`Unknown Clickhouse error: code 386 not in safe or unsafe codes`) that was firing whenever an admin wrote a query like `SELECT [1, 'a']` or `SELECT if(1, 'a', 1)`, while keeping the raw error message out of prod responses. - Add two e2e regression tests: one against the cross-project `analytics_internal.users` table, and one against `system.query_log`, to pin that 386 is wrapped with the generic `Error during execution of this query.` message in prod (full detail only surfaces in dev/test). ## Why unsafe, not safe Both callers of `getSafeClickhouseErrorMessage` (`apps/backend/src/app/api/latest/internal/analytics/query/route.ts:59` and `apps/backend/src/lib/ai/tools/sql-query.ts:80`) execute caller-authored SQL under `readonly: "1"` with `SQL_project_id`/`SQL_branch_id` scoping. The ClickHouse client runs under a `limited_user` whose grants restrict most tables — but ClickHouse resolves types **before** enforcing ACL. That means a query like `SELECT if(1, query, 1) FROM system.query_log` surfaces code 386 with a message like `There is no supertype for types String, UInt8 ...`, leaking that `system.query_log.query` is a `String` — schema info from a table the caller can't actually read. This is the same type-before-ACL class as code 43 (`ILLEGAL_TYPE_OF_ARGUMENT`), which is already classified unsafe. Classifying 386 as unsafe keeps the defense-in-depth consistent: if per-customer tables are ever introduced and grants don't block reference-resolution in time, 386 won't leak their schema. Cost: in prod, an admin writing a malformed type-mismatch query sees only `Error during execution of this query.` instead of the supertype hint. Dev and test environments still show the full error via the existing `getNodeEnvironment()` branch, so local iteration is unaffected. ## Test plan - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts` — all 64 tests pass, including the two 386 regression tests. - [ ] Monitor Sentry after deploy to confirm the `unknown-clickhouse-error-for-query` events for code 386 stop firing. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved handling of a ClickHouse type-mismatch error to prevent exposure of sensitive data and ensure sanitized error responses. * **Tests** * Added regression tests that verify error responses are sanitized, return consistent error codes, and include expected headers without leaking internal details. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
cbd945e3a6
|
[codex] Fix Neon malformed Basic auth validation (#1381)
## What changed This fixes Sentry issue [STACK-BACKEND-1A3](https://stackframe-pw.sentry.io/issues/7436639623/?project=4507442898272256&query=is%3Aunresolved&referrer=issue-stream&seerDrawer=true). A request with this malformed header: ```http Authorization: Basic ``` used to crash the Neon auth validator with a `StackAssertionError`, which turned a bad client request into a 500. The fix makes `neonAuthorizationHeaderSchema` only validate Neon client credentials after the Basic auth header successfully decodes. If decoding fails, the Neon-specific validator returns `true` and lets `basicAuthorizationHeaderSchema` produce the intended 400 schema error: `Authorization header must be in the format "Basic <base64>"`. ## Reviewer walkthrough There are two checks chained together: 1. `basicAuthorizationHeaderSchema` checks that the header is structurally valid Basic auth. 2. `neonAuthorizationHeaderSchema` checks that the decoded `client_id:client_secret` matches a configured Neon client. Yup may still run the second check after the first one has failed, because route validation collects errors with `abortEarly: false`. The old code assumed the first check had already passed and called `throwErr(...)` when decoding returned `null`. This PR changes that path to return `true`, because the format error is already owned by the first check. ## Tests - `pnpm -C packages/stack-shared exec vitest run --maxWorkers=1 --minWorkers=1 src/schema-fields.ts` - `pnpm -C apps/e2e exec vitest run --maxWorkers=1 --minWorkers=1 tests/backend/endpoints/api/v1/integrations/neon/projects/transfer.test.ts -t "malformed"` - `pnpm -C packages/stack-shared lint` - `pnpm -C packages/stack-shared typecheck` - `pnpm -C apps/e2e lint` - `pnpm -C apps/e2e typecheck` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Enhanced authorization header validation in API endpoints with improved error handling, ensuring malformed credentials return clear, specific validation error messages. * **Tests** * Added comprehensive end-to-end test coverage for API request validation, including edge cases for authorization headers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |