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 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 6d3bc2db3..d4bf9e014 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/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/app/api/latest/internal/projects/crud.tsx b/apps/backend/src/app/api/latest/internal/projects/crud.tsx index ba0acfa52..7260c6c2e 100644 --- a/apps/backend/src/app/api/latest/internal/projects/crud.tsx +++ b/apps/backend/src/app/api/latest/internal/projects/crud.tsx @@ -32,7 +32,13 @@ export const adminUserProjectsCrudHandlers = createLazyProxy(() => createCrudHan const project = await createOrUpdateProject({ ownerIds: userIds, type: 'create', - data, + data: { + ...data, + config: { + allow_localhost: true, + ...data.config, + }, + }, }); const tenancy = await getSoleTenancyFromProjectBranch(project.id, DEFAULT_BRANCH_ID); return { 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/freestyle.tsx b/apps/backend/src/lib/freestyle.tsx index ab2357587..03acc0ed0 100644 --- a/apps/backend/src/lib/freestyle.tsx +++ b/apps/backend/src/lib/freestyle.tsx @@ -17,7 +17,12 @@ export class TracedFreestyleSandboxes { 'freestyle.nodeModules.count': options?.nodeModules ? Object.keys(options.nodeModules).length.toString() : '0', } }, async () => { - return await this.freestyle.executeScript(script, options); + try { + return await this.freestyle.executeScript(script, options); + } catch (error) { + captureError("freestyle.executeScript", error); + throw new StackAssertionError("Error executing script with Freestyle! " + errorToNiceString(error), { cause: error }); + } }); } } 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/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/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/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/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 {