mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
a62702354b
1841 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a62702354b | Don't show alpha apps during onboarding | ||
|
|
bb901068cb | Fix React error | ||
|
|
48acb8c640 | chore: update package versions | ||
|
|
0848a1aaed | Add schema to migration that was missing it | ||
|
|
29cea48beb
|
Remote dev envs (#1435) | ||
|
|
deff6c3cc4
|
[DEVIN: Konsti] Fix failing E2E tests: CLI error string and MCP prompt name (#1439) | ||
|
|
d68631ea4f | Update GitHub URL | ||
|
|
d0202eeef9
|
payments: rework refund flow to three-knob API (#1429)
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
- Replaces per-entry refund schema with a flat `{ amount_usd,
revoke_product, end_subscription? }` shape; refund state is now derived
from bulldozer ledger rows (`refund:<sourceTxnId>:<uuid>`) instead of
the legacy `refundedAt` column, enabling multiple partial refunds up to
the remaining cap.
- Adds `invoice_id` for refunding any subscription invoice (start or
renewal), Stripe idempotency keys derived from `(tenancyId, sourceTxnId,
amount, prior_refunded)` so retries dedupe but intentional partials
don't collide, and a legacy backstop that rejects pre-rework
`refundedAt` purchases.
- Dashboard refund dialog rebuilt around the three toggles (revoke→end
coupling cascades into the UI); refund rows surface in the listing as
`type: "refund"` with `adjusted_by` linkage handling both new and legacy
formats.
## Implements
[STA2-52 — Build in refund logic for
payments](https://linear.app/stack-auth/issue/STA2-52/build-in-refund-logic-for-payments)
## Documented limitations (planned follow-up work)
These are called out in code comments and intentionally deferred to a
follow-up PR:
- **Cap-check race under concurrent refunds.** Bulldozer's embedded
`BEGIN/COMMIT` prevents an outer Prisma tx from scoping the writes, so
two concurrent refunds can both pass the cap check. Needs a
bulldozer-aware mutex or pending-refund-intent pattern. In practice
refunds are admin-only and rare, so the race window is small.
- **Stripe + DB non-atomicity on the DB-success → response-loss path.**
The Stripe idempotency key is keyed on `(tenancyId, sourceTxnId, amount,
priorRefunded)`, so a retry after Stripe-success → DB-fail self-heals
(Stripe dedupes; the next attempt writes the bulldozer row). The hole is
the reverse direction: if the bulldozer row commits but the response is
lost, a retry sees a higher `priorRefunded` and generates a fresh key —
Stripe would issue a second real refund. No out-of-band reconciliation
today.
- **Dashboard can't reach the `invoice_id` path.** Refund actions are
only enabled on `purchase` rows and the submit call never passes
`invoice_id`, so admins refunding a renewal must use the API directly.
Follow-up: enable the action on `subscription-renewal` rows and thread
`invoice_id` through.
## Architectural note
`active-subscription-end` and `item-quantity-expire` entries are **not**
emitted on the refund row itself. They're produced by the derived
sub-end transaction (`transactions.ts:158-228`) once Prisma
`subscription.endedAt` is updated, keeping the `expiresWhen` /
`when-repeated` semantics in one place. This is the main structural
divergence from the ticket's literal entry recipe.
## Review follow-ups addressed in this PR
**First-pass review:**
- **KnownError back-compat preserved**: `SubscriptionAlreadyRefunded` /
`OneTimePurchaseAlreadyRefunded` are once again thrown by the
legacy-`refundedAt` backstop, and `TestModePurchaseNonRefundable` is
thrown when an admin sends `amount_usd > 0` against a test-mode
purchase. Callers catching by error code keep working through the
rework.
- **Idempotency-key comment corrected**: now accurately describes the
`(tenancyId, sourceTxnId, amount, priorRefunded)` key and its
self-healing behaviour on the Stripe-success → DB-fail retry path (see
Documented limitations above for the remaining hole).
- **Renewal-invoice e2e coverage added**: new test sets up a live-mode
subscription via Stripe webhooks (`subscription_create` +
`subscription_cycle` invoices), refunds the renewal invoice via
`invoice_id`, and asserts the resulting `refund_transaction_id` starts
with `refund:sub-renewal:` and is linked back via `adjusted_by` on the
*renewal* row (not the start row). Plus negative cases:
cross-subscription `invoice_id` → 404, `invoice_id` on a one-time
purchase → SchemaError.
**Second-pass review:**
- **Idempotent sub-cancel error-code string fix**: the Stripe code for
re-cancelling an already-canceled sub is
`subscription_already_canceled`, not `subscription_canceled` — the
previous catch would have re-thrown.
- **End-only sub refund replay rejected**: when `amount=0, revoke=false,
end=true` and the sub is already `cancelAtPeriodEnd` or `endedAt`, throw
SchemaError. Otherwise `readPriorRefundSummary` doesn't see end-only
events and the call would be a forever-no-op accumulating empty refund
rows.
- **`revoke_product=true` with renewal `invoice_id` rejected**: the
product grant lives on the sub-start txn, not on renewal txns — a
renewal-scoped revocation would write a back-reference to a non-existent
entry. Forces admin to revoke against the start invoice (or the default
no-`invoice_id` call).
- **Refund row `id` matches the linkage**: the listing route now returns
the full refund txnId as `id` for `type: "refund"` rows so it matches
`adjusted_by.transaction_id` — the dashboard can join source rows to
their refund rows.
- **+2 e2e tests** for the above (end-only replay rejection,
revoke+renewal rejection).
**Third-pass review:**
- **Dashboard refund dialog seeds state on open**: previously the reset
block lived in `ActionDialog`'s `onOpenChange`, which doesn't fire on
the open transition for a controlled dialog. As a result the dialog
opened with the initial `useState` defaults (`amountUsd = '0'`), and an
admin submitting unchanged on a paid purchase would revoke/end at $0
instead of refunding the charged amount. The seed now runs in the menu
`onClick` before `setIsDialogOpen(true)`.
- **`SUBSCRIPTION_START_PRODUCT_GRANT_ENTRY_INDEX` corrected from 1 →
0**: the constant is persisted as `adjustedEntryIndex` on
product-revocation entries and copied through verbatim by
`mapLedgerEntry`. That mapper drops the hidden
`active-subscription-start` entry, so the public-API layout puts the
product grant at index 0. The prior value of `1` pointed at the
money-transfer entry (or out of range on test-mode subs) through the
public listing.
- **`amountTotal` cap gated behind a USD pre-flight**:
`SubscriptionInvoice` doesn't persist invoice currency, and the previous
code took `invoice.amountTotal` as USD cents directly. Now
`getTotalUsdStripeUnits` (which throws on non-USD pricing) is always
called first; `amountTotal` is only preferred as the actual cap after
that pre-flight succeeds.
## Test plan
- [x] `pnpm typecheck` — 28/28 pass
- [x] `pnpm lint` — 28/28 pass
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/transactions-refund.test.ts`
— **19/19 pass** (was 14/14 on the original PR; +3 for `invoice_id`
path: renewal refund happy path, unrelated `invoice_id` rejection,
`invoice_id` on OTP rejection; +2 for second-pass: end-only replay
rejection, revoke+renewal rejection)
- [x] curl smoke against
`/api/latest/internal/payments/transactions/refund` — unknown purchase →
404, no-op → 400, negative → 400, sub-revoke-without-end → 400
- [x] **Dashboard UI end-to-end re-run pending** — the original
agent-browser pass ran before the third-pass dialog-seed fix, so any
"money + revoke" submissions may have actually sent `amount_usd = "0"`.
Re-test before un-drafting: open the refund dialog from the menu,
confirm the amount field pre-fills with the charged amount, exercise
validation (negative / exceeds-cap / no-op), and submit both an
end-subscription-only sub refund and a money+revoke OTP refund; verify
bulldozer rows and Prisma `cancelAtPeriodEnd` updates.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Ledger-driven refund flow with stable refund IDs, invoice-aware
refunds, OTP/product-revocation support, tri-state end_action (now /
at-period-end / none), and API responses that include
refund_transaction_id.
* **Bug Fixes / Improvements**
* Deterministic Stripe idempotency, stronger replay protection,
refundable-amount caps, test-mode constraints, and transactions listing
updated to surface refunds.
* **Tests**
* Expanded unit and E2E coverage for new request shape, invoice paths,
money-unit conversion, and edge cases.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1429)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
b526e3b367
|
Project transfer page redesign (#1309)
<!-- 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** * Reusable transfer confirmation UI with clear loading, success, and error states. * Neon-specific transfer flow added, guiding sign-in, account switching, or accepting transfers. * Custom integration transfer flow with streamlined confirm/check behavior. * Improved transfer sign-up redirect so users return to the correct page after auth. * **Bug Fixes** * Consistent messaging for missing/invalid/expired transfer codes. * Safer widget “Reload” handling when reset may be unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- ## Summary Redesigns the **custom integration** project-transfer confirmation page (`/integrations/custom/projects/transfer/confirm`) onto the new design-components system (`DesignCard` + `DesignAlert` + `DesignButton` + `DesignInput`). The presentational shell is extracted into a reusable `ProjectTransferConfirmView` so the route file only handles state + API calls. The legacy Neon transfer page is split out unchanged into its own client component to keep the existing Neon × Stack co-branded UI intact. --- ## Screenshots — before and after > Captured against `http://localhost:8101` at 1280×900. Dev-only overlays (outdated-version banner, console toast, DEV badge) are hidden via injected CSS for clarity. ### Custom integration — missing transfer code Visiting `/integrations/custom/projects/transfer/confirm` with no `?code=…` query param. | Before (`dev`) | After (this PR) | | --- | --- | |  |  | |  |  | Before was a raw `"Error: No transfer code provided."` line. After is a dedicated `DesignAlert` with an explanation and recovery instructions. ### Custom integration — invalid / expired code (check endpoint fails) | Before (`dev`) | After (this PR) | | --- | --- | |  |  | |  |  | Before showed the raw backend error string (`Request validation failed on POST …`). After uses a `DesignCard` with the `ArrowsLeftRightIcon`, a friendlier "This transfer can't continue" copy in an inline `DesignAlert`, the Stack Auth logomark in the actions slot, and an explicit **Close** button to dismiss. ### Neon integration — legacy UI preserved The Neon page (`/integrations/neon/projects/transfer/confirm`) was deliberately **not** redesigned — it still uses the Neon × Stack co-branded card so partner-facing copy/branding stay identical. It's now its own client component (`neon-transfer-confirm-page.tsx`) instead of sharing the redesigned one. | Before (`dev`) | After (this PR) | | --- | --- | |  |  | |  |  | Same shell on both sides — copy was tightened slightly ("Return to your Neon dashboard and start the transfer again") and the raw API error string is gone. --- ## What changed - **New** `apps/dashboard/src/components/project-transfer-confirm-view.tsx` — purely presentational `ProjectTransferConfirmView`. Owns the design-components shell, the loading spinner, the signed-in vs signed-out branches of the success state (with `DesignInput` + "Use a different account" button), and the error / missing-code alerts. - **New** `apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx` — extraction of the legacy Neon UI (Neon logo, Stack logo, "Project transfer" header, Card / CardContent / CardFooter). Behaviour and copy match the previous `transfer-confirm-page` exactly when `type === "neon"`. - **Rewritten** `apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx` — now hard-coded to the `custom` integration (no more `type` prop), defers UI to `ProjectTransferConfirmView`, and exports a `TransferConfirmMissingCodeView` used by the route when `code` is absent from the URL. - **Route plumbing** - `app/(main)/integrations/custom/projects/transfer/confirm/page.tsx` — renders the redesigned flow, falls back to `TransferConfirmMissingCodeView` when `code` is missing. - `app/(main)/integrations/neon/projects/transfer/confirm/page.tsx` — points at the new dedicated Neon client component. - **New** `apps/dashboard/src/lib/stack-app-internals.ts` — consolidates the symbol-keyed `getStackAppInternals(app)` helper (and `stackAppInternalsSymbol`) into one module with a JSDoc explainer + runtime type guard, replacing scattered `as any` casts. - **New** `apps/dashboard/src/lib/transfer-utils.ts` — `buildTransferSignUpUrl()` helper so the route file + the view stay in sync on the `/handler/signup?after_auth_return_to=…` query construction. --- ## Bot review follow-ups addressed in this PR - **Fail-loud assertions for unset handlers** in the success state of `ProjectTransferConfirmView` (`StackAssertionError` instead of silent no-op). - **SSR safety:** moved every `window.location` read into client-only handlers / `useEffect`s — the page was previously evaluating it at module load. - **Friendly error fallback** when the backend `/check` endpoint throws — replaces the raw `KnownError<…>` message with "This transfer link is invalid, has expired, or has already been used. Open the original link from the partner or integrations dashboard, or start the transfer again." - **`runAsynchronouslyWithAlert`** around every async `onClick` (Transfer, Sign in, Switch account, Close) so unhandled rejections surface to the user. - **JSX entity bug fix:** `'` was a string-attribute literal, not a JSX expression — converted to a JSX expression so it renders as `'`. - **`window.close()` removal** in error state — replaced with a Close button that resets local state, so users on a fresh tab (no opener) aren't stuck. - **`getStackAppInternals` consolidated** — previously three independent copies (here + two in `projects/page-client.tsx`). Now one helper with a runtime type guard instead of `as any`, plus a comment explaining the symbol-keyed SDK escape hatch. - **Widget-playground reset:** the original change here turned out to duplicate a deliberate prior fix on `dev` (N2D4, `e68015909d "Fix lint"`). Reverted in `fe92689eb` so we don't fight that fix. --- ## Notes for reviewers - **Start with** `components/project-transfer-confirm-view.tsx`. Everything reviewer-interesting is in the props shape (`ProjectTransferConfirmUiState` union, `onPrimary` / `onCancel` / `onSwitchAccount` callbacks). The route file just wires those to the `getStackAppInternals(app).sendRequest(...)` calls. - **The Neon page was intentionally not migrated.** Partner-facing co-branding (Neon logo × Stack logo, "Neon would like to transfer…" copy) is unchanged — flag it if you think it should be brought onto design-components too, but the goal of this PR was only the custom flow. - **API surface is unchanged** — same `/integrations/custom/projects/transfer/confirm/check` and `/integrations/custom/projects/transfer/confirm` endpoints, same request bodies, same redirect to `/projects/{project_id}` on success. - **Success state isn't in the screenshots** because reproducing it locally needs a real transfer code (the `/check` endpoint validates the code against the DB). It uses the same `DesignCard` shell with either a `DesignInput` showing the receiving account + a "Use a different account" outline button (signed-in branch), or a `DesignAlert variant="info"` prompting sign-in (signed-out branch). Worth manually testing on a real transfer before merging. ## Test plan - [ ] Visit `/integrations/custom/projects/transfer/confirm` with no `code` → renders the "transfer link is incomplete" alert (screenshots above) - [ ] Visit `/integrations/custom/projects/transfer/confirm?code=invalid` → renders the redesigned card with the friendly error inside a `DesignAlert variant="error"` and a working Close button - [ ] Trigger a real custom-integration transfer end to end → loading spinner, success state, "Accept transfer" works while signed in, "Sign in" deep-links to `/handler/signup?after_auth_return_to=…` while signed out - [ ] Visit `/integrations/neon/projects/transfer/confirm?code=…` → unchanged legacy Neon × Stack co-branded card - [ ] Light + dark mode visual sanity (screenshots above are the canonical reference) --------- Co-authored-by: Aadesh Kheria <kheriaaadesh@gmail.com> Co-authored-by: aadesh18 <110230993+aadesh18@users.noreply.github.com> |
||
|
|
9102b3db75
|
[Feat] Hexclave AI integration: skill, MCP SKILL.md route, docs (#1434)
## Summary - Adds a `hexclave` SKILL.md pointer skill that fetches the live skill body on every invocation - Adds an `/SKILL.md` route on the MCP app that renders the full skill (CLI usage + docs sidebar generated from `docs.json`) - Expands `docs-mintlify/guides/getting-started/ai-integration.mdx` with three install paths (CLI, Skill, MCP) and per-agent config snippets - Updates `packages/stack-shared/src/helpers/init-prompt.ts` to install both the MCP server and skill file, with per-project vs global scope detection ## Test plan - [ ] `pnpm typecheck` - [ ] `pnpm lint` - [ ] Hit the MCP app's `/SKILL.md` endpoint locally and verify it returns valid markdown with the full docs sidebar - [ ] Render the updated `ai-integration.mdx` in Mintlify preview and confirm tabs/cards render <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Rewrote the AI integration guide with complete, user-facing instructions for connecting Stack Auth to coding agents; removed the separate MCP setup page and updated site navigation. * Added the canonical Stack Auth skill content and guidance that clients should fetch the latest skill at runtime. * **New Features** * MCP now serves the canonical Stack Auth skill dynamically and provides interactive skill responses. * Init prompts now include full MCP + skill install workflows and scope guidance. * Added a health-check endpoint. * **Chores** * Added scaffold and configs for a new skills app (build, dev, lint, and type settings). <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1434?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
5cb9240bc3
|
refactor(dashboard): unify AI chat surfaces on assistant-ui Thread (#1427)
## Summary - Replace the bespoke `ai-chat-shared` chat UI (used by ask-ai, the stack companion widget, vibe coding chat, and the create-dashboard preview) with the shared `assistant-ui` `Thread` component. - Extract streaming request/format helpers into a new `components/assistant-ui/chat-stream.ts` module so each surface only owns its `ChatModelAdapter`. - Add a reusable `ToolFallback` for tool-call rendering and delete the now-unused `ai-chat-shared.tsx` (-1386 / +747 lines net). Stacked on top of `refactor/data-grid-and-dashboard-surfaces`. Base: `refactor/data-grid-and-dashboard-surfaces` → Head: `refactor/assistant-ui-chat-surfaces` · 18 files changed > Red outlines on the **after** shots mark the unified `assistant-ui` `Thread` surface in each location. ## Screenshots ### Analytics → Tables — AI Query dialog | | Before | After | |---|---|---| | **Light** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-before-light.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-after-light.png" width="480" /> | | **Dark** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-before-dark.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-after-dark.png" width="480" /> | ### Stack Companion — chat widget | | Before | After | |---|---|---| | **Light** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-before-light.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-after-light.png" width="480" /> | | **Dark** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-before-dark.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-after-dark.png" width="480" /> | ### Ask-AI command palette (⌘K → Ask AI) | | Before | After | |---|---|---| | **Light** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-before-light.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-after-light.png" width="480" /> | | **Dark** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-before-dark.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-after-dark.png" width="480" /> | ### Email editor — embedded chat panel | | Before | After | |---|---|---| | **Light** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-before-light.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-after-light.png" width="480" /> | | **Dark** | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-before-dark.png" width="480" /> | <img src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-after-dark.png" width="480" /> | ## Notes for reviewers The four surfaces above all previously shared `components/commands/ai-chat-shared.tsx` (516 lines, deleted). After this PR they each own a thin `ChatModelAdapter` and render through `components/assistant-ui/thread.tsx` + the new `chat-stream.ts` helpers. Visual differences between **before** and **after** are intentional — the `assistant-ui` `Thread` brings its own message bubbles, scroll-to-bottom behaviour, composer, and `ToolFallback` rendering. The email editor's chat panel is the surface where the behaviour change is most visible (tool-call rendering now consistent with the rest of the app). Heaviest changes (lines): - `components/stack-companion/ai-chat-widget.tsx` (571) - `components/commands/ai-chat-shared.tsx` (516, deleted) - `analytics/tables/ai-query-dialog.tsx` (429) - `components/vibe-coding/chat-adapters.ts` (400) - `components/assistant-ui/chat-stream.ts` (284, new) - `components/commands/ask-ai.tsx` (274) - `components/assistant-ui/thread.tsx` (115) - `components/assistant-ui/tool-fallback.tsx` (113) ## Test plan - [ ] `pnpm lint` - [ ] `pnpm typecheck` - [ ] Manually exercise each affected surface: command-center Ask AI, stack-companion widget, vibe-coding chat, analytics tables AI query, create-dashboard preview, email editor chat. - [ ] Verify tool-call chips render consistently across all four surfaces (uses the new `ToolFallback`). - [ ] Verify streaming + cancel works on each adapter (`chat-stream.ts` is shared). |
||
|
|
c808e23b7d
|
Data-grid overhaul + session-replays / team-payments dashboard surfaces (#1424)
## Summary Refactors the dashboard data-grid into a smaller, URL-state-aware primitive and lands several new dashboard surfaces around it: per-user session replays, team-level analytics and payments, and pagination for permission definitions. Also moves session replays out from under `/analytics` to a top-level surface and adds a `project_user.last_active_at` index that the new weekly-active metrics depend on. **Base:** `dev` → **Head:** `refactor/data-grid-and-dashboard-surfaces` **Scope:** 91 files, +5,644 / −1,858. Assets in [this gist](https://gist.github.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7). ## Screenshots Captured from a local dev server (dashboard at `:8101`, dummy project seeded with 26 users). Standard viewport **1920×1200**, widescreen **2560×1440**. ### Users list — data-grid overhaul in context | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### User detail — new session-replays card + weekly metrics | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Session replays — moved out of `/analytics` | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Project permissions — new pagination | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Other migrated surfaces | Page | Light | Dark | | --- | --- | --- | | Project picker |  |  | | Overview / setup |  |  | | Teams list |  |  | | Team permissions |  |  | | API keys |  |  | ### Scroll behaviour — new data-grid on the users list | Light | Dark | | --- | --- | |  |  | ## What's new - **`packages/dashboard-ui-components/src/components/data-grid`** — rewritten. Trimmed `data-grid.tsx` from ~1.7k LOC, split sizing logic into `data-grid-sizing.ts`, added `use-url-state.ts` for URL-synced state, and added `data-grid.test.tsx`. - **Session replays** moved from `…/analytics/replays` to `…/session-replays` (top-level surface). New `user-session-replays.tsx` card on the user detail page; new internal `route.tsx` to feed it. - **Teams** detail page gains `team-analytics.tsx` and `team-payments.tsx`. - **Permissions** — new shared `permission-definitions-pagination.ts` consumed by both project and team permission CRUD routes. - **Backend** — Prisma migration `add_project_user_last_active_at_idx` + a `lastActiveAt` index that backs the new weekly-active metrics. - **Polish** — `editable-input`, `inline-save-discard`, `settings.tsx`, walkthrough steps, and several data-table components touched in line with the data-grid rewrite. ## Notes for reviewers - The data-grid rewrite changes the *shape* of state (now URL-synced), not just internals. Consumers in `apps/dashboard/src/components/data-table/*` were updated to match — please scan those for any missed knobs. - The `analytics/replays` → `session-replays` rename is git-tracked as renames; diffs should be small in those files. - New SDK surface in `packages/template/src/lib/stack-app/session-replays/index.ts` and additions in `admin-app-impl.ts` / `server-app-impl.ts` mean OpenAPI specs (`docs-mintlify/openapi/{admin,client}.json`) regenerate; the diff is mostly mechanical. ## Test plan - [ ] `pnpm typecheck` clean - [ ] `pnpm lint` clean - [ ] Data-grid unit tests pass (`packages/dashboard-ui-components`) - [ ] Manual: users list — column resize, sort, filter, paginate; URL state reflects each change and survives reload - [ ] Manual: user detail — session-replays card lists replays; weekly-metrics card renders without `lastActiveAt` index migration applied (i.e. on a fresh DB) and after applying it - [ ] Manual: project + team permissions — pagination cursor advances and stays consistent under search - [ ] Manual: session-replays top-level page loads; old `/analytics/replays/...` URL path is no longer expected to be linked anywhere <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Session Replays app (embedded mode, search, sorting, share links) * Tabbed Team pages with Team Analytics and Team Payments dashboards * Server-backed cursor pagination, debounced search, and infinite-scroll for teams/users/permissions * **UX** * Permission and member tables refresh after edits; permission creation triggers table refresh * Users list supports sorting by last-active * **Performance** * Index added to speed ProjectUser last-active queries * **Documentation** * API/SDK docs updated for pagination and new query params * Contributor guidance: explicit git-safety rules added (no destructive git ops without consent) * **Tests** * Added e2e tests for pagination and filtering on list endpoints <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
a9623d976a
|
[Refactor] [Fix] Remove default prod creation (#1350)
With the new bulldozer rework we dont support default products anymore. Users are encouraged to currently manually handle granting products to their end users. We block api requests and new product creations that attempt to set no price, and we remove any options to set include-by-default. We also migrate users' existing product snapshots in `Subscriptions`, `OneTimePurchases`, and `ProductVersions` to have no price set if it's an include-by-default product. This will make it so that next time a user goes onto their products page, they will be informed that the pricing is invalid and it is no longer delivered by default. Note, however, that these products will still be providing items and the like to the users who have them. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Migrated legacy product snapshots so missing included-items no longer break readers. * Removed deprecated "include-by-default" pricing sentinel; pricing now requires explicit price entries and write validation rejects the old sentinel. * **Chores** * Simplified dashboard pricing flows: create/edit/save now use explicit prices and surface an alert when a formerly implicit free plan needs an explicit $0 price. * Config overrides and stored data are auto-normalized to explicit price objects. * **Tests** * Updated and added tests covering migration, validation, and switching behavior for explicit prices. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: mantrakp04 <mantrakp@gmail.com> Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com> |
||
|
|
15faf709f3
|
stack-cli: explicit --cloud-project-id / --config-file across exec, config, project (#1422)
## Summary Reworks the `stack` CLI surface so the cloud-vs-local choice is **explicit at every invocation**, removing the global `--project-id` / `STACK_PROJECT_ID` env var and the local-default `exec` behavior introduced earlier in this branch. ### `stack exec` - Removes `--cloud`, `STACK_EXEC_DEFAULT_TARGET`, and the implicit local default. The CLI now requires **exactly one** of: - `--cloud-project-id <id>` — run against the Stack Auth cloud API - `--config-file <path>` — run against the local emulator project mapped to that absolute config-file path - The `--config-file` branch resolves the project id by calling the existing `GET /api/latest/internal/local-emulator/project` endpoint and matching `absolute_file_path` client-side. No new backend endpoint introduced. ### `stack config pull` / `stack config push` - Both now take `--cloud-project-id <id>` per-command instead of the global flag / `STACK_PROJECT_ID` env. - `config pull --config-file` is **optional**: when omitted, the CLI uses `./stack.config.ts` from the current directory. If neither flag nor cwd file is present, it exits with a clear hint to pass `--config-file` or `cd` into a directory containing `stack.config.ts`. ### `stack project list` - Default (no flags) lists both **cloud and local emulator** projects. Each entry carries a `target: "cloud" | "dev"` field (text format: `<id>\t<displayName>\t[<target>]`). - `--cloud` / `--dev` filter to a single source (mutually exclusive — passing both errors). - On the default code path, an unreachable local emulator emits a single stderr warning (`warning: skipping dev projects — local emulator not reachable …`) and the command still succeeds with cloud results. With `--dev` explicit, the unreachable case hard-errors. ### `stack project create` - Now requires `--cloud` to make the cloud-vs-local choice explicit. There is no local alternative today; the flag exists to surface the decision so a future local-project create doesn't silently change behavior. ### Backend - Bumps the `LIMIT` on `GET /api/latest/internal/local-emulator/project` from 20 → 100 so `project list --dev` doesn't silently truncate. ### Refactors (from earlier in this branch, unchanged here) - Local-emulator paths/ports/PCK polling live in `packages/stack-cli/src/lib/emulator-paths.ts`. - Shared local-emulator admin credentials live in `packages/stack-shared/src/local-emulator.ts`. - `resolveAuth` / `resolveLocalEmulatorAuth` take an explicit `projectId: string` (no more `Flags` parameter). - New `packages/stack-cli/src/lib/local-emulator-client.ts` encapsulates the GET-and-match flow used by both `exec --config-file` and `project list --dev`. ## Breaking changes **Scripts that relied on any of the following must be updated:** | Removed | Replacement | | --- | --- | | Global `--project-id <id>` flag | Per-command `--cloud-project-id <id>` | | `STACK_PROJECT_ID` env var | Per-command `--cloud-project-id <id>` | | `stack exec --cloud` | `stack exec --cloud-project-id <id>` | | `STACK_EXEC_DEFAULT_TARGET=cloud\|local` | `--cloud-project-id <id>` or `--config-file <path>` | | `stack exec` defaulting to local emulator | Explicit `--config-file <path>` required | | `stack project create` without a flag | `stack project create --cloud …` required | ## Test plan - [x] `pnpm lint` (stack-cli, backend, e2e) — clean - [x] `pnpm --filter @stackframe/stack-cli typecheck` — clean - [x] `pnpm --filter @stackframe/stack-cli exec vitest run` — **72/72 passing** (new unit tests: `parseExecTarget`, `resolveConfigFilePathForPull`, `resolveProjectListSources`, `formatProjectList`) - [x] `pnpm test run apps/e2e/tests/general/cli.test.ts` — **73 passing, 4 skipped, 0 failing**. New e2e cases cover: - `exec` with neither flag → errors with "Specify a target" - `exec` with both flags → errors with "not both" - `exec --config-file` with missing file / missing PCK / unreachable API - `exec --config-file` happy path against a real local-emulator backend (gated on `NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true`) - `config pull` cwd fallback to `./stack.config.ts` - `config pull` with no `--config-file` and no cwd `stack.config.ts` → errors with `Pass --config-file …` - `project list --cloud --dev` together → errors - `project list` default with unreachable emulator → cloud results + single stderr warning - `project create` without `--cloud` → errors - All previously-`--cloud` exec cases ported to `--cloud-project-id` - [x] Manual smoke: `stack exec --help`, `stack project list --cloud --dev`, `stack project create` all emit the expected friendly errors / help text. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * CLI `exec`, `config`, and `project` commands now require explicit targeting via `--cloud-project-id` (cloud) or `--config-file` (local emulator). * `project list` now supports `--cloud` and `--dev` flags to display projects from both sources with target indicators. * Enhanced environment variable validation for emulator service ports with proper fallback handling. * **Bug Fixes** * `project list` now gracefully handles unreachable emulator with warning fallback instead of failure. * **Tests** * Expanded test coverage for project targeting, config file resolution, and emulator connectivity scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
024da3cacb
|
[Fix] freestyle-mock honors $PORT, drop server.listen string-patch (#1432)
## Summary The multi-worker freestyle-mock rewrite ([#1430](https://github.com/hexclave/stack-auth/pull/1430)) hardcoded `server.listen(8080)`, which collides with qstash inside the local-emulator container. Supervisord sets `PORT=8180` for freestyle-mock specifically to avoid this clash, but the new source ignores `process.env.PORT`. The local-emulator Dockerfile previously bridged this with a `server.replace('server.listen(8080)', ...)` string-patch on the embedded source. The new code is `server.listen(8080, () => { ... })` — the literal `'server.listen(8080)'` substring no longer matches, so the replace silently no-ops and freestyle-mock binds 8080. qstash then can't start (`address already in use: 127.0.0.1:8080` → FATAL), the backend (which depends on qstash) never comes up, and the emulator smoke test times out. Observed in [this run](https://github.com/hexclave/stack-auth/actions/runs/25832479377): ``` smoke-test: FTL address already in use: 127.0.0.1:8080 smoke-test: WARN exited: qstash (exit status 1; not expected) smoke-test: INFO gave up: qstash entered FATAL state, too many start retries too quickly [603s] SMOKE TEST FAILED: backend /health?db=1 did not return 200 within 300s ``` ## Changes - `docker/dependencies/freestyle-mock/Dockerfile`: `server.listen(PORT)` where `PORT = process.env.PORT || 8080`, plus the startup log reflects the actual port. - `docker/local-emulator/Dockerfile`: drop the now-redundant string-replace for the listen call. The two remaining replaces (`fs/promises` import + node_modules symlink) are unrelated and kept. ## Test plan - [ ] QEMU emulator build workflow passes on this branch (smoke test reaches healthy backend). - [ ] Verify locally that supervisord's `PORT=8180` is honored by freestyle-mock and qstash binds 8080 cleanly. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Server listening port is now configurable via PORT (default 8080). * Local emulator startup adjusted to better handle dependencies and create a node_modules symlink for smoother local runs. * Seed/process transaction timeout increased to 90s for reliability. * Local database statement timeout changed to 0 (no statement timeout). * **CI** * Added step to enable and validate KVM access during emulator builds. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1432) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
988505249b
|
[Revert] team invitation accept email-match check (#1431)
## Summary Reverts the team-invitation accept email-match check added in #1365 in response to user friction. The check required the signed-in user to own the invited email as a *verified* contact channel before accepting, which rejected legitimate flows where the recipient hadn't verified the invited email on their account. - Drops the pre-claim `validate` hook in `accept/verification-code-handler.tsx` that compared the accepting user's verified channels to the invited email. - Drops the `normalizeEmail(body.email)` in `send-code/route.tsx` (only existed to make the now-removed compare case-insensitive). - Removes the four e2e tests that asserted the check (mismatch, does-not-burn, case-insensitive, happy-path). - Reverts `items.test.ts` invitee sign-up back to bare `Auth.fastSignUp()`. ## What's preserved - **`TeamInvitationEmailMismatch`** in `packages/stack-shared/src/known-errors.tsx` and its plumbing in `client-interface.ts` / `client-app-impl.ts` / `client-app.ts` — intentionally kept so the check can be reinstated in a focused follow-up without re-plumbing the SDK return types. - **The TOCTOU fix** from the same PR (atomic `updateMany` claim in `route-handlers/verification-code-handler.tsx` and its 5-parallel-redemption test) is unrelated and untouched. ## Test plan - [x] `pnpm lint` — clean (28/28) - [x] `pnpm --filter @stackframe/backend --filter @stackframe/e2e-tests typecheck` — clean - [ ] Pre-existing dashboard typecheck failure on `transaction-table.tsx:347` (`refundEntries`) reproduces on `origin/dev` — not caused by this PR - [ ] e2e team-invitations + items + otp sign-in suites <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Simplified team invitation acceptance process by removing strict email matching requirements, allowing users to accept invitations more flexibly. * **Tests** * Updated team invitation tests to reflect simplified acceptance flow. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1431) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c0871e64b2
|
[Feat(tests)] multi-worker freestyle mock (#1430)
### Context Lots of flakiness comes from email polling leading to timeouts. This usually happens when freestyle mock cannot service requests in time. Old mock was single threaded and so clogged up by a lot of requests. ### Summary of Changes A multiworker system should be better. |
||
|
|
2cf0f6f981
|
[Apps] Adding support app alpha and dogfooding (#1368)
<!-- 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** * Support app: inbox UI to create, view, reply, and manage conversations (status, priority, assignee, tags, internal notes). * Dashboard pages: Conversations and Support Settings; feedback can create managed conversations. * Public/internal APIs for listing, creating, updating, and fetching conversation details; client-side helpers. * **SLA** * Configurable first/next response targets, urgency classification, and timing logic. * **Data** * New conversation persistence (conversations, entry points, messages) and migration tests; preserves conversations on user/team deletion and anonymizes sender data. * **Tests** * Unit, migration, and end-to-end tests added. * **Documentation** * Updated docs describing conversation model and workflow rules. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
3385d6e2b0
|
[Fix] recover stale external db requests (#1428)
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
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (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
Failures between claiming and the deletion of outgoing requests from the handler can leave requests stale and never clean them up. Some of these requests may also have duplicates that are fresh in the outgoing queue. These requests need to be deleted or retried. It's important to still log the stale requests to sentry so the root cause can be investigated. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved detection and recovery of stale outgoing requests; telemetry now records precise reset/deleted counts and includes sampled affected IDs. * Added an early fast path to skip unnecessary external calls when there are no pending requests. * **Refactor** * Consolidated stale-request handling into a dedicated helper and optimized recovery logic; poller telemetry now includes claim-limit attributes. [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1428) <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
4648fc1899
|
[Feat] new scripts on migrate/seed/init run for internal (#1421)
### Context One script grants free plan to any team which is a customer of the internal project who doesnt have it already. We also want to migrate our users (internal) to the latest version of their products. Needed because some subs on dev right now dont have a plan. And internal isnt using latest version of its own growth plan. ### Describing the Paths we want to Account for 1. Users on production who currently don't have a plan should get free plans, since this script is run with every migrate 2. Users on production should get the latest version of each plan of ours. So a forced migration to latest version of internal project plans 3. No other project's products/product lines should be affected. They will continue to have product versioning 4. 2 should apply to test mode subscriptions as well, on top of stripe subscriptions. All of them should be refreshed 5. Internal project itself should get latest version of its own growth plan 6. If the bulldozer write fails, we should be able to recover on next migration (this should already be handled by init bulldozer script, because it checks if prisma db and bulldozer db are out of sync) 7. if the regenerate or backfill fail, we should be able to recover just by rerunning the script 8. Product version table should not balloon. No table should really balloon ### What I've tested on local 1. Put in 1000 db subscription rows, made them all stale and then ran the regen script. It took about 6 minutes to update all of them, and it was idempotent so rerunning it again did nothing. 2. With proper stripe keys I switched off of test mode on the internal app, granted a product to a new team and updated the product's item list. At this point I checked and the new team had the outdated version of the product. Then I ran the regen script and the new team was moved to latest product version. 3. Tried the above with the internal team's growth plan too and it worked as well. 4. Backfill actually grants free plan ### Deployment strategy in prod Run the backfill and the regen scripts once each after your migrations on the prod db. `pnpm db:backfill-internal-free-plans` will make sure every team has a free plan at least if they dont have an existing plan (and it is idempotent). After that, run `pnpm db:regen-internal-subscriptions-to-latest` which will migrate every user to the latest version of their plan (i.e latest snapshot). This should also be idempotent. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Automated backfill to grant internal free plans to qualifying billing teams. * Regeneration tool to refresh internal subscription snapshots to the latest product versions. * **Chores** * Added CLI commands and package scripts to run backfill and regen jobs. * Database init now runs payment initialization before backfill/regen. * **Tests** * Integration and unit tests added/updated to validate backfill, regeneration, and free-plan idempotency. [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1421) <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
d2030e826b | Unhandled promise rejections no longer kill the whole server if not in development | ||
|
|
76023af9d6
|
Custom Dashboards Versioning fix (#1418)
This PR fixes the versioning error that we ran into for custom dashboards. Now if the latest version of the packages does not work, we fall back to the version that is one patch below the latest version. We log this into sentry. If the fall back doesn't work either, we log that into sentry as well and show the user an error message. Apart from that, I also made changes to ensure dashboards with older versions of the dashboard-ui-component package would still work. Each dashboard now stores the version it was created with, as a comment at the top of its source code, and we use that version when loading the dashboard. When a dashboard gets edited via the AI chat, we re-stamp it with the latest version of the package so it stays up to date. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved error handling and reporting for dashboard load failures; host surfaces structured dependency errors for faster diagnostics. * Added automatic fallback loading for missing resources to reduce load failures. * Fixed page height calculation so pages align correctly with the viewport. * **New Features** * Generated and editor-provided dashboard code is now stamped with the app version for clearer provenance. * **UI/UX Improvements** * Clearer, more informative error messages when custom dashboard loading encounters issues. [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1418) <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
efa2153d47 | Improve project overview weekly users | ||
|
|
e61c70d3c1 | Update implementation | ||
|
|
e50358710a
|
fix(tests): use sql.json in onboarding migration test and refresh metrics snapshot (#1420)
## Summary
Two small test-maintenance fixes that came up while running the suite:
- **Onboarding migration test**
(`apps/backend/prisma/migrations/20260420000000_add_project_onboarding_state/tests/default-and-updates.ts`):
switch the JSON insert from `\${JSON.stringify(onboardingState)}::jsonb`
to `\${sql.json(onboardingState)}`. This matches the pattern used by
every other migration test in the repo (see
`20260214000000_fix_trusted_domains_config/tests/*`) and lets the
`postgres` driver handle serialization and parameter binding
consistently rather than relying on a manual `::jsonb` cast.
- **Internal metrics snapshot**
(`apps/e2e/tests/backend/endpoints/api/v1/__snapshots__/internal-metrics.test.ts.snap`):
update `active_users_by_country.AQ` to list `mailbox-2` before
`mailbox-1`. The `should return metrics data with users` test signs in
`mailbox-1` (mailboxes[0]) into AQ first, then later signs `mailbox-2`
(mailboxes[1]) into AQ, so sorted by `last_active_at_millis desc`
`mailbox-2` should come first. The snapshot now matches that ordering.
No production code is touched — both changes are limited to test
fixtures.
## Test plan
- [ ] `pnpm -C apps/backend test run` (migration tests)
- [ ] `pnpm -C apps/e2e test run internal-metrics` (snapshot test)
- [ ] `pnpm lint`
- [ ] `pnpm typecheck`
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* No user-facing behavior changed; test flows made more robust and less
flaky (migration validation, metrics ingestion polling, CLI expiry
checks, failed-emails digest expectations).
* **API / Documentation**
* CLI auth default expiration reduced from 2 hours to 2 minutes (updated
OpenAPI defaults and related test expectations).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
9ff2c13f8d | Add functionality to restrict or unrestrict users | ||
|
|
80a26ca15d | chore: update package versions | ||
|
|
227dac6567
|
feat(dashboard): add weekly users metrics for projects (#1412)
- Introduced a new API endpoint to fetch weekly and daily user metrics for managed projects. - Updated the dashboard to utilize this new endpoint, replacing the previous daily active users data. - Created a new component to visualize weekly users metrics in the project cards. - Refactored existing components to accommodate the new data structure and ensure proper rendering of user activity charts. This change enhances the analytics capabilities of the dashboard, providing better insights into user engagement over time. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * New internal endpoint providing per-project weekly user totals and 7-day daily activity series. * **Updates** * Dashboard and project cards switched from DAU to weekly user metrics; main metric shows weekly users and label reads "users/wk". * Charts now display weekly-user-aware sparklines alongside daily activity. * **Tests** * Added unit tests covering weekly aggregation and daily-series merging. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
261d8923d4
|
stack-cli: support self-hosted URLs and tighten CLI auth polling (#1419)
## Summary - **Self-hosted CLI**: read `STACK_API_URL` / `STACK_DASHBOARD_URL` from env in `stack-cli` so the published CLI can talk to self-hosted Stack Auth installs without a custom build. The existing `STACK_CLI_PUBLISHABLE_CLIENT_KEY` override is kept as-is. - **Docker example**: surface the three CLI-relevant vars in `docker/server/.env.example` so self-host operators see them. - **Tighter polling-code TTL**: default `2h -> 2min`, max `24h -> 15min` for the CLI auth polling code. The code is only valid while a user is actively waiting in `stack login`, so a tight window limits the blast radius of a leaked code. - **Raw-SQL poll handler**: convert `apps/backend/src/app/api/latest/auth/cli/poll/route.tsx` from `prisma.cliAuthAttempt.*` to raw SQL targeted at the tenancy source-of-truth schema, matching the pattern already used by the initiate handler in `apps/backend/src/app/api/latest/auth/cli/route.tsx`. ## Test plan - [ ] `pnpm typecheck` - [ ] `pnpm lint` - [ ] `pnpm test run` (focus on CLI-auth tests if any) - [ ] Manual: `stack login` against a local backend - polling code now expires after ~2 minutes by default - `waiting` / `success` / `used` / `expired` branches still return correct status codes and bodies - [ ] Manual: published `stack-cli` against a self-hosted backend with `STACK_API_URL` / `STACK_DASHBOARD_URL` set, end-to-end login Made with [Cursor](https://cursor.com) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * More robust CLI authentication polling with atomic database updates to prevent races; returns explicit statuses (waiting/expired/used/success) and provides the refresh token on success. * **Changes** * Default CLI auth token TTL reduced to 2 minutes and capped at 15 minutes. * Anonymous refresh token is considered present only when not null; null expiry is treated as not-expired. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
68ae6d1f1c
|
[codex] Add TanStack Start SDK integration (#1399)
## Summary - Adds the generated `@stackframe/tanstack-start` workspace package registration. - Adds TanStack Start platform macros/dependencies to the SDK template and generator. - Adds TanStack Start cookie/token-store support plus the handler SSR guard needed by Start. ## Scope This intentionally excludes Dashboard V2 routes, hooks, components, app shell logic, and dashboard API type additions. Those stay in the existing dashboard PR/branch. ## Validation - `pnpm install --lockfile-only --ignore-scripts` - `pnpm install --ignore-scripts` - `pnpm -C packages/template lint src/components-page/stack-handler-client.tsx src/lib/cookie.ts src/lib/stack-app/apps/implementations/client-app-impl.ts` Package typecheck was attempted with `pnpm -C packages/template typecheck`, but the clean worktree lacks generated package declaration outputs for workspace dependencies such as `@stackframe/stack-shared` and `@stackframe/stack-ui`. Per repo instructions, package builds/codegen are not run by agents. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * TanStack Start integration: published SDK package, example demo app, dashboard onboarding flow, framework-aware CTAs/docs, and a TanStack-specific provider for client-only auth routes. * Improved client/server auth: safer runtime guards and consistent cookie/token-store behavior across SSR and client. * **Documentation** * New Integrations guide and expanded getting-started/setup docs with TanStack Start examples and env/key guidance. * **Chores** * Template, build, tooling, and demo config updates to support the new platform. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
acc646cb0b
|
stack-cli: cloud/local init flow, auto-create on empty projects, post-setup next-steps (#1383)
### Summary Reworks `stack init` UX, adds Sentry error reporting to the CLI, polishes the emulator start flow, and overhauls the local-emulator dashboard's "Open config file" dialog. #### `stack init` flow - **New top-level flow.** Drops the old "link existing vs. create new local" fork. `init` now asks *where* to create the project — "Stack Auth Cloud" or "Local". Adds a new `create-cloud` mode that logs the user in, creates a cloud project, mints keys, and writes `.env` — no round-trip through the dashboard. - **Conditional emulator-install warning.** The "Local" choice label only shows "(requires local emulator installation, ~1.3gb storage required)" when the QEMU image isn't already on disk; otherwise it shows "(emulator already installed)". Driven by a new `isEmulatorImageInstalled()` helper in `commands/emulator.ts`. - **Auto-create on zero-projects.** When the link-from-cloud path hits an empty project list, the CLI now prompts *"You don't have any Stack Auth projects yet. Would you like to create one?"* and, on yes, runs the same flow as `stack project create`. Skips the pointless "select a project" prompt when we just created one. - **MCP-server notice.** Before invoking the coding agent, the CLI announces that it's also registering the Stack Auth MCP server (`mcp.stack-auth.com`) so the agent can answer Stack-specific questions going forward. - **Local-emulator env header.** When `writeProjectKeysToEnv` runs in `local` mode it writes a 3-line comment header above the keys explaining they're emulator-only and only valid while the emulator is running. - **"What's next" footer.** After setup finishes, prints a short orientation block: where the sign-up/sign-in routes live (`/handler/sign-up`, `/handler/sign-in`), how to start the local emulator (for `create` mode), a dashboard deep link for cloud projects (respects `STACK_DASHBOARD_URL`), and a docs link. #### Sentry error reporting (`lib/sentry.ts`, `index.ts`, `tsdown.config.ts`) - New `lib/sentry.ts` initializes `@sentry/node` with PII scrubbing (Stack key prefixes, JWTs, home-dir paths, sensitive field names like `token`/`secret`/`password`/`dsn`). - DSN is baked at build time via a tsdown `define` sentinel (`__STACK_CLI_SENTRY_DSN__`) — no DSN in source, no runtime env-var dependency for installed users. CI sets `STACK_CLI_SENTRY_DSN_BUILD` before `pnpm build`. - Disabled when `NODE_ENV=development` or `CI`. No user opt-out. - Wired into `main()`'s catch (only for unexpected errors — `CliError`/`AuthError` still print and exit cleanly) plus `uncaughtException` and `unhandledRejection` handlers via a `handleFatal` helper. #### `stack emulator start` welcome - After a fresh start (not when reusing a running VM, not when `--config-file` keeps stdout JSON-only), prints a short "Emulator is up" block with service URLs (dashboard / backend / inbucket) and common commands (`status`, `stop`, `reset`, `run`). #### Local-emulator dashboard "Open config file" dialog The dialog at `http://localhost:26700` (when no project is loaded) used to be a single text input asking for an absolute path, with no explanation of where that path comes from. **Backend** (`apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`): - POST is now tolerant of directory paths or paths that don't end in `.ts`/`.js`/`.mjs` — it appends `stack.config.ts` and creates the file if missing (`writeConfigToFile` mkdir's parents). Lets users paste a project folder instead of hunting for the config file. - New GET endpoint returns up to 20 most-recent `LocalEmulatorProject` rows joined with their display names, sorted by `updatedAt` desc. Same `isLocalEmulatorEnabled()` + client-auth gating as POST. **Dashboard** (`apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx`): - Title changed to "Open your Stack Auth project". Description now explicitly ties the file to `stack init`: *"Point the local dashboard at the `stack.config.ts` in your project. If you just ran `stack init`, it was created at the root of that project."* - Added: *"Don't have one yet? Paste your project folder path instead and we'll create stack.config.ts for you."* - Recent-projects list (clickable rows that prefill the input) fetched from the new GET endpoint when the dialog opens. - OS-specific copy-path tip below the input (macOS ⌥-Copy as Pathname, Windows Shift+RC Copy as path, Linux `realpath`). - "Open project" button is disabled when the input is empty. - All error paths (empty input, non-absolute path, server errors, exceptions) surface via destructive toasts instead of throwing. Why no native file picker: browsers do not expose absolute filesystem paths from `<input type="file">`, drag-and-drop, or the File System Access API. The backend requires an absolute path, so a Finder-style picker isn't possible from a web page. The recent list + OS tips are the workaround. ### Goal The previous `init` flow dead-ended new users: if you had no project you got an error telling you to go create one in the dashboard and come back. The happy path also forced a choice between "link existing" and "create local emulator" — not the question most users are trying to answer. The emulator dashboard's open-project dialog had similar friction: an unexplained path field with no recall of previously-opened projects. And the CLI silently swallowed unexpected errors with no telemetry. This branch makes the first-run path work end-to-end from the terminal, gives the emulator dashboard a usable open-project surface, and turns CLI crashes into actionable bug reports. ### How to review - Start with `packages/stack-cli/src/commands/init.ts` — the whole user-facing flow lives in `runInit`. Mode dispatch at the top, `handleCreateCloud` is the new cloud branch, `printNextSteps` is the footer, the MCP notice prints right before `runClaudeAgent`. - `packages/stack-cli/src/lib/sentry.ts` is small and self-contained; the sentinel-replacement contract is in `tsdown.config.ts`'s `define` block. Confirm `dist/index.js` contains zero `__STACK_CLI_SENTRY_DSN__` occurrences after a build with the env var unset, and the actual DSN host after a build with it set. - `packages/stack-cli/src/commands/emulator.ts` — `printEmulatorWelcome()` is the welcome block; `isEmulatorImageInstalled()` is the new exported helper used by `init.ts`. - `apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx` — the directory-tolerance branch is in the POST handler around the `looksLikeConfigFile` check; the GET handler is appended at the bottom. - `apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx` — dialog markup, recent-list fetch effect, `pathCopyTip` memo, and the toast-based error handling in `handleOpenConfigFile`. - Non-interactive (CI) paths stay strict: empty-project list still errors with a pointer to `stack project create --display-name`. No surprise project creation in CI. - No tests. The CLI has no harness for the interactive flow; verification is manual. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Recent local emulator projects listed in the config dialog for quick selection. * New CLI create-cloud mode and --display-name flag; interactive cloud project creation and clearer next steps. * Emulator start shows a welcome banner with service URLs when a new instance starts. * **Improvements** * Config dialog UX, validation, error-toasting, and platform-aware copy refined; “Open project” disabled for empty/invalid paths. * CLI: centralized interactive project creation and improved fatal error handling. * **Chores** * Sentry added and initialized for CLI error reporting. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Bilal Godil <bg2002@gmail.com> |
||
|
|
6eaf49237f
|
Add fix command registration and update agent UI label handling (#1387)
Adds a fix command to the stack cli <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a CLI "fix" command to submit Stack Auth errors (flag, stdin, or interactive), confirm before applying changes, show a customizable progress label, and produce a final markdown report with Error, Files changed, and Solution. * Added a CLI "doctor" command to analyze projects (framework override, output directory, JSON output), run framework-specific checks, validate env and config, and exit non-zero on failures. * **Tests** * Added comprehensive end-to-end tests for the doctor command. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
647883c7ac
|
Move MCP server into a standalone apps/mcp app (#1405)
## Summary Splits the Stack Auth MCP server out of `apps/backend` and into a dedicated Next.js app at `apps/mcp/`, served on port `:42` (suffixed via `NEXT_PUBLIC_STACK_PORT_PREFIX`) and exposed in production at `https://mcp.stack-auth.com/mcp`. The backend no longer carries the MCP transport route; clients now point at the new host. Base: `dev` → Head: `chore/move-mcp-to-a-sep-app` Scope: 34 files, +1425 / −353 ## What changed - **New app** `apps/mcp/` — standalone Next.js + `@vercel/mcp-adapter`, with: - `src/app/api/internal/[transport]/route.ts` — MCP transport handler (moved from backend) - `src/app/mcp/route.ts`, `src/app/route.ts` — public landing + setup page - `src/app/health/route.ts` — health check - `src/mcp-handler.ts`, `src/setup-page.ts`, `src/analytics.ts` - **Backend** drops `apps/backend/src/app/api/internal/[transport]/route.ts` (−105) — MCP code is gone from the backend image. - **Dashboard** install hint updated to point at `https://mcp.stack-auth.com/mcp` (was `/`). - **Dev launchpad** gets an MCP tile so the new service shows up alongside the rest of the local stack. - **CI** workflows (`db-migration-backwards-compatibility`, `e2e-api-tests*`) start the MCP service in the background before running tests. - **Docs** (`docs-mintlify`, `docs/`) and `init-stack` / `init-prompt` updated to reference the new URL. - **E2E** `apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts` reworked to hit the new host; `helpers.ts` and env files gain an MCP base-URL var. ## Visuals ### New `apps/mcp` setup page (`https://mcp.stack-auth.com/`) The standalone app's root now serves a self-contained MCP setup guide with per-client instructions (Cursor, VS Code, Codex, Claude Code, Claude Desktop, Windsurf, ChatGPT, Gemini CLI):  ### Dev launchpad now lists the MCP service New tile at port suffix `:42`, importance 2, alongside Backend / Dashboard / Demo app:  ## Notes for reviewers - The MCP transport endpoint moved path: it was mounted under `/api/internal/[transport]` in the backend; in the new app it's at the same path but on the dedicated host. The public-facing URL is `https://mcp.stack-auth.com/mcp`. - `apps/mcp` ships its own PostHog analytics client (`src/analytics.ts`) so the backend doesn't have to proxy events for it anymore. - Port allocation: `${PORT_PREFIX}42` (default `8142` in dev). Picked to fit the existing dev-launchpad importance-2 row. - No DB migrations. ## Test plan - [x] `apps/mcp` builds and `pnpm dev` serves on `:8142` - [x] Dev launchpad renders the new MCP tile (screenshot above) - [x] MCP setup page renders client tabs (screenshot above) - [x] E2E `mcp.test.ts` updated to hit the new host - [ ] CI green on `e2e-api-tests*` and `db-migration-backwards-compatibility` workflows (they were touched to start the MCP service) - [ ] `init-stack` / `mcp.ts` install flow lands users on the new URL <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Standalone MCP app added with a public /mcp endpoint and health check. * MCP appears in the dev-launchpad apps list. * **Documentation** * MCP endpoint updated to https://mcp.stack-auth.com/mcp in all setup guides and installer snippets. * Setup page enhanced with detailed client install tabs and instructions. * **Chores** * MCP service integrated into CI/e2e workflows and local env configs. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
7acbd8d56d | Improved StackAssertionError error logging | ||
|
|
d69773c9df | Retry OAuth refreshes | ||
|
|
616d805443
|
layout fix (#1408)
This PR fixes a layout bug <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Updated dashboard top-panel sizing to use viewport-aware height for a more consistent fit across screen sizes. * Improved dark-mode spacing to prevent clipping and ensure content remains fully visible without extra scrolling. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
7f35ae7d54 |
Fix migration tests
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
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
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
|
||
|
|
5ccd8dfd38 | Update GitHub URL | ||
|
|
bc6347e3c3
|
[Feat]: set flag to disable billing (#1417)
### Context There are some kinks to work out with deploying plan limits onto prod, so we'd like to disable it temporarily. ### Summary of Changes We update all call sites of the item quantity things with a flag based check. Idea is when flag is set to true, it should function as if there are no limits. |
||
|
|
765b0f4e29
|
New setup (#1413) | ||
|
|
440c18c894 | chore: update package versions | ||
|
|
2e41fde9c2 | _useSession now refreshes tokens more aggressively | ||
|
|
8901a93b55
|
Rename STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY to STACK_INTERNAL_PROJECT_SECRET_SERVER_KEY (#1415)
## Summary - Renames the env var `STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY` to `STACK_INTERNAL_PROJECT_SECRET_SERVER_KEY` everywhere it is used (20 occurrences across 8 files), covering backend env files, the Prisma seed script, runtime config, and the docker entrypoint/local-emulator scripts. - Mirrors the prior publishable-client-key rename in #1411. ## Test plan - [x] `pnpm lint` - [x] `pnpm typecheck` - [ ] Verify local emulator still boots with the renamed variable - [ ] Verify any deploy/CI configs that set the old name are updated alongside this change <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated internal environment variable naming for API key management and server configuration consistency across backend systems, Docker deployment, and local development setup. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
24245ae54e
|
Rename STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY to STACK_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY (#1411)
## Summary - Renames the env var `STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY` to `STACK_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY` everywhere it is used (24 occurrences across 9 files), covering backend env files, the Prisma seed script, runtime config, and the docker entrypoint/local-emulator scripts. ## Test plan - [x] `pnpm lint` - [x] `pnpm typecheck` - [ ] Verify local emulator still boots with the renamed variable - [ ] Verify any deploy/CI configs that set the old name are updated alongside this change <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated internal environment variable naming for consistency across backend configuration files and deployment scripts. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b0812c8808
|
feat(analytics): gzip event batch body to bypass adblockers (#1407)
## Summary
The `POST /api/latest/analytics/events/batch` endpoint was being dropped
by content-blocking browser extensions (adblockers) because the JSON
request body literally contains the substring `$click`. Many filter
lists pattern-match on tokens like that and silently kill the request —
analytics events from anyone with an adblocker enabled never reached our
backend.
This PR encodes the request body so keyword-matching filters can't see
those tokens, while keeping the URL path unchanged (only the body was
being matched here) and keeping older SDK clients working.
## Approach
- **Client**: gzip the JSON payload via the browser-native
`CompressionStream("gzip")` API and POST it as
`application/octet-stream`. Falls back to plain JSON if
`CompressionStream` isn't available (very old browsers / non-browser
runtimes).
- **Server**: a yup `.transform()` on the body schema detects an
`ArrayBuffer`/`Uint8Array` input, gunzips it, and `JSON.parse`s before
normal schema validation runs. The existing JSON path is untouched, so
requests from older SDK versions in the wild continue to work without
changes — and all existing schema-error snapshot tests still pass
verbatim.
- **Safety**: hard caps on compressed (1 MB) and decompressed (8 MB)
sizes guard against zip-bomb shaped abuse. `node:zlib`'s
`maxOutputLength` enforces the latter at the C++ layer.
Bonus: gzip also gives a meaningful bandwidth win — click/page-view
events compress very well — and keepalive bodies (which have a 64 KB cap
in browsers) get more headroom.
## Files
- `apps/backend/src/app/api/latest/analytics/events/batch/route.tsx` —
body schema gains `.transform()` that gunzips binary inputs; size limits
added; everything else unchanged.
- `packages/stack-shared/src/interface/client-interface.ts` —
`sendAnalyticsEventBatch` now routes through a new module-level
`encodeAnalyticsBody` helper that gzips and switches Content-Type. Same
outer signature; encoding is internal.
- `apps/e2e/tests/backend/backend-helpers.ts` — `niceBackendFetch` gains
optional `rawBody`/`rawContentType` params so tests can send non-JSON
payloads. Existing JSON callers unaffected.
-
`apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts`
— adds two tests:
- happy path: gzipped binary body returns `inserted: 1`
- sad path: garbage bytes return 400
## Out of scope (intentional)
- **URL path renaming**: not all adblockers match on `/analytics/`, but
some do. We're shipping the body fix first and will revisit if requests
still get blocked after deployment.
- **Encryption**: gzip is enough to defeat keyword filters. Encryption
adds key-management cost with no real adversary.
- **SDK regen**: only `client-interface.ts` (in `stack-shared`) was
touched; `event-tracker.ts` (the caller) is unchanged because it already
passes a JSON string. No `pnpm -w run generate-sdks` needed.
## Test plan
- [x] `pnpm typecheck` — green
- [x] `pnpm lint` — green
- [ ] Manually verify in dev: enable adblocker, click around with
analytics enabled, confirm batch requests now go through
- [ ] Spot-check ClickHouse `analytics_internal.events` shows the
expected rows
- [ ] Run the new e2e tests (`pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts`)
and confirm both new cases plus all preexisting snapshots pass
- [ ] Confirm the JSON back-compat path still works by hitting the route
with the existing JSON-body curl/test payloads
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Analytics batch uploads now accept gzipped binary payloads; clients
can send compressed bytes and the server will detect and decompress.
* Client sender can gzip event batches (falls back to JSON) and uses
keepalive to choose JSON vs compressed bytes.
* **Bug Fixes**
* Malformed, non-gzip, or overly-large compressed payloads now return a
clear 400 response.
* **Tests**
* Added E2E and unit tests plus test-helper support for raw/gzipped
request bodies and encoding behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
185bddec9e
|
[Dashboard] Redefine the user page with tabs and updated UI (#1351)
<!-- 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** * Tabbed user profile with Activity (30-day analytics, KPIs, daily chart, top lists, recent events), Payments (transactions, subscriptions, product/item balances) and an activity heatmap sidebar. * New internal user-activity API and admin-facing activity hook; admin API client can fetch per-user activity. * **UI/UX Improvements** * Unified menus, cards and tables; inline editable user details with accept/revert; metadata editor validates JSON; country-code input has draft editing; tabs support optional icons. * **API** * Transactions endpoint and admin transaction queries now support optional customer-scoped filtering. * **Tests** * End-to-end coverage for the user-activity endpoint. <!-- end of auto-generated comment: release notes by coderabbit.ai --> <img width="1326" height="752" alt="image" src="https://github.com/user-attachments/assets/97c04dca-db59-4357-98b1-8eae5a7a3673" /> <img width="1142" height="251" alt="image" src="https://github.com/user-attachments/assets/e1aa44fc-0d7e-436d-90a5-c7cb15155e24" /> <img width="1170" height="1125" alt="image" src="https://github.com/user-attachments/assets/bf6659fd-a9b5-4ae6-a13d-dab9956ad650" /> |
||
|
|
7a54e82865 | Revert globe coloring to old algorithm | ||
|
|
3a5153f4db
|
feat(payments): collect 0.9% platform fee on every stripe money movement (#1378)
## Summary Charges the platform 0.9% on both legs of each transaction on non-internal projects. - **Charge leg** — rides along via Stripe's native \`application_fee_amount\` / \`application_fee_percent\` params on the PaymentIntent / Subscription. - **Refund leg** — Stripe's default reverses our charge-leg fee on refund, netting us zero. We disable that with \`refund_application_fee: false\` ## Refs - https://docs.stripe.com/api/subscriptions/create#create_subscription-application_fee_percent - https://docs.stripe.com/api/payment_intents/object#payment_intent_object-application_fee_amount --------- Co-authored-by: nams1570 <amanganapathy@gmail.com> |
||
|
|
c01c052ac9
|
[Refactor][Feat] Implement Plan Limits for Hard-and-Soft Item Caps (#1215)
### Suggested Review Areas Please see `plans.ts` and `seed.ts` to verify whether the item caps are where they should be. Outside of that, each commit should be atomic so stepping through the commits should give you an idea of how I implemented each limit. ### Discussion Something to discuss: when a user cancels team/growth we regrant free fine, but any extra-seats they had just keeps billing. So they end up paying ~$29/mo per extra-seat on top of free's 1 seat, which is strictly worse than just staying on team. This surfaced while manually testing this PR, we only enforce the add-on base requirement at purchase time, nothing cascades on cancel. Should we cascade cancel add ons? ### Context Now that we have a stable suite of products for stack-auth, we want to limit the items under each product a customer has access to based on their plan. So for example, a free plan user has a certain amount of emails they can send out each month, and so on. We try to implement limits in this PR. ### Summary of Changes Implemented hard limits for dashboard admins, analytics per-query timeouts, sent email monthly capacity, events, and session replays. Implemented a soft cap for auth users (where if there's a signup beyond the limit, we log it to sentry so we can manually choose to email that user/team). For auth users, we do not block new user sign ups once plan limit has been hit. We also don't degrade or impact the customer experience. It logs to sentry and it is up to us to take manual action to email the user to upgrade the plan. Also, implementation wise, we count all the users across all the projects for this team and compare it to their plan item limit, rather than debiting items like we do for other approaches. As a soft cap, this should be fine plus this is a better source of truth. For email capacity, we operate a monthly limit of emails. Once this is hit, no more emails can be sent until the next month/ a plan upgrade. These emails will be treated as a send error, so they can be manually resent once the capacity is reset. With respect to the `email-queue` state engine, they go from `SENDING`->`SERVER_ERROR`, hooking into the existing state engine flow, with an external error that shows it's because of the rate limit. This is cleaner than inventing a new state that is identical for all intents and purposes to `SERVER_ERROR`. We check in processSingleEmail since that maps to the sending state. For analytics query timeouts, the backend route accepts a timeout parameter with the request. The way we implement the timeout for each query is by taking the `min(request_timeout,plan_timeout)` and using that. This determines how long a query can run for. For analytics events, there are server-side events (like refresh token refreshes or sign up rule triggers) and client side events (like page views or clicks). When these events occur, they are written to the events table in clickhouse. We choose to implement a hard cap for the total events, not just server side or client side. Once the cap is hit, we stop storing the events and display a banner on the analytics page. A different banner renders when we are at >=80% of total plan capacity. For session replays, we stop creating new session replays when the limit is hit. Old replays can still have chunks appended to them. The source of truth here is the session replay table- a new replay corresponds to a new row in the table. We have similar banners as to the events. Dashboard admins should be 4 for both team and unlimited. #### Implementation Caveats For debiting items across these limits, we now use `tryDecreaseQuantity` at the beginning. This means we debit first if possible before conducting the action (like writing events to clickhouse). In practice, this means that if clickhouse fails, then the user is debited for something that doesn't happen. However trying to build a refund workaround would be very clunky, and also, clickhouse is reliable. For debits that are very small in the order of things (say, 200 items on a 100k plan), it doesn't mean much. For emails, we don't debit items if it's a retry. This prevents the user for being charged multiple times for effectively one email. ### UI Changes The only UI changes in this PR are having certain banners render in analytics when a customer is approaching/ is at their monthly limit of session replays or events. ### Out of Scope for this PR We do not have metered pricing yet, so events/session replays/ email use beyond the limits cannot be charged yet. This is why for this implementation, we rely on hard and soft caps. We do not implement payment per-transaction pricing yet. That is deferred to a followup PR. The UI for the onboarding call will be set up as part of the overall onboarding flow which doesn't exist yet, so it has been deferred. Since the UI for the dashboard home page and project/account settings is currently being reworked, finding a better spot for plan upgrades is not handled in this PR. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Session replays added as a monthly included entitlement; onboarding calls added to Team/Growth plans. Dashboard banners warn about analytics-event and session-replay limits. Projects page adds extra-seat flow and improved invitation error handling. * **Behavior Changes** * Monthly renewal semantics for emails-per-month and analytics-events; analytics query timeouts now respect plan limits and are clamped. Email sends, analytics events, and new session creation are blocked when quotas are exhausted. Growth plan seats set to 4. * **Tests** * E2E and unit tests added to verify quota enforcement and free-plan regranting. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com> |
||
|
|
c69a27017b
|
fix team invitation email check + verification code TOCTOU (#1365)
## Summary Two authorization fixes in the backend. Both are pre-existing in `dev` and were found during a security audit of `apps/backend/src`. ### 1. Team invitation accept — email not validated [`team-invitations/accept/verification-code-handler.tsx`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx) destructured the invited email as `{}` and only used `data.team_id` + the accepting `user`. Any signed-in user in the tenancy who possessed the 45-char code could join the team as themselves — the invitation was not actually bound to the email it was addressed to. **Attack scenarios that work without this fix** - Forwarded invitation email (shared inbox, assistant inbox, auto-forward rules). - Screenshot of the invitation link pasted into Slack / Notion. - Insider with server-access reading the email outbox (`GET /api/latest/emails/outbox` returns rendered `html` + `variables.teamInvitationLink`). - Stale invite still sitting in spam after the invitee forwarded it elsewhere. **Fix.** The accept handler now requires that the accepting user owns the invited email as a *verified* contact channel on their account. Matches the invariant already used by the "list invitations for me" endpoint ([`team-invitations/crud.tsx:41-66`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/app/api/latest/team-invitations/crud.tsx#L41-L66)). Rejections return a new `TEAM_INVITATION_EMAIL_MISMATCH` (403) error. ### 2. Verification-code handler TOCTOU [`route-handlers/verification-code-handler.tsx`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/route-handlers/verification-code-handler.tsx) had a classic read-then-write TOCTOU: ```ts const verificationCode = await prisma.verificationCode.findUnique(...); if (verificationCode.usedAt) throw new KnownErrors.VerificationCodeAlreadyUsed(); // ... validation ... await prisma.verificationCode.update({ data: { usedAt: new Date() } }); // unconditional return await options.handler(...); ``` Five concurrent requests with the same code all pass the `if (usedAt)` gate, all mark the code used, all run the post-handler. For OTP sign-in the handler calls `createAuthTokens` which writes a fresh `projectUserRefreshToken` row per call — so **one OTP → N refresh tokens**. `auth/sessions/current` only revokes by `id: refreshTokenId` and there is no bulk-revoke for passwordless users (only password change in [`users/crud.tsx:1210`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/app/api/latest/users/crud.tsx#L1210) does `deleteMany`). A phished OTP therefore becomes a session-persistence primitive. **Fix.** Replace the unconditional `update` with a conditional `updateMany({ where: { …, usedAt: null } })` executed before `options.handler`; if `count === 0` the race was already lost and we throw `VERIFICATION_CODE_ALREADY_USED` (409). This also benefits MFA sign-in and passkey sign-in, which share the same handler. ## Changes | File | Change | |---|---| | `team-invitations/accept/verification-code-handler.tsx` | Require verified contact channel matching `method.email` | | `route-handlers/verification-code-handler.tsx` | Atomic `updateMany` claim gated on `usedAt: null` | | `stack-shared/src/known-errors.tsx` | New `TeamInvitationEmailMismatch` (403) | | `e2e/.../team-invitations.test.ts` | Two new tests (mismatch + happy path) | | `e2e/.../auth/otp/sign-in.test.ts` | One new test: 5 parallel redemptions of one OTP → 1× 200 + 4× 409 | ## Test plan - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts` — 27/27 pass - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/auth/otp/sign-in.test.ts` — 12/12 (+ 4 pre-existing `it.todo`) - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/auth/password` — 33/33 (+ 7 pre-existing todos) - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/contact-channels` — 24/24 - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/auth/passkey apps/e2e/tests/backend/endpoints/api/v1/auth/mfa` — 16/16 - [x] `pnpm --filter @stackframe/backend typecheck` — clean - [x] `pnpm --filter @stackframe/backend lint` + `pnpm --filter @stackframe/stack-shared lint` — clean ## Notes - The broader "plaintext credentials in DB + Sentry logs every header" finding from the same audit is **not** in this PR — a scrubber for `Sentry.setContext` request headers + unit tests is prepared on a local stash and will go out as a separate PR. - The team-invitation fix does not require any config change; fresh signups via the OTP / password flows that set `primary_email_verified: true` during creation already land the user with a verified channel matching the invited email, so the happy path is unaffected. ### Follow-up review (Codex) Addressed in follow-up commit `954cddb`: - **Finding 1 (High)**: mismatched invite acceptance was consuming the invitation before rejecting. Moved the email-ownership check into the pre-claim `options.validate` hook so a wrong-email attempt leaves `usedAt` untouched and the real recipient can still redeem. New test asserts this end-to-end. - **Finding 3 (Medium)**: invitation stored `body.email` raw but contact channels are stored via `normalizeEmail`, so case-varied invites (e.g. `Alice@Example.com`) wouldn't match a `alice@example.com` channel. `send-code` now normalizes on storage and `accept` normalizes on compare for back-compat with already-issued invites. New test covers the mixed-case path. - **Finding 2 (partial)**: added `expiresAt > now` to the atomic claim predicate for the boundary case where a code expires between the read and the claim. The reviewer's broader point about the `attemptCount` rate-limit check being non-atomic with its own increment **pre-dates this PR** (it reads the in-memory `verificationCode.attemptCount` from line 150, not a fresh read) and exists independently of the `usedAt` TOCTOU I'm fixing here. Tracking that as a separate follow-up so this PR stays scoped to the two originally-flagged issues. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Invite acceptance now requires the invitee’s verified, normalized (case‑insensitive) email; mismatches return HTTP 403 (TEAM_INVITATION_EMAIL_MISMATCH). * Client APIs now surface the new email-mismatch error alongside verification errors. * **Bug Fixes** * OTP verification codes are now guarded against parallel double‑redeem so only one request succeeds. * **Tests** * Added E2E tests for invitation email validation, non‑consuming rejection, case‑insensitive matching, and OTP concurrency. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |