From a11022524a9393e956949ea5a0177e0b7fd8c07b Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 30 Jul 2025 10:50:19 -0700 Subject: [PATCH 1/9] Update CLAUDE.md --- CLAUDE.md | 99 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 857e46192..18b87fbb1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,31 +1,76 @@ -# Development Guidelines for Stack Auth +# CLAUDE.md -## Build/Test/Lint Commands -- Build: `pnpm build` (all), `pnpm build:packages` (packages only), `pnpm build:backend` (backend) -- Lint: `pnpm lint` (zero warnings allowed) -- Typecheck: `pnpm typecheck` -- Test: `pnpm test` (all), `pnpm test:unit` (unit tests), `pnpm test:e2e` (e2e tests) -- Run single test: `pnpm test path/to/test.test.ts` or `pnpm test -t "test name pattern"` -- Start dependencies: `pnpm start-deps` (DB, services), `pnpm stop-deps` (shutdown) -- Dev mode: `pnpm dev` (all services) or `pnpm dev:basic` (backend+dashboard) -- Prisma CLI: `pnpm prisma` (use instead of the `prisma` command) +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Coding Guidelines -- TypeScript with strict types, prefer `type` over `interface` -- Avoid casting to `any`; Prefer making changes to the API so that `any` casts are unnecessary to access a property or method -- 2-space indentation, spaces in braces, semicolons required -- Return promises with `return await`, no floating promises -- Proper error handling for async code with try/catch -- Use helper functions: `yupXyz()` for validation, `getPublicEnvVar()` for env -- Switch cases must use blocks -- React Server Components preferred where applicable -- No direct 'use' imports from React (use React.use instead) -- Follow existing file structure and naming patterns +## Development Commands -## Testing Guidelines -- Import test utilities from `/apps/e2e/test/helpers.ts` -- Prefer inline snapshot testing with `expect(response).toMatchInlineSnapshot(...)` +### Essential Commands +- **Install dependencies**: `pnpm install` +- **Build packages**: `pnpm build:packages` +- **Generate code**: `pnpm codegen` +- **Start dependencies**: `pnpm restart-deps` (resets & restarts Docker containers for DB, Inbucket, etc. Usually already called by the user) +- **Run development**: `pnpm dev` (starts all services on different ports. Usually already started by the user in the background) +- **Run minimal dev**: `pnpm dev:basic` (only backend and dashboard for resource-limited systems) +- **Run tests**: `pnpm test --no-watch` (uses Vitest). You can filter with `pnpm test --no-watch ` +- **Lint code**: `pnpm lint` +- **Type check**: `pnpm typecheck` -## Monorepo Structure -Managed with Turbo and pnpm workspaces. Core packages in `packages/`, apps in `apps/`. -`packages/stack` is generated and will not be committed into the repository; change the files in `packages/template` instead. +### Testing +- **Run all tests**: `pnpm test --no-watch` +- **Run some tests**: `pnpm test --no-watch ` + +### Database Commands +- **Generate migration**: `pnpm db:migration-gen` +- **Reset database** (rarely used): `pnpm db:reset` +- **Seed database** (rarely used): `pnpm db:seed` +- **Initialize database** (rarely used): `pnpm db:init` +- **Run migrations** (rarely used): `pnpm db:migrate` + +## Architecture Overview + +Stack Auth is a monorepo using Turbo for build orchestration. The main components are: + +### Apps (`/apps`) +- **backend** (`/apps/backend`): Next.js API backend running on port 8102 + - Main API routes in `/apps/backend/src/app/api/latest` + - Database models using Prisma +- **dashboard** (`/apps/dashboard`): Admin dashboard on port 8101 +- **dev-launchpad**: Development portal on port 8100 +- **e2e**: End-to-end tests + +### Packages (`/packages`) +- **stack** (`/packages/stack`): Main Next.js SDK +- **stack-shared** (`/packages/stack-shared`): Shared utilities and types +- **stack-ui** (`/packages/stack-ui`): UI components +- **react** (`/packages/react`): React SDK +- **js** (`/packages/js`): JavaScript SDK + +### Key Technologies +- **Framework**: Next.js (with App Router) +- **Database**: PostgreSQL with Prisma ORM +- **Testing**: Vitest +- **Package Manager**: pnpm with workspaces +- **Build Tool**: Turbo +- **TypeScript**: Used throughout +- **Styling**: Tailwind CSS + +### API Structure +The API follows a RESTful design with routes organized by resource type: +- Auth endpoints: `/api/latest/auth/*` +- User management: `/api/latest/users/*` +- Team management: `/api/latest/teams/*` +- OAuth providers: `/api/latest/oauth-providers/*` + +### Development Ports +- 8100: Dev launchpad +- 8101: Dashboard +- 8102: Backend API +- 8103: Demo app +- 8104: Documentation +- 8105: Inbucket (email testing) +- 8106: Prisma Studio + +## Important Notes +- Environment variables are pre-configured in `.env.development` files +- Code generation (`pnpm codegen`) must be run after schema changes +- The project uses a custom route handler system in the backend for consistent API responses From 480dcba12e7b5bcd4477877288951334a6d16f19 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 30 Jul 2025 10:50:54 -0700 Subject: [PATCH 2/9] chore: update package versions --- apps/backend/CHANGELOG.md | 8 ++++++++ apps/backend/package.json | 2 +- apps/dashboard/CHANGELOG.md | 10 ++++++++++ apps/dashboard/package.json | 2 +- apps/dev-launchpad/CHANGELOG.md | 6 ++++++ apps/dev-launchpad/package.json | 2 +- apps/e2e/CHANGELOG.md | 9 +++++++++ apps/e2e/package.json | 2 +- apps/mock-oauth-server/CHANGELOG.md | 2 ++ apps/mock-oauth-server/package.json | 2 +- docs/CHANGELOG.md | 9 +++++++++ docs/package.json | 2 +- examples/cjs-test/CHANGELOG.md | 6 ++++++ examples/cjs-test/package.json | 2 +- examples/demo/CHANGELOG.md | 9 +++++++++ examples/demo/package.json | 2 +- examples/docs-examples/CHANGELOG.md | 9 +++++++++ examples/docs-examples/package.json | 2 +- examples/e-commerce/CHANGELOG.md | 6 ++++++ examples/e-commerce/package.json | 2 +- examples/js-example/CHANGELOG.md | 6 ++++++ examples/js-example/package.json | 2 +- examples/middleware/CHANGELOG.md | 6 ++++++ examples/middleware/package.json | 2 +- examples/partial-prerendering/CHANGELOG.md | 6 ++++++ examples/partial-prerendering/package.json | 2 +- examples/react-example/CHANGELOG.md | 6 ++++++ examples/react-example/package.json | 2 +- examples/supabase/CHANGELOG.md | 6 ++++++ examples/supabase/package.json | 2 +- packages/init-stack/CHANGELOG.md | 7 +++++++ packages/init-stack/package.json | 2 +- packages/js/package.json | 2 +- packages/react/package.json | 2 +- packages/stack-sc/CHANGELOG.md | 2 ++ packages/stack-sc/package.json | 2 +- packages/stack-shared/CHANGELOG.md | 6 ++++++ packages/stack-shared/package.json | 2 +- packages/stack-ui/CHANGELOG.md | 7 +++++++ packages/stack-ui/package.json | 2 +- packages/stack/package.json | 2 +- packages/template/CHANGELOG.md | 10 ++++++++++ packages/template/package-template.json | 2 +- packages/template/package.json | 2 +- 44 files changed, 160 insertions(+), 24 deletions(-) diff --git a/apps/backend/CHANGELOG.md b/apps/backend/CHANGELOG.md index a68e11eda..f4642455c 100644 --- a/apps/backend/CHANGELOG.md +++ b/apps/backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @stackframe/stack-backend +## 2.8.27 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/apps/backend/package.json b/apps/backend/package.json index 43411be0c..7ee342cc5 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-backend", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "clean": "rimraf src/generated && rimraf .next && rimraf node_modules", diff --git a/apps/dashboard/CHANGELOG.md b/apps/dashboard/CHANGELOG.md index e5f16597a..78049ff2d 100644 --- a/apps/dashboard/CHANGELOG.md +++ b/apps/dashboard/CHANGELOG.md @@ -1,5 +1,15 @@ # @stackframe/stack-dashboard +## 2.8.27 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + - @stackframe/stack@2.8.27 + - @stackframe/stack-ui@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 8500c40cb..41c364790 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-dashboard", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "clean": "rimraf .next && rimraf node_modules", diff --git a/apps/dev-launchpad/CHANGELOG.md b/apps/dev-launchpad/CHANGELOG.md index 74ff3a578..6d9d023a1 100644 --- a/apps/dev-launchpad/CHANGELOG.md +++ b/apps/dev-launchpad/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/dev-launchpad +## 2.8.27 + +### Patch Changes + +- Various changes + ## 2.8.26 ## 2.8.25 diff --git a/apps/dev-launchpad/package.json b/apps/dev-launchpad/package.json index 04d96931d..737efaa9e 100644 --- a/apps/dev-launchpad/package.json +++ b/apps/dev-launchpad/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/dev-launchpad", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "dev": "serve -p 8100 -s public", diff --git a/apps/e2e/CHANGELOG.md b/apps/e2e/CHANGELOG.md index 01d4e176e..d53322c21 100644 --- a/apps/e2e/CHANGELOG.md +++ b/apps/e2e/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/e2e-tests +## 2.8.27 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + - @stackframe/js@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/apps/e2e/package.json b/apps/e2e/package.json index 1a4590fb6..9d3d70a42 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e2e-tests", - "version": "2.8.26", + "version": "2.8.27", "private": true, "type": "module", "scripts": { diff --git a/apps/mock-oauth-server/CHANGELOG.md b/apps/mock-oauth-server/CHANGELOG.md index 1d8aee9cb..7b9433a68 100644 --- a/apps/mock-oauth-server/CHANGELOG.md +++ b/apps/mock-oauth-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/mock-oauth-server +## 2.8.27 + ## 2.8.26 ## 2.8.25 diff --git a/apps/mock-oauth-server/package.json b/apps/mock-oauth-server/package.json index 0ee0ee39b..7a3ea4bc6 100644 --- a/apps/mock-oauth-server/package.json +++ b/apps/mock-oauth-server/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/mock-oauth-server", - "version": "2.8.26", + "version": "2.8.27", "private": true, "main": "index.js", "scripts": { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ff30e7dfc..a8b70a4fc 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/stack-docs +## 2.8.27 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + - @stackframe/stack@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/docs/package.json b/docs/package.json index 04b1b84c9..cde83f11e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-docs", - "version": "2.8.26", + "version": "2.8.27", "description": "", "main": "index.js", "private": true, diff --git a/examples/cjs-test/CHANGELOG.md b/examples/cjs-test/CHANGELOG.md index f5f9d6f23..48e67f941 100644 --- a/examples/cjs-test/CHANGELOG.md +++ b/examples/cjs-test/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/example-cjs-test +## 2.8.27 + +### Patch Changes + +- @stackframe/stack@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/cjs-test/package.json b/examples/cjs-test/package.json index 7a8124c9a..2d65910be 100644 --- a/examples/cjs-test/package.json +++ b/examples/cjs-test/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-cjs-test", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "dev": "next dev --port 8110", diff --git a/examples/demo/CHANGELOG.md b/examples/demo/CHANGELOG.md index 8c2b35396..0b4d50d2c 100644 --- a/examples/demo/CHANGELOG.md +++ b/examples/demo/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/example-demo-app +## 2.8.27 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + - @stackframe/stack@2.8.27 + - @stackframe/stack-ui@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/demo/package.json b/examples/demo/package.json index 41fb299db..8f028d259 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-demo-app", - "version": "2.8.26", + "version": "2.8.27", "description": "", "private": true, "scripts": { diff --git a/examples/docs-examples/CHANGELOG.md b/examples/docs-examples/CHANGELOG.md index 3e40ccaac..237623835 100644 --- a/examples/docs-examples/CHANGELOG.md +++ b/examples/docs-examples/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/docs-examples +## 2.8.27 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + - @stackframe/stack@2.8.27 + - @stackframe/stack-ui@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/docs-examples/package.json b/examples/docs-examples/package.json index fb5509dc9..57b1e302d 100644 --- a/examples/docs-examples/package.json +++ b/examples/docs-examples/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/docs-examples", - "version": "2.8.26", + "version": "2.8.27", "description": "", "private": true, "scripts": { diff --git a/examples/e-commerce/CHANGELOG.md b/examples/e-commerce/CHANGELOG.md index fa4844d19..cc755e194 100644 --- a/examples/e-commerce/CHANGELOG.md +++ b/examples/e-commerce/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/e-commerce-demo +## 2.8.27 + +### Patch Changes + +- @stackframe/stack@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/e-commerce/package.json b/examples/e-commerce/package.json index 3da0a53da..b62617ee0 100644 --- a/examples/e-commerce/package.json +++ b/examples/e-commerce/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e-commerce-demo", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "dev": "next dev --port 8111", diff --git a/examples/js-example/CHANGELOG.md b/examples/js-example/CHANGELOG.md index 7c29a4514..d55eff140 100644 --- a/examples/js-example/CHANGELOG.md +++ b/examples/js-example/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/js-example +## 2.8.27 + +### Patch Changes + +- @stackframe/js@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/js-example/package.json b/examples/js-example/package.json index dcfa1ea4e..34f234259 100644 --- a/examples/js-example/package.json +++ b/examples/js-example/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/js-example", - "version": "2.8.26", + "version": "2.8.27", "private": true, "description": "", "main": "index.js", diff --git a/examples/middleware/CHANGELOG.md b/examples/middleware/CHANGELOG.md index fca4b811b..5e43611e7 100644 --- a/examples/middleware/CHANGELOG.md +++ b/examples/middleware/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/example-middleware-demo +## 2.8.27 + +### Patch Changes + +- @stackframe/stack@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/middleware/package.json b/examples/middleware/package.json index d782018a9..df36fdd6b 100644 --- a/examples/middleware/package.json +++ b/examples/middleware/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-middleware-demo", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "dev": "next dev --port 8112", diff --git a/examples/partial-prerendering/CHANGELOG.md b/examples/partial-prerendering/CHANGELOG.md index 31765317d..a3b900f79 100644 --- a/examples/partial-prerendering/CHANGELOG.md +++ b/examples/partial-prerendering/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/example-partial-prerendering +## 2.8.27 + +### Patch Changes + +- @stackframe/stack@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/partial-prerendering/package.json b/examples/partial-prerendering/package.json index e0cee99c2..50294555c 100644 --- a/examples/partial-prerendering/package.json +++ b/examples/partial-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-partial-prerendering", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "dev": "next dev --port 8109", diff --git a/examples/react-example/CHANGELOG.md b/examples/react-example/CHANGELOG.md index 5e87ae6ff..084716478 100644 --- a/examples/react-example/CHANGELOG.md +++ b/examples/react-example/CHANGELOG.md @@ -1,5 +1,11 @@ # react-example +## 2.8.27 + +### Patch Changes + +- @stackframe/react@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/react-example/package.json b/examples/react-example/package.json index 10da6a35a..04ed99749 100644 --- a/examples/react-example/package.json +++ b/examples/react-example/package.json @@ -1,7 +1,7 @@ { "name": "react-example", "private": true, - "version": "2.8.26", + "version": "2.8.27", "type": "module", "scripts": { "dev": "vite --force --port 8120", diff --git a/examples/supabase/CHANGELOG.md b/examples/supabase/CHANGELOG.md index e51c8c420..4716712e2 100644 --- a/examples/supabase/CHANGELOG.md +++ b/examples/supabase/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/example-supabase +## 2.8.27 + +### Patch Changes + +- @stackframe/stack@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/examples/supabase/package.json b/examples/supabase/package.json index a764aae8f..3226cf8c2 100644 --- a/examples/supabase/package.json +++ b/examples/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-supabase", - "version": "2.8.26", + "version": "2.8.27", "private": true, "scripts": { "dev": "next dev --turbo --port 8115", diff --git a/packages/init-stack/CHANGELOG.md b/packages/init-stack/CHANGELOG.md index 8fdc628f9..3ed072801 100644 --- a/packages/init-stack/CHANGELOG.md +++ b/packages/init-stack/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/init-stack +## 2.8.27 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json index 905d6800b..1ff5efa89 100644 --- a/packages/init-stack/package.json +++ b/packages/init-stack/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/init-stack", - "version": "2.8.26", + "version": "2.8.27", "description": "The setup wizard for Stack. https://stack-auth.com", "main": "dist/index.js", "type": "module", diff --git a/packages/js/package.json b/packages/js/package.json index 85fa58028..e7bd0faa9 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,7 +1,7 @@ { "//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY", "name": "@stackframe/js", - "version": "2.8.26", + "version": "2.8.27", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/react/package.json b/packages/react/package.json index d1854f69b..335e419fd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY", "name": "@stackframe/react", - "version": "2.8.26", + "version": "2.8.27", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/stack-sc/CHANGELOG.md b/packages/stack-sc/CHANGELOG.md index 968a6f1e1..b7062f27e 100644 --- a/packages/stack-sc/CHANGELOG.md +++ b/packages/stack-sc/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/stack-sc +## 2.8.27 + ## 2.8.26 ## 2.8.25 diff --git a/packages/stack-sc/package.json b/packages/stack-sc/package.json index 275194bd8..83644be6c 100644 --- a/packages/stack-sc/package.json +++ b/packages/stack-sc/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-sc", - "version": "2.8.26", + "version": "2.8.27", "exports": { "./force-react-server": { "types": "./dist/index.react-server.d.ts", diff --git a/packages/stack-shared/CHANGELOG.md b/packages/stack-shared/CHANGELOG.md index 0bf35e06d..3d6af0df4 100644 --- a/packages/stack-shared/CHANGELOG.md +++ b/packages/stack-shared/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/stack-shared +## 2.8.27 + +### Patch Changes + +- Various changes + ## 2.8.26 ### Patch Changes diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index 443ee2f8f..16e9b3ca3 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-shared", - "version": "2.8.26", + "version": "2.8.27", "scripts": { "build": "rimraf dist && tsup-node", "typecheck": "tsc --noEmit", diff --git a/packages/stack-ui/CHANGELOG.md b/packages/stack-ui/CHANGELOG.md index 7575f499b..3ff98aa1f 100644 --- a/packages/stack-ui/CHANGELOG.md +++ b/packages/stack-ui/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/stack-ui +## 2.8.27 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/packages/stack-ui/package.json b/packages/stack-ui/package.json index e9536c564..97c691b47 100644 --- a/packages/stack-ui/package.json +++ b/packages/stack-ui/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-ui", - "version": "2.8.26", + "version": "2.8.27", "main": "./dist/index.js", "types": "./dist/index.d.ts", "sideEffects": false, diff --git a/packages/stack/package.json b/packages/stack/package.json index a8a6d3fd3..b05e35d85 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,7 +1,7 @@ { "//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY", "name": "@stackframe/stack", - "version": "2.8.26", + "version": "2.8.27", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/template/CHANGELOG.md b/packages/template/CHANGELOG.md index 1589bd731..082b7b294 100644 --- a/packages/template/CHANGELOG.md +++ b/packages/template/CHANGELOG.md @@ -1,5 +1,15 @@ # @stackframe/stack +## 2.8.27 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.8.27 + - @stackframe/stack-ui@2.8.27 + - @stackframe/stack-sc@2.8.27 + ## 2.8.26 ### Patch Changes diff --git a/packages/template/package-template.json b/packages/template/package-template.json index 208b878cd..af99e194a 100644 --- a/packages/template/package-template.json +++ b/packages/template/package-template.json @@ -11,7 +11,7 @@ "//": "NEXT_LINE_PLATFORM template", "private": true, - "version": "2.8.26", + "version": "2.8.27", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/template/package.json b/packages/template/package.json index 05ee04450..2bdece6d3 100644 --- a/packages/template/package.json +++ b/packages/template/package.json @@ -2,7 +2,7 @@ "//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY", "name": "@stackframe/template", "private": true, - "version": "2.8.26", + "version": "2.8.27", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", From d601e99c6c4c8ed29c8ea04ed3723628b9591620 Mon Sep 17 00:00:00 2001 From: Madison Date: Wed, 30 Jul 2025 13:18:17 -0500 Subject: [PATCH 3/9] init deeplink redirects (#804) Fix for a lot of the deep links that are causing 404 errors from old docs. ---- > [!IMPORTANT] > Adds dynamic redirection for documentation routes in multiple sections, redirecting to the correct page or an overview, with special handling for REST API routes. > > - **Behavior**: > - Adds dynamic redirection for documentation routes in `js`, `next`, `python`, `react`, and `rest-api` sections. > - Redirects to the correct documentation page or an overview if the page doesn't exist. > - For `rest-api`, redirects to `/api` instead of `/docs`. > - **Files**: > - `route.ts` in `js`, `next`, `python`, `react`, and `rest-api` sections handle the redirection logic. > - Uses `source.getPage()` or `apiSource.getPage()` to check page existence. > > This description was created by [Ellipsis](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral) for 50c4b592a400f5b155c4b392f4131065bdc9a3db. You can [customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this summary. It will automatically update as commits are pushed. ---- ## Summary by CodeRabbit * **New Features** * Added dynamic redirection for documentation routes in JavaScript, Next.js, Python, React, and REST API sections. Users are now automatically redirected to the correct documentation page if it exists, or receive a 404 not found response if the page is missing. This improves navigation and error handling for documentation URLs. --------- Co-authored-by: Konsti Wohlwend --- docs/src/app/js/[...path]/route.ts | 31 ++++++++++++++++++++++++ docs/src/app/next/[...path]/route.ts | 31 ++++++++++++++++++++++++ docs/src/app/python/[...path]/route.ts | 31 ++++++++++++++++++++++++ docs/src/app/react/[...path]/route.ts | 31 ++++++++++++++++++++++++ docs/src/app/rest-api/[...path]/route.ts | 31 ++++++++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 docs/src/app/js/[...path]/route.ts create mode 100644 docs/src/app/next/[...path]/route.ts create mode 100644 docs/src/app/python/[...path]/route.ts create mode 100644 docs/src/app/react/[...path]/route.ts create mode 100644 docs/src/app/rest-api/[...path]/route.ts diff --git a/docs/src/app/js/[...path]/route.ts b/docs/src/app/js/[...path]/route.ts new file mode 100644 index 000000000..caeb4078e --- /dev/null +++ b/docs/src/app/js/[...path]/route.ts @@ -0,0 +1,31 @@ +import { source } from 'lib/source'; +import { notFound, redirect } from 'next/navigation'; +import { NextRequest } from 'next/server'; + +export function GET(request: NextRequest) { + const pathname = new URL(request.url).pathname; + + // Ensure we have the correct target path without double prefixes using proper URL construction + let targetPath: string; + if (pathname.startsWith('/docs')) { + targetPath = pathname; + } else { + // Remove leading slash and use as relative path to properly construct /docs prefix + targetPath = new URL(pathname.substring(1), 'file:///docs/').pathname; + } + + // Extract slug by removing any '/docs' prefix and splitting by '/' + const cleanPath = pathname.startsWith('/docs') ? pathname.substring(5) : pathname; + const slug = cleanPath.substring(1).split('/').filter(Boolean); + + // Check if the target page exists + const page = source.getPage(slug); + + if (page) { + // Page exists, redirect to the full path + return redirect(targetPath); + } else { + // Page doesn't exist, return 404 + return notFound(); + } +} diff --git a/docs/src/app/next/[...path]/route.ts b/docs/src/app/next/[...path]/route.ts new file mode 100644 index 000000000..0e328050f --- /dev/null +++ b/docs/src/app/next/[...path]/route.ts @@ -0,0 +1,31 @@ +import { source } from 'lib/source'; +import { notFound, redirect } from 'next/navigation'; +import { NextRequest } from 'next/server'; + +export function GET(request: NextRequest) { + const pathname = new URL(request.url).pathname; + + // Ensure we have the correct target path without double prefixes using proper URL construction + let targetPath: string; + if (pathname.startsWith('/docs')) { + targetPath = pathname; + } else { + // Remove leading slash and use as relative path to properly construct /docs prefix + targetPath = new URL(pathname.substring(1), 'file:///docs/').pathname; + } + + // Extract slug by removing any '/docs' prefix and splitting by '/' + const cleanPath = pathname.startsWith('/docs') ? pathname.substring(5) : pathname; + const slug = cleanPath.substring(1).split('/').filter(Boolean); + + // Check if the target page exists + const page = source.getPage(slug); + + if (page) { + // Page exists, redirect to the full path + return redirect(targetPath); + } else { + // Page doesn't exist, redirect to overview + return notFound(); + } +} diff --git a/docs/src/app/python/[...path]/route.ts b/docs/src/app/python/[...path]/route.ts new file mode 100644 index 000000000..0e328050f --- /dev/null +++ b/docs/src/app/python/[...path]/route.ts @@ -0,0 +1,31 @@ +import { source } from 'lib/source'; +import { notFound, redirect } from 'next/navigation'; +import { NextRequest } from 'next/server'; + +export function GET(request: NextRequest) { + const pathname = new URL(request.url).pathname; + + // Ensure we have the correct target path without double prefixes using proper URL construction + let targetPath: string; + if (pathname.startsWith('/docs')) { + targetPath = pathname; + } else { + // Remove leading slash and use as relative path to properly construct /docs prefix + targetPath = new URL(pathname.substring(1), 'file:///docs/').pathname; + } + + // Extract slug by removing any '/docs' prefix and splitting by '/' + const cleanPath = pathname.startsWith('/docs') ? pathname.substring(5) : pathname; + const slug = cleanPath.substring(1).split('/').filter(Boolean); + + // Check if the target page exists + const page = source.getPage(slug); + + if (page) { + // Page exists, redirect to the full path + return redirect(targetPath); + } else { + // Page doesn't exist, redirect to overview + return notFound(); + } +} diff --git a/docs/src/app/react/[...path]/route.ts b/docs/src/app/react/[...path]/route.ts new file mode 100644 index 000000000..0e328050f --- /dev/null +++ b/docs/src/app/react/[...path]/route.ts @@ -0,0 +1,31 @@ +import { source } from 'lib/source'; +import { notFound, redirect } from 'next/navigation'; +import { NextRequest } from 'next/server'; + +export function GET(request: NextRequest) { + const pathname = new URL(request.url).pathname; + + // Ensure we have the correct target path without double prefixes using proper URL construction + let targetPath: string; + if (pathname.startsWith('/docs')) { + targetPath = pathname; + } else { + // Remove leading slash and use as relative path to properly construct /docs prefix + targetPath = new URL(pathname.substring(1), 'file:///docs/').pathname; + } + + // Extract slug by removing any '/docs' prefix and splitting by '/' + const cleanPath = pathname.startsWith('/docs') ? pathname.substring(5) : pathname; + const slug = cleanPath.substring(1).split('/').filter(Boolean); + + // Check if the target page exists + const page = source.getPage(slug); + + if (page) { + // Page exists, redirect to the full path + return redirect(targetPath); + } else { + // Page doesn't exist, redirect to overview + return notFound(); + } +} diff --git a/docs/src/app/rest-api/[...path]/route.ts b/docs/src/app/rest-api/[...path]/route.ts new file mode 100644 index 000000000..9653e7bfd --- /dev/null +++ b/docs/src/app/rest-api/[...path]/route.ts @@ -0,0 +1,31 @@ +import { apiSource } from 'lib/source'; +import { notFound, redirect } from 'next/navigation'; +import { NextRequest } from 'next/server'; + +export function GET(request: NextRequest) { + const pathname = new URL(request.url).pathname; + + // For rest-api, we redirect to /api not /docs using proper URL construction + let targetPath: string; + if (pathname.startsWith('/api')) { + targetPath = pathname; + } else { + // Remove leading slash and use as relative path to properly construct /api prefix + targetPath = new URL(pathname.substring(1), 'file:///api/').pathname; + } + + // Extract slug by removing any '/api' prefix and splitting by '/' + const cleanPath = pathname.startsWith('/api') ? pathname.substring(4) : pathname; + const slug = cleanPath.substring(1).split('/').filter(Boolean); + + // Check if the target page exists using apiSource for API docs + const page = apiSource.getPage(slug); + + if (page) { + // Page exists, redirect to the full path + return redirect(targetPath); + } else { + // Page doesn't exist, redirect to overview + return notFound(); + } +} From 78ba327f6054bcec12b93e77baaa19c4a55eb6fc Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 30 Jul 2025 11:30:41 -0700 Subject: [PATCH 4/9] Fix typo --- docs/templates/sdk/objects/stack-app.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templates/sdk/objects/stack-app.mdx b/docs/templates/sdk/objects/stack-app.mdx index f02f158dc..874287658 100644 --- a/docs/templates/sdk/objects/stack-app.mdx +++ b/docs/templates/sdk/objects/stack-app.mdx @@ -507,7 +507,7 @@ Like `StackClientApp`, but with [server permissions](../../concepts/stack-app.md Since this functionality should only be available in environments you trust (ie. your own server), it requires a [`SECRET_SERVER_KEY`](../../rest-api/overview.mdx). In some cases, you may want to use a [`StackServerApp`](#stackserverapp) on the client; an example for this is an internal dashboard that only your own employees have access to. -We generally recommend against doing this unless you are aware of and protected against the (potentially severe) secutiry implications of +We generally recommend against doing this unless you are aware of and protected against the (potentially severe) security implications of exposing [`SECRET_SERVER_KEY`](../../rest-api/overview.mdx) on the client. From 6c55e3da66b11dfba18fea0cb01c1dff14752d7f Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 30 Jul 2025 12:00:09 -0700 Subject: [PATCH 5/9] Update Featurebase redirect --- .../email-templates/[templateId]/route.tsx | 2 - .../latest/internal/email-templates/route.tsx | 2 - .../internal/email-themes/[id]/route.tsx | 1 - .../latest/internal/email-themes/route.tsx | 2 - apps/backend/src/lib/config.tsx | 12 +- apps/backend/src/lib/permissions.tsx | 3 - apps/backend/src/lib/projects.tsx | 196 ++++++----- .../integrations/featurebase/sso/page.tsx | 5 + .../(main)/integrations/{ => neon}/layout.tsx | 0 .../api/v1/internal/projects.test.ts | 98 ++++++ pnpm-lock.yaml | 321 ------------------ 11 files changed, 204 insertions(+), 438 deletions(-) rename apps/dashboard/src/app/(main)/integrations/{ => neon}/layout.tsx (100%) diff --git a/apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx b/apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx index d6611f082..63e64351d 100644 --- a/apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx +++ b/apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx @@ -1,6 +1,5 @@ import { overrideEnvironmentConfigOverride } from "@/lib/config"; import { getActiveEmailTheme, renderEmailWithTemplate } from "@/lib/email-rendering"; -import { globalPrismaClient } from "@/prisma-client"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { KnownErrors } from "@stackframe/stack-shared/dist/known-errors"; import { adaptSchema, templateThemeIdSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; @@ -55,7 +54,6 @@ export const PATCH = createSmartRouteHandler({ } await overrideEnvironmentConfigOverride({ - tx: globalPrismaClient, projectId: tenancy.project.id, branchId: tenancy.branchId, environmentConfigOverrideOverride: { diff --git a/apps/backend/src/app/api/latest/internal/email-templates/route.tsx b/apps/backend/src/app/api/latest/internal/email-templates/route.tsx index 0755672e2..d56dda9de 100644 --- a/apps/backend/src/app/api/latest/internal/email-templates/route.tsx +++ b/apps/backend/src/app/api/latest/internal/email-templates/route.tsx @@ -1,5 +1,4 @@ import { overrideEnvironmentConfigOverride } from "@/lib/config"; -import { globalPrismaClient } from "@/prisma-client"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { adaptSchema, templateThemeIdSchema, yupArray, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; import { filterUndefined, typedEntries } from "@stackframe/stack-shared/dist/utils/objects"; @@ -94,7 +93,6 @@ export const POST = createSmartRouteHandler({ `; await overrideEnvironmentConfigOverride({ - tx: globalPrismaClient, projectId: tenancy.project.id, branchId: tenancy.branchId, environmentConfigOverrideOverride: { diff --git a/apps/backend/src/app/api/latest/internal/email-themes/[id]/route.tsx b/apps/backend/src/app/api/latest/internal/email-themes/[id]/route.tsx index 072b06955..676a619a4 100644 --- a/apps/backend/src/app/api/latest/internal/email-themes/[id]/route.tsx +++ b/apps/backend/src/app/api/latest/internal/email-themes/[id]/route.tsx @@ -83,7 +83,6 @@ export const PATCH = createSmartRouteHandler({ throw new KnownErrors.EmailRenderingError(result.error); } await overrideEnvironmentConfigOverride({ - tx: globalPrismaClient, projectId: tenancy.project.id, branchId: tenancy.branchId, environmentConfigOverrideOverride: { diff --git a/apps/backend/src/app/api/latest/internal/email-themes/route.tsx b/apps/backend/src/app/api/latest/internal/email-themes/route.tsx index d98cea94d..4473629b4 100644 --- a/apps/backend/src/app/api/latest/internal/email-themes/route.tsx +++ b/apps/backend/src/app/api/latest/internal/email-themes/route.tsx @@ -1,5 +1,4 @@ import { overrideEnvironmentConfigOverride } from "@/lib/config"; -import { globalPrismaClient } from "@/prisma-client"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { LightEmailTheme } from "@stackframe/stack-shared/dist/helpers/emails"; import { adaptSchema, yupArray, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; @@ -30,7 +29,6 @@ export const POST = createSmartRouteHandler({ async handler({ body, auth: { tenancy } }) { const id = generateUuid(); await overrideEnvironmentConfigOverride({ - tx: globalPrismaClient, projectId: tenancy.project.id, branchId: tenancy.branchId, environmentConfigOverrideOverride: { diff --git a/apps/backend/src/lib/config.tsx b/apps/backend/src/lib/config.tsx index 1ca71c3cc..3817cd98c 100644 --- a/apps/backend/src/lib/config.tsx +++ b/apps/backend/src/lib/config.tsx @@ -199,18 +199,17 @@ export function getOrganizationConfigOverrideQuery(options: OrganizationOptions) export async function overrideProjectConfigOverride(options: { projectId: string, projectConfigOverrideOverride: ProjectConfigOverrideOverride, - tx: PrismaClientTransaction, }): Promise { // set project config override on our own DB // TODO put this in a serializable transaction (or a single SQL query) to prevent race conditions - const oldConfig = await rawQuery(options.tx, getProjectConfigOverrideQuery(options)); + const oldConfig = await rawQuery(globalPrismaClient, getProjectConfigOverrideQuery(options)); const newConfig = override( oldConfig, options.projectConfigOverrideOverride, ); await assertNoConfigOverrideErrors(projectConfigSchema, newConfig); - await options.tx.project.update({ + await globalPrismaClient.project.update({ where: { id: options.projectId, }, @@ -224,7 +223,6 @@ export function overrideBranchConfigOverride(options: { projectId: string, branchId: string, branchConfigOverrideOverride: BranchConfigOverrideOverride, - tx: PrismaClientTransaction, }): Promise { // update config.json if on local emulator // throw error otherwise @@ -235,18 +233,17 @@ export async function overrideEnvironmentConfigOverride(options: { projectId: string, branchId: string, environmentConfigOverrideOverride: EnvironmentConfigOverrideOverride, - tx: PrismaClientTransaction, }): Promise { // save environment config override on DB // TODO put this in a serializable transaction (or a single SQL query) to prevent race conditions - const oldConfig = await rawQuery(options.tx, getEnvironmentConfigOverrideQuery(options)); + const oldConfig = await rawQuery(globalPrismaClient, getEnvironmentConfigOverrideQuery(options)); const newConfig = override( oldConfig, options.environmentConfigOverrideOverride, ); await assertNoConfigOverrideErrors(environmentConfigSchema, newConfig); - await options.tx.environmentConfigOverride.upsert({ + await globalPrismaClient.environmentConfigOverride.upsert({ where: { projectId_branchId: { projectId: options.projectId, @@ -269,7 +266,6 @@ export function overrideOrganizationConfigOverride(options: { branchId: string, organizationId: string | null, organizationConfigOverrideOverride: OrganizationConfigOverrideOverride, - tx: PrismaClientTransaction, }): Promise { // save organization config override on DB (either our own, or the source of truth one) throw new StackAssertionError('Not implemented'); diff --git a/apps/backend/src/lib/permissions.tsx b/apps/backend/src/lib/permissions.tsx index c2aef7627..61329f387 100644 --- a/apps/backend/src/lib/permissions.tsx +++ b/apps/backend/src/lib/permissions.tsx @@ -213,7 +213,6 @@ export async function createPermissionDefinition( await overrideEnvironmentConfigOverride({ branchId: options.tenancy.branchId, projectId: options.tenancy.project.id, - tx: globalTx, environmentConfigOverrideOverride: { "rbac.permissions": { ...oldConfig.rbac.permissions, @@ -272,7 +271,6 @@ export async function updatePermissionDefinition( await overrideEnvironmentConfigOverride({ branchId: options.tenancy.branchId, projectId: options.tenancy.project.id, - tx: globalTx, environmentConfigOverrideOverride: { "rbac.permissions": { ...typedFromEntries( @@ -347,7 +345,6 @@ export async function deletePermissionDefinition( await overrideEnvironmentConfigOverride({ branchId: options.tenancy.branchId, projectId: options.tenancy.project.id, - tx: globalTx, environmentConfigOverrideOverride: { "rbac.permissions": typedFromEntries( typedEntries(oldConfig.rbac.permissions) diff --git a/apps/backend/src/lib/projects.tsx b/apps/backend/src/lib/projects.tsx index 36bdd3dfb..84a9d5f91 100644 --- a/apps/backend/src/lib/projects.tsx +++ b/apps/backend/src/lib/projects.tsx @@ -122,114 +122,112 @@ export async function createOrUpdateProject( branchId = options.branchId; } - const translateDefaultPermissions = (permissions: { id: string }[] | undefined) => { - return permissions ? typedFromEntries(permissions.map((permission) => [permission.id, true])) : undefined; - }; + return [project.id, branchId]; + }); - await overrideProjectConfigOverride({ - tx, - projectId: project.id, - projectConfigOverrideOverride: { - sourceOfTruth: options.sourceOfTruth || (JSON.parse(getEnvVariable("STACK_OVERRIDE_SOURCE_OF_TRUTH", "null")) ?? undefined), - }, - }); + // Update project config override + await overrideProjectConfigOverride({ + projectId: projectId, + projectConfigOverrideOverride: { + sourceOfTruth: options.sourceOfTruth || (JSON.parse(getEnvVariable("STACK_OVERRIDE_SOURCE_OF_TRUTH", "null")) ?? undefined), + }, + }); - const dataOptions = options.data.config || {}; - const configOverrideOverride: EnvironmentConfigOverrideOverride = filterUndefined({ - // ======================= auth ======================= - 'auth.allowSignUp': dataOptions.sign_up_enabled, - 'auth.password.allowSignIn': dataOptions.credential_enabled, - 'auth.otp.allowSignIn': dataOptions.magic_link_enabled, - 'auth.passkey.allowSignIn': dataOptions.passkey_enabled, - 'auth.oauth.accountMergeStrategy': dataOptions.oauth_account_merge_strategy, - 'auth.oauth.providers': dataOptions.oauth_providers ? typedFromEntries(dataOptions.oauth_providers - .map((provider) => { - return [ - provider.id, - { - type: provider.id, - isShared: provider.type === "shared", - clientId: provider.client_id, - clientSecret: provider.client_secret, - facebookConfigId: provider.facebook_config_id, - microsoftTenantId: provider.microsoft_tenant_id, - allowSignIn: true, - allowConnectedAccounts: true, - } satisfies OrganizationRenderedConfig['auth']['oauth']['providers'][string] - ]; - })) : undefined, - // ======================= users ======================= - 'users.allowClientUserDeletion': dataOptions.client_user_deletion_enabled, - // ======================= teams ======================= - 'teams.allowClientTeamCreation': dataOptions.client_team_creation_enabled, - 'teams.createPersonalTeamOnSignUp': dataOptions.create_team_on_sign_up, - // ======================= domains ======================= - 'domains.allowLocalhost': dataOptions.allow_localhost ?? true, - 'domains.trustedDomains': dataOptions.domains ? typedFromEntries(dataOptions.domains.map((domain) => { + // Update environment config override + const translateDefaultPermissions = (permissions: { id: string }[] | undefined) => { + return permissions ? typedFromEntries(permissions.map((permission) => [permission.id, true])) : undefined; + }; + const dataOptions = options.data.config || {}; + const configOverrideOverride: EnvironmentConfigOverrideOverride = filterUndefined({ + // ======================= auth ======================= + 'auth.allowSignUp': dataOptions.sign_up_enabled, + 'auth.password.allowSignIn': dataOptions.credential_enabled, + 'auth.otp.allowSignIn': dataOptions.magic_link_enabled, + 'auth.passkey.allowSignIn': dataOptions.passkey_enabled, + 'auth.oauth.accountMergeStrategy': dataOptions.oauth_account_merge_strategy, + 'auth.oauth.providers': dataOptions.oauth_providers ? typedFromEntries(dataOptions.oauth_providers + .map((provider) => { return [ - generateUuid(), + provider.id, { - baseUrl: domain.domain, - handlerPath: domain.handler_path, - } satisfies OrganizationRenderedConfig['domains']['trustedDomains'][string], + type: provider.id, + isShared: provider.type === "shared", + clientId: provider.client_id, + clientSecret: provider.client_secret, + facebookConfigId: provider.facebook_config_id, + microsoftTenantId: provider.microsoft_tenant_id, + allowSignIn: true, + allowConnectedAccounts: true, + } satisfies OrganizationRenderedConfig['auth']['oauth']['providers'][string] ]; })) : undefined, - // ======================= api keys ======================= - 'apiKeys.enabled.user': dataOptions.allow_user_api_keys, - 'apiKeys.enabled.team': dataOptions.allow_team_api_keys, - // ======================= emails ======================= - 'emails.server': dataOptions.email_config ? { - isShared: dataOptions.email_config.type === 'shared', - host: dataOptions.email_config.host, - port: dataOptions.email_config.port, - username: dataOptions.email_config.username, - password: dataOptions.email_config.password, - senderName: dataOptions.email_config.sender_name, - senderEmail: dataOptions.email_config.sender_email, - } satisfies OrganizationRenderedConfig['emails']['server'] : undefined, - 'emails.selectedThemeId': dataOptions.email_theme, - // ======================= rbac ======================= - 'rbac.defaultPermissions.teamMember': translateDefaultPermissions(dataOptions.team_member_default_permissions), - 'rbac.defaultPermissions.teamCreator': translateDefaultPermissions(dataOptions.team_creator_default_permissions), - 'rbac.defaultPermissions.signUp': translateDefaultPermissions(dataOptions.user_default_permissions), - }); + // ======================= users ======================= + 'users.allowClientUserDeletion': dataOptions.client_user_deletion_enabled, + // ======================= teams ======================= + 'teams.allowClientTeamCreation': dataOptions.client_team_creation_enabled, + 'teams.createPersonalTeamOnSignUp': dataOptions.create_team_on_sign_up, + // ======================= domains ======================= + 'domains.allowLocalhost': dataOptions.allow_localhost, + 'domains.trustedDomains': dataOptions.domains ? typedFromEntries(dataOptions.domains.map((domain) => { + return [ + generateUuid(), + { + baseUrl: domain.domain, + handlerPath: domain.handler_path, + } satisfies OrganizationRenderedConfig['domains']['trustedDomains'][string], + ]; + })) : undefined, + // ======================= api keys ======================= + 'apiKeys.enabled.user': dataOptions.allow_user_api_keys, + 'apiKeys.enabled.team': dataOptions.allow_team_api_keys, + // ======================= emails ======================= + 'emails.server': dataOptions.email_config ? { + isShared: dataOptions.email_config.type === 'shared', + host: dataOptions.email_config.host, + port: dataOptions.email_config.port, + username: dataOptions.email_config.username, + password: dataOptions.email_config.password, + senderName: dataOptions.email_config.sender_name, + senderEmail: dataOptions.email_config.sender_email, + } satisfies OrganizationRenderedConfig['emails']['server'] : undefined, + 'emails.selectedThemeId': dataOptions.email_theme, + // ======================= rbac ======================= + 'rbac.defaultPermissions.teamMember': translateDefaultPermissions(dataOptions.team_member_default_permissions), + 'rbac.defaultPermissions.teamCreator': translateDefaultPermissions(dataOptions.team_creator_default_permissions), + 'rbac.defaultPermissions.signUp': translateDefaultPermissions(dataOptions.user_default_permissions), + }); - if (options.type === "create") { - configOverrideOverride['rbac.permissions.team_member'] ??= { - description: "Default permission for team members", - scope: "team", - containedPermissionIds: { - '$read_members': true, - '$invite_members': true, - }, - } satisfies OrganizationRenderedConfig['rbac']['permissions'][string]; - configOverrideOverride['rbac.permissions.team_admin'] ??= { - description: "Default permission for team admins", - scope: "team", - containedPermissionIds: { - '$update_team': true, - '$delete_team': true, - '$read_members': true, - '$remove_members': true, - '$invite_members': true, - '$manage_api_keys': true, - }, - } satisfies OrganizationRenderedConfig['rbac']['permissions'][string]; + if (options.type === "create") { + configOverrideOverride['rbac.permissions.team_member'] ??= { + description: "Default permission for team members", + scope: "team", + containedPermissionIds: { + '$read_members': true, + '$invite_members': true, + }, + } satisfies OrganizationRenderedConfig['rbac']['permissions'][string]; + configOverrideOverride['rbac.permissions.team_admin'] ??= { + description: "Default permission for team admins", + scope: "team", + containedPermissionIds: { + '$update_team': true, + '$delete_team': true, + '$read_members': true, + '$remove_members': true, + '$invite_members': true, + '$manage_api_keys': true, + }, + } satisfies OrganizationRenderedConfig['rbac']['permissions'][string]; - configOverrideOverride['rbac.defaultPermissions.teamCreator'] ??= { 'team_admin': true }; - configOverrideOverride['rbac.defaultPermissions.teamMember'] ??= { 'team_member': true }; + configOverrideOverride['rbac.defaultPermissions.teamCreator'] ??= { 'team_admin': true }; + configOverrideOverride['rbac.defaultPermissions.teamMember'] ??= { 'team_member': true }; - configOverrideOverride['auth.password.allowSignIn'] ??= true; - } - - await overrideEnvironmentConfigOverride({ - tx, - projectId: project.id, - branchId: branchId, - environmentConfigOverrideOverride: configOverrideOverride, - }); - - return [project.id, branchId]; + configOverrideOverride['auth.password.allowSignIn'] ??= true; + } + await overrideEnvironmentConfigOverride({ + projectId: projectId, + branchId: branchId, + environmentConfigOverrideOverride: configOverrideOverride, }); diff --git a/apps/dashboard/src/app/(main)/integrations/featurebase/sso/page.tsx b/apps/dashboard/src/app/(main)/integrations/featurebase/sso/page.tsx index e0ab747ac..48c345fab 100644 --- a/apps/dashboard/src/app/(main)/integrations/featurebase/sso/page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/featurebase/sso/page.tsx @@ -2,8 +2,13 @@ import { stackServerApp } from "@/stack"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; import { urlString } from "@stackframe/stack-shared/dist/utils/urls"; import * as jose from "jose"; +import { Metadata } from "next"; import { redirect } from "next/navigation"; +export const metadata: Metadata = { + title: "Signing you in...", +}; + export default async function FeaturebaseSSO({ searchParams, }: { diff --git a/apps/dashboard/src/app/(main)/integrations/layout.tsx b/apps/dashboard/src/app/(main)/integrations/neon/layout.tsx similarity index 100% rename from apps/dashboard/src/app/(main)/integrations/layout.tsx rename to apps/dashboard/src/app/(main)/integrations/neon/layout.tsx diff --git a/apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts index 2c6148065..8f88c61c4 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts @@ -496,6 +496,104 @@ it("verifies email_theme update persists", async ({ expect }) => { expect(response.body.config.email_theme).toBe("a0172b5d-cff0-463b-83bb-85124697373a"); // default-dark }); +it("updates trusted domains without modifying allow_localhost", async ({ expect }) => { + await Project.createAndSwitch(); + const response1 = await niceBackendFetch("/api/v1/internal/projects/current", { + method: "PATCH", + accessType: "admin", + body: { + config: { + allow_localhost: false, + }, + }, + }); + expect(response1).toMatchInlineSnapshot(` + NiceResponse { + "status": 200, + "body": { + "config": { + "allow_localhost": false, + "allow_team_api_keys": false, + "allow_user_api_keys": false, + "client_team_creation_enabled": false, + "client_user_deletion_enabled": false, + "create_team_on_sign_up": false, + "credential_enabled": true, + "domains": [], + "email_config": { "type": "shared" }, + "email_theme": "", + "enabled_oauth_providers": [], + "magic_link_enabled": false, + "oauth_account_merge_strategy": "link_method", + "oauth_providers": [], + "passkey_enabled": false, + "sign_up_enabled": true, + "team_creator_default_permissions": [{ "id": "team_admin" }], + "team_member_default_permissions": [{ "id": "team_member" }], + "user_default_permissions": [], + }, + "created_at_millis": , + "description": "", + "display_name": "New Project", + "id": "", + "is_production_mode": false, + }, + "headers": Headers {