From 918f205bd97747da640f46862284f97e15095f25 Mon Sep 17 00:00:00 2001 From: Zai Shi Date: Sun, 1 Dec 2024 15:57:51 +0100 Subject: [PATCH] Self-host docker (#353) --- .dockerignore | 139 ++++++++++++ .github/workflows/docker-build.yaml | 60 ++++++ .github/workflows/publish-docs.yaml | 2 +- .gitignore | 2 +- .vscode/settings.json | 2 + apps/backend/.env | 2 +- apps/backend/.env.development | 2 +- apps/backend/next.config.mjs | 4 + apps/backend/package.json | 5 +- apps/backend/prisma/seed-self-host.ts | 202 ++++++++++++++++++ apps/backend/prisma/tsup.config.ts | 13 ++ .../[provider_id]/access-token/route.tsx | 2 +- .../neon/oauth/idp/[[...route]]/route.tsx | 2 +- .../integrations/neon/oauth/token/route.tsx | 4 +- apps/backend/src/oauth/providers/apple.tsx | 4 +- .../backend/src/oauth/providers/bitbucket.tsx | 6 +- apps/backend/src/oauth/providers/discord.tsx | 2 +- apps/backend/src/oauth/providers/facebook.tsx | 2 +- apps/backend/src/oauth/providers/github.tsx | 4 +- apps/backend/src/oauth/providers/gitlab.tsx | 6 +- apps/backend/src/oauth/providers/google.tsx | 2 +- apps/backend/src/oauth/providers/linkedin.tsx | 6 +- .../backend/src/oauth/providers/microsoft.tsx | 6 +- apps/backend/src/oauth/providers/mock.tsx | 2 +- apps/backend/src/oauth/providers/spotify.tsx | 6 +- apps/backend/src/oauth/providers/x.tsx | 6 +- apps/dashboard/.env | 2 +- apps/dashboard/.env.development | 2 +- apps/dashboard/next.config.mjs | 4 + apps/dashboard/package.json | 1 + .../[projectId]/auth-methods/providers.tsx | 2 +- .../(main)/integrations/neon/confirm/page.tsx | 4 +- apps/dashboard/src/app/layout.tsx | 14 +- apps/dashboard/src/components/env-keys.tsx | 2 +- docker/server/.env | 31 +++ docker/server/.env.example | 18 ++ docker/server/Dockerfile | 94 ++++++++ docker/server/entrypoint.sh | 29 +++ docs/fern/docs/pages/others/self-host.mdx | 26 ++- ...ack-app-constructor-options-before-ssk.mdx | 2 +- examples/cjs-test/.env.development | 2 +- examples/demo/.env | 2 +- examples/demo/.env.development | 2 +- examples/docs-examples/.env | 2 +- examples/docs-examples/.env.development | 2 +- examples/e-commerce/.env.development | 2 +- examples/middleware/.env | 2 +- examples/middleware/.env.development | 2 +- examples/partial-prerendering/.env | 2 +- .../partial-prerendering/.env.development | 2 +- packages/stack-shared/src/utils/env.tsx | 32 ++- packages/stack-shared/src/utils/jwt.tsx | 2 +- packages/stack/package.json | 2 +- packages/stack/src/lib/stack-app.ts | 2 +- pnpm-lock.yaml | 129 +++++++---- turbo.json | 23 ++ 56 files changed, 829 insertions(+), 105 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-build.yaml create mode 100644 apps/backend/prisma/seed-self-host.ts create mode 100644 apps/backend/prisma/tsup.config.ts create mode 100644 docker/server/.env create mode 100644 docker/server/.env.example create mode 100644 docker/server/Dockerfile create mode 100755 docker/server/entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2eff84b4e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,139 @@ +# Git ignore rules +*.untracked +*.untracked.* + +.vercel + +# Misc +.DS_Store +.eslintcache +.env.local +.env.*.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log +ui-debug.log +.pnpm-debug.log +.husky +tmp + +vitest.config.ts.timestamp-* +tsup.config.bundled_* + +# Dependencies +node_modules + +# Build dirs +.next +build +dist + +# Generated files +.docusaurus +.cache-loader +**.tsbuildinfo + +.xata* + +# VS +/.vs/slnx.sqlite-journal +/.vs/slnx.sqlite +/.vs +.vscode/generated* + +# Jetbrains +.idea + +# GitHub Actions runner +/actions-runner +/_work + +# DB +dev.db* +packages/adapter-prisma/prisma/dev.db +packages/adapter-prisma/prisma/migrations +db.sqlite +packages/adapter-supabase/supabase/.branches +packages/adapter-drizzle/.drizzle + +# Tests +coverage +dynamodblocal-bin +firestore-debug.log +test.schema.gql +test-results +playwright-report +blob-report +playwright/.cache + +# Turborepo +.turbo + +# docusaurus +docs/.docusaurus +docs/manifest.mjs + +# Core +packages/core/src/providers/oauth-types.ts +packages/core/lib +packages/core/providers +docs/docs/reference/core + +# Next.js +docs/docs/reference/nextjs +next-env.d.ts + +# SvelteKit +packages/frameworks-sveltekit/index.* +packages/frameworks-sveltekit/client.* +packages/frameworks-sveltekit/.svelte-kit +packages/frameworks-sveltekit/package +packages/frameworks-sveltekit/vite.config.js.timestamp-* +packages/frameworks-sveltekit/vite.config.ts.timestamp-* +docs/docs/reference/sveltekit + +# SolidStart +docs/docs/reference/solidstart + +# Express +docs/docs/reference/express + +# Adapters +docs/docs/reference/adapter + +## Drizzle migration folder +.drizzle + +# Sentry Config File +.sentryclirc + +# Python +__pycache__/ +.venv/ + +# Docker ignore rules +.changeset +.git +.github +.turbo +**/.turbo +.vscode + +.env +.env.* +**/.env +**/.env.* +**/.next + +**/dist + +examples + +node_modules +**/node_modules + +deploy +!deploy/docker/**/entrypoint.sh +docker-compose.yaml diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml new file mode 100644 index 000000000..f3ae04a84 --- /dev/null +++ b/.github/workflows/docker-build.yaml @@ -0,0 +1,60 @@ +name: Docker Build and Push + +on: + push: + branches: + - main + - dev + - docker-build + tags: + - "*.*.*" + pull_request: + +jobs: + build-server: + name: Docker Build and Push Server + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKER_REPO }}/server + tags: | + type=ref,event=branch + type=sha,prefix= + type=match,pattern=\d.\d.\d + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set push condition + id: push-condition + run: | + if [[ ${{ github.event_name == 'push' }} == 'true' ]]; then + echo "should_push=true" >> $GITHUB_OUTPUT + else + echo "should_push=false" >> $GITHUB_OUTPUT + fi + + - name: Login to DockerHub + if: steps.push-condition.outputs.should_push == 'true' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/server/Dockerfile + push: ${{ steps.push-condition.outputs.should_push }} + tags: ${{ steps.meta.outputs.tags == 'main' && 'latest' || steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml index 71378515e..9c26345c9 100644 --- a/.github/workflows/publish-docs.yaml +++ b/.github/workflows/publish-docs.yaml @@ -9,7 +9,7 @@ jobs: run: runs-on: ubuntu-latest env: - NEXT_PUBLIC_STACK_URL: http://localhost:8102 + NEXT_PUBLIC_STACK_API_URL: http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID: internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: internal-project-publishable-client-key STACK_SECRET_SERVER_KEY: internal-project-secret-server-key diff --git a/.gitignore b/.gitignore index cb9d46371..b6e934f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -113,6 +113,6 @@ docs/docs/reference/adapter # Sentry Config File .sentryclirc -# python +# Python __pycache__/ .venv/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 04147e50d..4c59e5ded 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -44,6 +44,7 @@ "pageview", "pkcco", "PKCE", + "pooler", "posthog", "preconfigured", "Proxied", @@ -55,6 +56,7 @@ "RPID", "simplewebauthn", "spoofable", + "stackauth", "stackframe", "supabase", "Svix", diff --git a/apps/backend/.env b/apps/backend/.env index c309469cc..20adc7031 100644 --- a/apps/backend/.env +++ b/apps/backend/.env @@ -1,5 +1,5 @@ # Basic -STACK_BASE_URL=# the base URL of Stack's backend/API. For local development, this is `http://localhost:8102`; for the managed service, this is `https://api.stack-auth.com`. +NEXT_PUBLIC_STACK_API_URL=# the base URL of Stack's backend/API. For local development, this is `http://localhost:8102`; for the managed service, this is `https://api.stack-auth.com`. NEXT_PUBLIC_STACK_DASHBOARD_URL=# the URL of Stack's dashboard. For local development, this is `http://localhost:8101`; for the managed service, this is `https://app.stack-auth.com`. STACK_SERVER_SECRET=# a random, unguessable secret key generated by `pnpm generate-keys` diff --git a/apps/backend/.env.development b/apps/backend/.env.development index 448b14222..e7d854ace 100644 --- a/apps/backend/.env.development +++ b/apps/backend/.env.development @@ -1,4 +1,4 @@ -STACK_BASE_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101 STACK_SERVER_SECRET=23-wuNpik0gIW4mruTz25rbIvhuuvZFrLOLtL7J4tyo diff --git a/apps/backend/next.config.mjs b/apps/backend/next.config.mjs index 6ea9c9602..1759ebf48 100644 --- a/apps/backend/next.config.mjs +++ b/apps/backend/next.config.mjs @@ -47,6 +47,10 @@ const withConfiguredSentryConfig = (nextConfig) => /** @type {import('next').NextConfig} */ const nextConfig = { + // optionally set output to "standalone" for Docker builds + // https://nextjs.org/docs/pages/api-reference/next-config-js/output + output: process.env.NEXT_CONFIG_OUTPUT, + // we're open-source, so we can provide source maps productionBrowserSourceMaps: true, poweredByHeader: false, diff --git a/apps/backend/package.json b/apps/backend/package.json index 8f13e3fd1..7f8be89f8 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -9,6 +9,8 @@ "with-env:prod": "dotenv -c --", "dev": "concurrently -n \"dev,codegen,prisma-studio\" -k \"next dev --port 8102\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\"", "build": "pnpm run codegen && next build", + "docker-build": "pnpm run codegen && next build --experimental-build-mode compile", + "self-host-seed-script": "tsup --config prisma/tsup.config.ts", "analyze-bundle": "ANALYZE_BUNDLE=1 pnpm run build", "start": "next start --port 8102", "codegen-prisma": "pnpm run prisma generate", @@ -78,8 +80,9 @@ "@types/semver": "^7.5.8", "concurrently": "^8.2.2", "glob": "^10.4.1", - "prisma": "^5.9.1", + "prisma": "^5.20.0", "rimraf": "^5.0.5", + "tsup": "^8.3.0", "tsx": "^4.7.2" } } diff --git a/apps/backend/prisma/seed-self-host.ts b/apps/backend/prisma/seed-self-host.ts new file mode 100644 index 000000000..82a0b86e9 --- /dev/null +++ b/apps/backend/prisma/seed-self-host.ts @@ -0,0 +1,202 @@ +/* eslint-disable no-restricted-syntax */ +import { PrismaClient } from '@prisma/client'; +import { hashPassword } from "@stackframe/stack-shared/dist/utils/hashes"; + +const prisma = new PrismaClient(); + +async function seed() { + console.log('Seeding database...'); + + // Optional default admin user + const adminEmail = process.env.STACK_DEFAULT_DASHBOARD_USER_EMAIL; + const adminPassword = process.env.STACK_DEFAULT_DASHBOARD_USER_PASSWORD; + const adminInternalAccess = process.env.STACK_DEFAULT_DASHBOARD_USER_INTERNAL_ACCESS === 'true'; + + // Optionally disable sign up for "internal" project + const signUpEnabled = process.env.STACK_INTERNAL_SIGN_UP_ENABLED === 'true'; + + // Optionally add a custom domain to the internal project + const dashboardDomain = process.env.NEXT_PUBLIC_STACK_DASHBOARD_URL; + const allowLocalhost = process.env.STACK_DASHBOARD_ALLOW_LOCALHOST === 'true'; + + let internalProject = await prisma.project.findUnique({ + where: { + id: 'internal', + }, + include: { + config: true, + } + }); + + if (!internalProject) { + console.log('No existing internal project found, creating...'); + + internalProject = await prisma.project.create({ + data: { + id: 'internal', + displayName: 'Stack Dashboard', + description: 'Stack\'s admin dashboard', + isProductionMode: false, + apiKeySets: { + create: [{ + description: "Internal API key set", + // These keys must match the values used in the Stack dashboard env to be able to login via the UI. + publishableClientKey: process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY, + secretServerKey: process.env.STACK_SECRET_SERVER_KEY, + superSecretAdminKey: process.env.STACK_SUPER_SECRET_ADMIN_KEY, + expiresAt: new Date('2099-12-31T23:59:59Z'), + }], + }, + config: { + create: { + allowLocalhost: true, + signUpEnabled, // see STACK_SIGN_UP_DISABLED var above + emailServiceConfig: { + create: { + proxiedEmailServiceConfig: { + create: {} + } + } + }, + createTeamOnSignUp: false, + clientTeamCreationEnabled: false, + authMethodConfigs: { + create: [ + { + passwordConfig: { + create: {}, + } + }, + ], + } + } + } + }, + include: { + config: true, + } + }); + + console.log('Internal project created'); + } + + // Create optional default admin user if credentials are provided. + // This user will be able to login to the dashboard with both email/password and magic link. + if (adminEmail && adminPassword) { + const oldAdminUser = await prisma.projectUser.findFirst({ + where: { + projectId: 'internal', + contactChannels: { + some: { + type: 'EMAIL', + value: adminEmail, + } + } + } + }); + + if (oldAdminUser) { + console.log(`User with email ${adminEmail} already exists, skipping creation`); + } else { + console.log(`No existing admin user with email ${adminEmail} found, creating...`); + + await prisma.$transaction(async (tx) => { + const newUser = await tx.projectUser.create({ + data: { + projectId: 'internal', + serverMetadata: adminInternalAccess + ? { managedProjectIds: ['internal'] } + : undefined, + } + }); + + await tx.contactChannel.create({ + data: { + projectUserId: newUser.projectUserId, + projectId: 'internal', + type: 'EMAIL' as const, + value: adminEmail as string, + isVerified: false, + isPrimary: 'TRUE', + usedForAuth: 'TRUE', + } + }); + + const passwordConfig = await tx.passwordAuthMethodConfig.findFirstOrThrow({ + where: { + projectConfigId: (internalProject as any).configId + }, + include: { + authMethodConfig: true, + } + }); + + await tx.authMethod.create({ + data: { + projectId: 'internal', + projectConfigId: (internalProject as any).configId, + projectUserId: newUser.projectUserId, + authMethodConfigId: passwordConfig.authMethodConfigId, + passwordAuthMethod: { + create: { + passwordHash: await hashPassword(adminPassword), + projectUserId: newUser.projectUserId, + } + } + } + }); + }); + + console.log('Initial admin user created: ', adminEmail); + } + } + + if (internalProject.config.allowLocalhost !== allowLocalhost) { + console.log('Updating allowLocalhost for internal project: ', allowLocalhost); + + await prisma.project.update({ + where: { id: 'internal' }, + data: { + config: { + update: { + allowLocalhost, + } + } + } + }); + } + + if (dashboardDomain) { + const url = new URL(dashboardDomain); + + if (url.hostname !== 'localhost') { + console.log('Adding trusted domain for internal project: ', dashboardDomain); + + await prisma.projectDomain.upsert({ + where: { + projectConfigId_domain: { + projectConfigId: internalProject.configId, + domain: dashboardDomain, + } + }, + update: {}, + create: { + projectConfigId: internalProject.configId, + domain: dashboardDomain, + handlerPath: '/', + } + }); + } else if (!allowLocalhost) { + throw new Error('Cannot use localhost as a trusted domain if STACK_DASHBOARD_ALLOW_LOCALHOST is not set to true'); + } + } + + console.log('Seeding complete!'); +} + +seed().catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); +// eslint-disable-next-line @typescript-eslint/no-misused-promises +}).finally(async () => await prisma.$disconnect()); diff --git a/apps/backend/prisma/tsup.config.ts b/apps/backend/prisma/tsup.config.ts new file mode 100644 index 000000000..608299798 --- /dev/null +++ b/apps/backend/prisma/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'tsup'; + +// tsup config to build the self-hosting seed script so it can be +// run in the Docker container with no extra dependencies. +export default defineConfig({ + entry: ['prisma/seed-self-host.ts'], + format: ['cjs'], + outDir: 'dist', + target: 'node22', + platform: 'node', + noExternal: ['@stackframe/stack-shared', '@prisma/client'], + clean: true +}); diff --git a/apps/backend/src/app/api/v1/auth/oauth/connected-accounts/[provider_id]/access-token/route.tsx b/apps/backend/src/app/api/v1/auth/oauth/connected-accounts/[provider_id]/access-token/route.tsx index 7d2aa5b69..cb1188b22 100644 --- a/apps/backend/src/app/api/v1/auth/oauth/connected-accounts/[provider_id]/access-token/route.tsx +++ b/apps/backend/src/app/api/v1/auth/oauth/connected-accounts/[provider_id]/access-token/route.tsx @@ -22,7 +22,7 @@ export const POST = createSmartRouteHandler({ }), handler: async ({ params, body }, fullReq) => { const response = await fetch( - `${getEnvVariable('STACK_BASE_URL')}/api/v1/connected-accounts/me/${params.provider_id}/access-token`, + `${getEnvVariable('NEXT_PUBLIC_STACK_API_URL')}/api/v1/connected-accounts/me/${params.provider_id}/access-token`, { method: 'POST', headers: Object.fromEntries(Object.entries(fullReq.headers).map(([key, value]) => [key, value?.[0] || ""])), diff --git a/apps/backend/src/app/api/v1/integrations/neon/oauth/idp/[[...route]]/route.tsx b/apps/backend/src/app/api/v1/integrations/neon/oauth/idp/[[...route]]/route.tsx index 767965c82..5ec67e5c9 100644 --- a/apps/backend/src/app/api/v1/integrations/neon/oauth/idp/[[...route]]/route.tsx +++ b/apps/backend/src/app/api/v1/integrations/neon/oauth/idp/[[...route]]/route.tsx @@ -13,7 +13,7 @@ const pathPrefix = "/api/v1/integrations/neon/oauth/idp"; let _oidcCallbackPromiseCache: Promise | undefined; function getOidcCallbackPromise() { if (!_oidcCallbackPromiseCache) { - const apiBaseUrl = new URL(getEnvVariable("STACK_BASE_URL")); + const apiBaseUrl = new URL(getEnvVariable("NEXT_PUBLIC_STACK_API_URL")); const idpBaseUrl = new URL(pathPrefix, apiBaseUrl); _oidcCallbackPromiseCache = (async () => { const oidc = await createOidcProvider({ diff --git a/apps/backend/src/app/api/v1/integrations/neon/oauth/token/route.tsx b/apps/backend/src/app/api/v1/integrations/neon/oauth/token/route.tsx index d730d9505..e1ed15353 100644 --- a/apps/backend/src/app/api/v1/integrations/neon/oauth/token/route.tsx +++ b/apps/backend/src/app/api/v1/integrations/neon/oauth/token/route.tsx @@ -36,7 +36,7 @@ export const POST = createSmartRouteHandler({ }), ), handler: async (req) => { - const tokenResponse = await fetch(new URL("/api/v1/integrations/neon/oauth/idp/token", getEnvVariable("STACK_BASE_URL")), { + const tokenResponse = await fetch(new URL("/api/v1/integrations/neon/oauth/idp/token", getEnvVariable("NEXT_PUBLIC_STACK_API_URL")), { method: "POST", body: new URLSearchParams(req.body).toString(), headers: { @@ -52,7 +52,7 @@ export const POST = createSmartRouteHandler({ } const tokenResponseBody = await tokenResponse.json(); - const userInfoResponse = await fetch(new URL("/api/v1/integrations/neon/oauth/idp/me", getEnvVariable("STACK_BASE_URL")), { + const userInfoResponse = await fetch(new URL("/api/v1/integrations/neon/oauth/idp/me", getEnvVariable("NEXT_PUBLIC_STACK_API_URL")), { method: "GET", headers: { Authorization: `Bearer ${tokenResponseBody.access_token}`, diff --git a/apps/backend/src/oauth/providers/apple.tsx b/apps/backend/src/oauth/providers/apple.tsx index 93420aa34..7e263c05f 100644 --- a/apps/backend/src/oauth/providers/apple.tsx +++ b/apps/backend/src/oauth/providers/apple.tsx @@ -1,8 +1,8 @@ import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; +import { decodeJwt } from 'jose'; import { OAuthUserInfo, validateUserInfo } from "../utils"; import { OAuthBaseProvider, TokenSet } from "./base"; -import { decodeJwt } from 'jose'; export class AppleProvider extends OAuthBaseProvider { private constructor( @@ -17,7 +17,7 @@ export class AppleProvider extends OAuthBaseProvider { issuer: "https://appleid.apple.com", authorizationEndpoint: "https://appleid.apple.com/auth/authorize", tokenEndpoint: "https://appleid.apple.com/auth/token", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/apple", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/apple", jwksUri: "https://appleid.apple.com/auth/keys", baseScope: "name email", authorizationExtraParams: { "response_mode": "form_post" }, diff --git a/apps/backend/src/oauth/providers/bitbucket.tsx b/apps/backend/src/oauth/providers/bitbucket.tsx index 0b1750f35..36e8b5987 100644 --- a/apps/backend/src/oauth/providers/bitbucket.tsx +++ b/apps/backend/src/oauth/providers/bitbucket.tsx @@ -1,6 +1,6 @@ -import { OAuthBaseProvider, TokenSet } from "./base"; -import { OAuthUserInfo, validateUserInfo } from "../utils"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { OAuthBaseProvider, TokenSet } from "./base"; export class BitbucketProvider extends OAuthBaseProvider { private constructor( @@ -15,7 +15,7 @@ export class BitbucketProvider extends OAuthBaseProvider { issuer: "https://bitbucket.org", authorizationEndpoint: "https://bitbucket.org/site/oauth2/authorize", tokenEndpoint: "https://bitbucket.org/site/oauth2/access_token", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/bitbucket", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/bitbucket", baseScope: "account email", ...options, })) diff --git a/apps/backend/src/oauth/providers/discord.tsx b/apps/backend/src/oauth/providers/discord.tsx index 104bbafa3..225f22b61 100644 --- a/apps/backend/src/oauth/providers/discord.tsx +++ b/apps/backend/src/oauth/providers/discord.tsx @@ -17,7 +17,7 @@ export class DiscordProvider extends OAuthBaseProvider { issuer: "https://discord.com", authorizationEndpoint: "https://discord.com/oauth2/authorize", tokenEndpoint: "https://discord.com/api/oauth2/token", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/discord", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/discord", baseScope: "identify email", ...options, })); diff --git a/apps/backend/src/oauth/providers/facebook.tsx b/apps/backend/src/oauth/providers/facebook.tsx index 679355419..233c5113a 100644 --- a/apps/backend/src/oauth/providers/facebook.tsx +++ b/apps/backend/src/oauth/providers/facebook.tsx @@ -19,7 +19,7 @@ export class FacebookProvider extends OAuthBaseProvider { issuer: "https://www.facebook.com", authorizationEndpoint: "https://facebook.com/v20.0/dialog/oauth/", tokenEndpoint: "https://graph.facebook.com/v20.0/oauth/access_token", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/facebook", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/facebook", baseScope: "openid public_profile email", openid: true, jwksUri: "https://www.facebook.com/.well-known/oauth/openid/jwks", diff --git a/apps/backend/src/oauth/providers/github.tsx b/apps/backend/src/oauth/providers/github.tsx index b43cc3979..2884c0ae0 100644 --- a/apps/backend/src/oauth/providers/github.tsx +++ b/apps/backend/src/oauth/providers/github.tsx @@ -1,7 +1,7 @@ import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; import { OAuthUserInfo, validateUserInfo } from "../utils"; import { OAuthBaseProvider, TokenSet } from "./base"; -import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; export class GithubProvider extends OAuthBaseProvider { private constructor( @@ -19,7 +19,7 @@ export class GithubProvider extends OAuthBaseProvider { authorizationEndpoint: "https://github.com/login/oauth/authorize", tokenEndpoint: "https://github.com/login/oauth/access_token", userinfoEndpoint: "https://api.github.com/user", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/github", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/github", baseScope: "user:email", // GitHub token does not expire except for lack of use in a year // We set a default of 1 year diff --git a/apps/backend/src/oauth/providers/gitlab.tsx b/apps/backend/src/oauth/providers/gitlab.tsx index 3dd04dfe0..2b17940bf 100644 --- a/apps/backend/src/oauth/providers/gitlab.tsx +++ b/apps/backend/src/oauth/providers/gitlab.tsx @@ -1,6 +1,6 @@ -import { OAuthBaseProvider, TokenSet } from "./base"; -import { OAuthUserInfo, validateUserInfo } from "../utils"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { OAuthBaseProvider, TokenSet } from "./base"; export class GitlabProvider extends OAuthBaseProvider { private constructor( @@ -17,7 +17,7 @@ export class GitlabProvider extends OAuthBaseProvider { tokenEndpoint: "https://gitlab.com/oauth/token", userinfoEndpoint: "https://gitlab.com/api/v4/user", redirectUri: - getEnvVariable("STACK_BASE_URL") + + getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/gitlab", baseScope: "read_user", ...options, diff --git a/apps/backend/src/oauth/providers/google.tsx b/apps/backend/src/oauth/providers/google.tsx index b2df0dd08..bee23ae11 100644 --- a/apps/backend/src/oauth/providers/google.tsx +++ b/apps/backend/src/oauth/providers/google.tsx @@ -18,7 +18,7 @@ export class GoogleProvider extends OAuthBaseProvider { authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", tokenEndpoint: "https://oauth2.googleapis.com/token", userinfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/google", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/google", openid: true, jwksUri: "https://www.googleapis.com/oauth2/v3/certs", baseScope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile", diff --git a/apps/backend/src/oauth/providers/linkedin.tsx b/apps/backend/src/oauth/providers/linkedin.tsx index 80b2e89b8..892ba928f 100644 --- a/apps/backend/src/oauth/providers/linkedin.tsx +++ b/apps/backend/src/oauth/providers/linkedin.tsx @@ -1,6 +1,6 @@ -import { OAuthBaseProvider, TokenSet } from "./base"; -import { OAuthUserInfo, validateUserInfo } from "../utils"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { OAuthBaseProvider, TokenSet } from "./base"; // Note: Need to install Sign In with LinkedIn using OpenID Connect from product section in app list. @@ -17,7 +17,7 @@ export class LinkedInProvider extends OAuthBaseProvider { issuer: "https://www.linkedin.com/oauth", authorizationEndpoint: "https://www.linkedin.com/oauth/v2/authorization", tokenEndpoint: "https://www.linkedin.com/oauth/v2/accessToken", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/linkedin", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/linkedin", baseScope: "openid profile email", openid: true, jwksUri: "https://www.linkedin.com/oauth/openid/jwks", diff --git a/apps/backend/src/oauth/providers/microsoft.tsx b/apps/backend/src/oauth/providers/microsoft.tsx index 8ed79e0fe..cba881473 100644 --- a/apps/backend/src/oauth/providers/microsoft.tsx +++ b/apps/backend/src/oauth/providers/microsoft.tsx @@ -1,6 +1,6 @@ -import { OAuthBaseProvider, TokenSet } from "./base"; -import { OAuthUserInfo, validateUserInfo } from "../utils"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { OAuthBaseProvider, TokenSet } from "./base"; export class MicrosoftProvider extends OAuthBaseProvider { private constructor( @@ -18,7 +18,7 @@ export class MicrosoftProvider extends OAuthBaseProvider { issuer: `https://login.microsoftonline.com${"/" + options.microsoftTenantId || ""}`, authorizationEndpoint: `https://login.microsoftonline.com/${options.microsoftTenantId || 'consumers'}/oauth2/v2.0/authorize`, tokenEndpoint: `https://login.microsoftonline.com/${options.microsoftTenantId || 'consumers'}/oauth2/v2.0/token`, - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/microsoft", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/microsoft", baseScope: "User.Read", ...options, })); diff --git a/apps/backend/src/oauth/providers/mock.tsx b/apps/backend/src/oauth/providers/mock.tsx index 6c0cdb8bc..6c973f003 100644 --- a/apps/backend/src/oauth/providers/mock.tsx +++ b/apps/backend/src/oauth/providers/mock.tsx @@ -12,7 +12,7 @@ export class MockProvider extends OAuthBaseProvider { static async create(providerId: string) { return new MockProvider(...await OAuthBaseProvider.createConstructorArgs({ discoverFromUrl: getEnvVariable("STACK_OAUTH_MOCK_URL"), - redirectUri: `${getEnvVariable("STACK_BASE_URL")}/api/v1/auth/oauth/callback/${providerId}`, + redirectUri: `${getEnvVariable("NEXT_PUBLIC_STACK_API_URL")}/api/v1/auth/oauth/callback/${providerId}`, baseScope: "openid", openid: true, clientId: providerId, diff --git a/apps/backend/src/oauth/providers/spotify.tsx b/apps/backend/src/oauth/providers/spotify.tsx index 602ebc1c4..ca6b69100 100644 --- a/apps/backend/src/oauth/providers/spotify.tsx +++ b/apps/backend/src/oauth/providers/spotify.tsx @@ -1,6 +1,6 @@ -import { OAuthBaseProvider, TokenSet } from "./base"; -import { OAuthUserInfo, validateUserInfo } from "../utils"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { OAuthBaseProvider, TokenSet } from "./base"; export class SpotifyProvider extends OAuthBaseProvider { private constructor( @@ -17,7 +17,7 @@ export class SpotifyProvider extends OAuthBaseProvider { issuer: "https://accounts.spotify.com", authorizationEndpoint: "https://accounts.spotify.com/authorize", tokenEndpoint: "https://accounts.spotify.com/api/token", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/spotify", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/spotify", baseScope: "user-read-email user-read-private", ...options, })); diff --git a/apps/backend/src/oauth/providers/x.tsx b/apps/backend/src/oauth/providers/x.tsx index 4ab24f6e7..06f07b075 100644 --- a/apps/backend/src/oauth/providers/x.tsx +++ b/apps/backend/src/oauth/providers/x.tsx @@ -1,6 +1,6 @@ -import { OAuthBaseProvider, TokenSet } from "./base"; -import { OAuthUserInfo, validateUserInfo } from "../utils"; import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { OAuthBaseProvider, TokenSet } from "./base"; export class XProvider extends OAuthBaseProvider { private constructor( @@ -15,7 +15,7 @@ export class XProvider extends OAuthBaseProvider { issuer: "https://twitter.com", authorizationEndpoint: "https://twitter.com/i/oauth2/authorize", tokenEndpoint: "https://api.x.com/2/oauth2/token", - redirectUri: getEnvVariable("STACK_BASE_URL") + "/api/v1/auth/oauth/callback/x", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_API_URL") + "/api/v1/auth/oauth/callback/x", baseScope: "users.read offline.access tweet.read", ...options, })) diff --git a/apps/dashboard/.env b/apps/dashboard/.env index afecc1be3..fa20194ae 100644 --- a/apps/dashboard/.env +++ b/apps/dashboard/.env @@ -1,5 +1,5 @@ # Basic -NEXT_PUBLIC_STACK_URL=# enter your stack endpoint here, For local development: http://localhost:8102 (no trailing slash) +NEXT_PUBLIC_STACK_API_URL=# enter your stack endpoint here, For local development: http://localhost:8102 (no trailing slash) NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# enter your Stack publishable client key here. For local development, just enter a random string, then run `pnpm prisma migrate reset` STACK_SECRET_SERVER_KEY=# enter your Stack secret client key here. For local development, do the same as above diff --git a/apps/dashboard/.env.development b/apps/dashboard/.env.development index b7f737b3c..8ae728d82 100644 --- a/apps/dashboard/.env.development +++ b/apps/dashboard/.env.development @@ -1,4 +1,4 @@ -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only diff --git a/apps/dashboard/next.config.mjs b/apps/dashboard/next.config.mjs index 696b17c3c..4c46f7854 100644 --- a/apps/dashboard/next.config.mjs +++ b/apps/dashboard/next.config.mjs @@ -61,6 +61,10 @@ const withConfiguredSentryConfig = (nextConfig) => /** @type {import('next').NextConfig} */ const nextConfig = { + // optionally set output to "standalone" for Docker builds + // https://nextjs.org/docs/pages/api-reference/next-config-js/output + output: process.env.NEXT_CONFIG_OUTPUT, + pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"], // we're open-source, so we can provide source maps diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 8dfa604de..49810b160 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -9,6 +9,7 @@ "with-env:prod": "dotenv -c --", "dev": "next dev --port 8101", "build": "next build", + "docker-build": "next build --experimental-build-mode compile", "analyze-bundle": "ANALYZE_BUNDLE=1 pnpm run build", "start": "next start --port 8101", "psql": "pnpm run with-env bash -c 'psql $STACK_DATABASE_CONNECTION_STRING'", diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx index 8d57dc989..fe236b650 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx @@ -107,7 +107,7 @@ export function ProviderSettingDialog(props: Props & { open: boolean, onClose: ( - {`${process.env.NEXT_PUBLIC_STACK_URL}/api/v1/auth/oauth/callback/${props.id}`} + {`${process.env.NEXT_PUBLIC_STACK_API_URL}/api/v1/auth/oauth/callback/${props.id}`} } diff --git a/apps/dashboard/src/app/(main)/integrations/neon/confirm/page.tsx b/apps/dashboard/src/app/(main)/integrations/neon/confirm/page.tsx index f9952ae3b..64e22653d 100644 --- a/apps/dashboard/src/app/(main)/integrations/neon/confirm/page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/neon/confirm/page.tsx @@ -28,7 +28,7 @@ export default async function NeonIntegrationConfirmPage(props: { searchParams: return { error: "unauthorized" }; } - const response = await fetch(new URL("/api/v1/integrations/neon/internal/confirm", getEnvVariable("NEXT_PUBLIC_STACK_URL")), { + const response = await fetch(new URL("/api/v1/integrations/neon/internal/confirm", getEnvVariable("NEXT_PUBLIC_STACK_API_URL")), { method: "POST", headers: { "Content-Type": "application/json", @@ -48,7 +48,7 @@ export default async function NeonIntegrationConfirmPage(props: { searchParams: const json = await response.json(); const authorizationCode = json.authorization_code; - const redirectUrl = new URL(`/api/v1/integrations/neon/oauth/idp/interaction/${encodeURIComponent(props.searchParams.interaction_uid)}/done`, getEnvVariable("NEXT_PUBLIC_STACK_URL")); + const redirectUrl = new URL(`/api/v1/integrations/neon/oauth/idp/interaction/${encodeURIComponent(props.searchParams.interaction_uid)}/done`, getEnvVariable("NEXT_PUBLIC_STACK_API_URL")); redirectUrl.searchParams.set("code", authorizationCode); redirect(redirectUrl.toString()); }; diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx index c913c74c2..e63605df2 100644 --- a/apps/dashboard/src/app/layout.tsx +++ b/apps/dashboard/src/app/layout.tsx @@ -8,21 +8,21 @@ import { StackProvider, StackTheme } from '@stackframe/stack'; import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env'; import { Toaster } from '@stackframe/stack-ui'; import { Analytics } from "@vercel/analytics/react"; +import { SpeedInsights } from "@vercel/speed-insights/next"; import { GeistMono } from "geist/font/mono"; import { GeistSans } from 'geist/font/sans'; import type { Metadata } from 'next'; +import dynamic from 'next/dynamic'; import { Inter as FontSans } from "next/font/google"; import React from 'react'; +import { VersionAlerter } from '../components/version-alerter'; import '../polyfills'; import { ClientPolyfill } from './client-polyfill'; import './globals.css'; import { CSPostHogProvider, UserIdentity } from './providers'; -import dynamic from 'next/dynamic'; -import { SpeedInsights } from "@vercel/speed-insights/next"; -import { VersionAlerter } from '../components/version-alerter'; export const metadata: Metadata = { - metadataBase: new URL(process.env.NEXT_PUBLIC_STACK_URL || ''), + metadataBase: new URL(process.env.NEXT_PUBLIC_STACK_API_URL || ''), title: { default: 'Stack Auth Dashboard', template: '%s | Stack Auth', @@ -31,12 +31,12 @@ export const metadata: Metadata = { openGraph: { title: 'Stack Auth Dashboard', description: 'Stack Auth is the open-source Auth0 alternative, and the fastest way to add authentication to your web app.', - images: [`${process.env.NEXT_PUBLIC_STACK_URL}/open-graph-image.png`] + images: [`${process.env.NEXT_PUBLIC_STACK_API_URL}/open-graph-image.png`] }, twitter: { title: 'Stack Auth Dashboard', description: 'Stack Auth is the open-source Auth0 alternative, and the fastest way to add authentication to your web app.', - images: [`${process.env.NEXT_PUBLIC_STACK_URL}/open-graph-image.png`] + images: [`${process.env.NEXT_PUBLIC_STACK_API_URL}/open-graph-image.png`] }, }; @@ -60,7 +60,7 @@ export default function RootLayout({ }: { children: React.ReactNode, }) { - const headTags: TagConfigJson[] = JSON.parse(getEnvVariable('NEXT_PUBLIC_STACK_HEAD_TAGS')); + const headTags: TagConfigJson[] = JSON.parse(getEnvVariable('NEXT_PUBLIC_STACK_HEAD_TAGS', '[]')); const translationLocale = getEnvVariable('STACK_DEVELOPMENT_TRANSLATION_LOCALE', "") || undefined; if (translationLocale !== undefined && getNodeEnvironment() !== 'development') { throw new Error(`STACK_DEVELOPMENT_TRANSLATION_LOCALE can only be used in development mode (found: ${JSON.stringify(translationLocale)})`); diff --git a/apps/dashboard/src/components/env-keys.tsx b/apps/dashboard/src/components/env-keys.tsx index b20b27e46..723d6c342 100644 --- a/apps/dashboard/src/components/env-keys.tsx +++ b/apps/dashboard/src/components/env-keys.tsx @@ -7,7 +7,7 @@ export default function EnvKeys(props: { superSecretAdminKey?: string, }) { const envFileContent = Object.entries({ - NEXT_PUBLIC_STACK_URL: process.env.NEXT_PUBLIC_STACK_URL === "https://api.stack-auth.com" ? undefined : process.env.NEXT_PUBLIC_STACK_URL, + NEXT_PUBLIC_STACK_API_URL: process.env.NEXT_PUBLIC_STACK_API_URL === "https://api.stack-auth.com" ? undefined : process.env.NEXT_PUBLIC_STACK_API_URL, NEXT_PUBLIC_STACK_PROJECT_ID: props.projectId, NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: props.publishableClientKey, STACK_SECRET_SERVER_KEY: props.secretServerKey, diff --git a/docker/server/.env b/docker/server/.env new file mode 100644 index 000000000..0a9977b3c --- /dev/null +++ b/docker/server/.env @@ -0,0 +1,31 @@ +NEXT_PUBLIC_STACK_API_URL=# https://your-backend-domain.com +NEXT_PUBLIC_STACK_DASHBOARD_URL=# https://your-dashboard-domain.com, this will be added as a trusted domain by the seed script +STACK_DASHBOARD_ALLOW_LOCALHOST=# if true, the internal dashboard project will allow localhost as a trusted domain. Do not set this to true in production. + +STACK_DATABASE_CONNECTION_STRING=# postgres connection string with pooler +STACK_DIRECT_DATABASE_CONNECTION_STRING=# postgres direct connection string + +NEXT_PUBLIC_STACK_PROJECT_ID=internal +NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# a secure random string +STACK_SECRET_SERVER_KEY=# a secure random string +STACK_SERVER_SECRET=# a 32 bytes base64url encoded random string, used for JWT encryption. can be generated with `pnpm generate-keys` + +# set these if you want to use the default admin user +STACK_DEFAULT_DASHBOARD_USER_EMAIL=# your admin email +STACK_DEFAULT_DASHBOARD_USER_PASSWORD=# your admin password +STACK_DEFAULT_DASHBOARD_USER_INTERNAL_ACCESS=# if true, the default admin user will have access to the internal dashboard project + +# Set these if you want to use any email functionality +STACK_EMAIL_HOST= +STACK_EMAIL_PORT= +STACK_EMAIL_USERNAME= +STACK_EMAIL_PASSWORD= +STACK_EMAIL_SENDER= + +# Set these if you want to use webhooks +STACK_SVIX_SERVER_URL=# this is only needed if you self-host the Svix service +STACK_SVIX_API_KEY= + + +STACK_RUN_MIGRATIONS=true +STACK_RUN_SEED_SCRIPT=true diff --git a/docker/server/.env.example b/docker/server/.env.example new file mode 100644 index 000000000..c5b1c7d1e --- /dev/null +++ b/docker/server/.env.example @@ -0,0 +1,18 @@ +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101 +STACK_DASHBOARD_ALLOW_LOCALHOST=true + +STACK_DATABASE_CONNECTION_STRING=postgres://postgres:password@host.docker.internal:5432/stackframe +STACK_DIRECT_DATABASE_CONNECTION_STRING=postgres://postgres:password@host.docker.internal:5432/stackframe + +NEXT_PUBLIC_STACK_PROJECT_ID=internal +NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only +STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only +STACK_SERVER_SECRET=23-wuNpik0gIW4mruTz25rbIvhuuvZFrLOLtL7J4tyo + +STACK_DEFAULT_DASHBOARD_USER_EMAIL=admin@email.com +STACK_DEFAULT_DASHBOARD_USER_PASSWORD=password +STACK_DEFAULT_DASHBOARD_USER_INTERNAL_ACCESS=false + +STACK_RUN_MIGRATIONS=true +STACK_RUN_SEED_SCRIPT=true diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile new file mode 100644 index 000000000..6c0578098 --- /dev/null +++ b/docker/server/Dockerfile @@ -0,0 +1,94 @@ +ARG NODE_VERSION=22.9.0 + +# Base +FROM node:${NODE_VERSION} AS base + +WORKDIR /app + +RUN apt-get update && \ + apt-get upgrade -y && \ + rm -rf /var/lib/apt/lists + +ENV PNPM_HOME=/pnpm +ENV PATH=$PNPM_HOME:$PATH + +RUN corepack enable +RUN corepack prepare pnpm --activate +RUN pnpm add -g turbo + + +# Prune stage +FROM base AS pruner + +COPY . . + +# https://turbo.build/repo/docs/guides/tools/docker +RUN turbo prune --scope=@stackframe/stack-backend --scope=@stackframe/stack-dashboard --docker + + +# Build stage +FROM base AS builder + +# copy over package.json files and install dependencies +COPY --from=pruner /app/out/json/ . +COPY --from=pruner /app/out/pnpm-lock.yaml . +COPY .gitignore . +COPY pnpm-workspace.yaml . +COPY turbo.json . +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + +# copy over the rest of the code for the build +COPY --from=pruner /app/out/full/ . + +# docs are currently required for the NextJS backend build, but won't exist in the final image +COPY docs ./docs + +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +ENV NEXT_CONFIG_OUTPUT=standalone + +# Build the backend NextJS app +RUN pnpm turbo run docker-build --filter=@stackframe/stack-backend... --filter=@stackframe/stack-dashboard... + +# Build the self-host seed script +RUN cd apps/backend && pnpm self-host-seed-script + +# Final image +FROM node:${NODE_VERSION}-slim + +WORKDIR /app + +# Install packages needed for deployment +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y openssl && \ + rm -rf /var/lib/apt/lists + +# Install Prisma CLI globally so we can run migrations on startup +RUN npm i -g prisma + +# Copy built backend +COPY --from=builder --chown=node:node /app/apps/backend/.next/standalone ./ +COPY --from=builder --chown=node:node /app/apps/backend/.next/static ./apps/backend/.next/static +COPY --from=builder --chown=node:node /app/apps/backend/prisma ./apps/backend/prisma +COPY --from=builder --chown=node:node /app/apps/backend/dist/seed-self-host.js ./apps/backend + +# Copy built dashboard +COPY --from=builder --chown=node:node /app/apps/dashboard/.next/standalone ./ +COPY --from=builder --chown=node:node /app/apps/dashboard/.next/static ./apps/dashboard/.next/static +COPY --from=builder --chown=node:node /app/apps/dashboard/public ./apps/dashboard/public + +# Add the entrypoint script +COPY ./docker/server/entrypoint.sh . +RUN chmod +x entrypoint.sh + +WORKDIR /app + +# Define environment variables for both services +ENV NODE_ENV=production +ENV BACKEND_PORT=8102 +ENV DASHBOARD_PORT=8101 + +USER node + +# Set entrypoint to run both backend and dashboard +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh new file mode 100755 index 000000000..9a9d2334e --- /dev/null +++ b/docker/server/entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +if [ "$STACK_RUN_MIGRATIONS" = "true" ]; then + echo "Running migrations..." + prisma migrate deploy --schema=./apps/backend/prisma/schema.prisma +else + echo "Skipping migrations." +fi + +if [ "$STACK_RUN_SEED_SCRIPT" = "true" ]; then + echo "Running seed script..." + cd apps/backend + node seed-self-host.js + cd ../.. +else + echo "Skipping seed script." +fi + +# Start backend and dashboard in parallel +echo "Starting backend on port $BACKEND_PORT..." +PORT=$BACKEND_PORT HOSTNAME=0.0.0.0 node apps/backend/server.js & + +echo "Starting dashboard on port $DASHBOARD_PORT..." +PORT=$DASHBOARD_PORT HOSTNAME=0.0.0.0 node apps/dashboard/server.js & + +# Wait for both to finish +wait -n diff --git a/docs/fern/docs/pages/others/self-host.mdx b/docs/fern/docs/pages/others/self-host.mdx index f21ed7b3b..02e415646 100644 --- a/docs/fern/docs/pages/others/self-host.mdx +++ b/docs/fern/docs/pages/others/self-host.mdx @@ -26,6 +26,24 @@ On a high level, Stack Auth is composed of the following services: - **Svix**: Used to send webhooks. Svix is open-source and can be self-hosted, but also offers a cloud hosted solution. More on Svix [here](https://svix.com) - **Email server**: We use [Inbucket](https://inbucket.org) as a local email server for development and a separate SMTP server for production. Any email service supporting SMTP will work. +## Run with Docker + +Stack Auth provides a pre-configured Docker image that bundles the dashboard and API backend into a single container. To complete the setup, you'll need to provide your own PostgreSQL database, and optionally configure an email server and Svix instance for webhooks. + +First, you need to get the [example environment file](https://github.com/stack-auth/stack/tree/main/docker/server/.env.example) and modify it to your needs. See the [full template here](https://github.com/stack-auth/stack/blob/dev/docker/server/.env). + +```sh +# Start a example Postgres database, don't use this setting in production +docker run -d --name db -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=stackframe -p 5432:5432 postgres:latest + +# Run the Docker container +docker run --env-file your-env-file.env -p 8101:8101 -p 8102:8102 stackauth/server:latest +``` + +Now you can open the dashboard at [http://localhost:8101](http://localhost:8101) and the API backend on port 8102. + +Now, login with your admin account on the dashboard and follow the [normal setup process](/getting-started/setup). Add `NEXT_PUBLIC_STACK_API_URL=https://your-backend-url.com` to your app's environment variables so that it connects to your API backend instead of the default Stack Auth API backend (https://api.stack-auth.com). + ## Local development ### Setup @@ -79,7 +97,7 @@ If you make changes to the Prisma schema, you need to run the following command pnpm run prisma migrate dev ``` -## Production deployment +## Run individual services ### Database, Svix, email @@ -94,7 +112,7 @@ git clone git@github.com:stack-auth/stack.git cd stack ``` -Set all the necessary environment variables (you can check out `apps/backend/.env`). Note that `STACK_BASE_URL` should be the URL of your deployed domain (e.g., https://your-backend-url.com). +Set all the necessary environment variables (you can check out `apps/backend/.env`). Note that `NEXT_PUBLIC_STACK_API_URL` should be the URL of your deployed domain (e.g., https://your-backend-url.com). Build and start the server: @@ -113,7 +131,7 @@ git clone git@github.com:stack-auth/stack.git cd stack ``` -Set all the necessary environment variables (you can check out `apps/dashboard/.env`). Note that `NEXT_PUBLIC_STACK_URL` should be the URL of your deployed backend (e.g., https://your-backend-url.com). +Set all the necessary environment variables (you can check out `apps/dashboard/.env`). Note that `NEXT_PUBLIC_STACK_API_URL` should be the URL of your deployed backend (e.g., https://your-backend-url.com). Build and start the server: @@ -143,4 +161,4 @@ To manage your dashboard configs with this account, manually go into the databas Go back to the dashboard, refresh the page, and you should see the "Stack Dashboard" project. We recommend disabling new user sign-ups to your internal project to avoid unauthorized account and project creations. -Now, create a new project for your app and follow the [normal setup process](/getting-started/setup). Add `NEXT_PUBLIC_STACK_URL=https://your-backend-url.com` to your app's environment variables so that it connects to your API backend instead of the default Stack Auth API backend (https://api.stack-auth.com). \ No newline at end of file +Now, create a new project for your app and follow the [normal setup process](/getting-started/setup). Add `NEXT_PUBLIC_STACK_API_URL=https://your-backend-url.com` to your app's environment variables so that it connects to your API backend instead of the default Stack Auth API backend (https://api.stack-auth.com). diff --git a/docs/fern/docs/pages/snippets/stack-app-constructor-options-before-ssk.mdx b/docs/fern/docs/pages/snippets/stack-app-constructor-options-before-ssk.mdx index e79dc7f6c..da5c27c60 100644 --- a/docs/fern/docs/pages/snippets/stack-app-constructor-options-before-ssk.mdx +++ b/docs/fern/docs/pages/snippets/stack-app-constructor-options-before-ssk.mdx @@ -21,7 +21,7 @@ - The base URL for Stack Auth's API. Only override this if you are self-hosting Stack Auth. Defaults to `https://api.stack-auth.com`, unless overridden by the `NEXT_PUBLIC_STACK_URL` environment variable. + The base URL for Stack Auth's API. Only override this if you are self-hosting Stack Auth. Defaults to `https://api.stack-auth.com`, unless overridden by the `NEXT_PUBLIC_STACK_API_URL` environment variable. The ID of the project that the app is associated with, as found on Stack Auth's dashboard. Defaults to the value of the `NEXT_PUBLIC_STACK_PROJECT_ID` environment variable. diff --git a/examples/cjs-test/.env.development b/examples/cjs-test/.env.development index 76ec17eaa..b3d3207d7 100644 --- a/examples/cjs-test/.env.development +++ b/examples/cjs-test/.env.development @@ -1,6 +1,6 @@ # Contains the credentials for the internal project of Stack's default development environment setup. # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com. -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only diff --git a/examples/demo/.env b/examples/demo/.env index 76163bb85..b86f8d97c 100644 --- a/examples/demo/.env +++ b/examples/demo/.env @@ -1,4 +1,4 @@ -NEXT_PUBLIC_STACK_URL=# enter your stack endpoint here, e.g. http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=# enter your stack endpoint here, e.g. http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=# enter your stack project id here NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# enter your stack publishable client key here STACK_SECRET_SERVER_KEY=# enter your stack secret server key here diff --git a/examples/demo/.env.development b/examples/demo/.env.development index 76ec17eaa..b3d3207d7 100644 --- a/examples/demo/.env.development +++ b/examples/demo/.env.development @@ -1,6 +1,6 @@ # Contains the credentials for the internal project of Stack's default development environment setup. # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com. -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only diff --git a/examples/docs-examples/.env b/examples/docs-examples/.env index 76163bb85..b86f8d97c 100644 --- a/examples/docs-examples/.env +++ b/examples/docs-examples/.env @@ -1,4 +1,4 @@ -NEXT_PUBLIC_STACK_URL=# enter your stack endpoint here, e.g. http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=# enter your stack endpoint here, e.g. http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=# enter your stack project id here NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# enter your stack publishable client key here STACK_SECRET_SERVER_KEY=# enter your stack secret server key here diff --git a/examples/docs-examples/.env.development b/examples/docs-examples/.env.development index 76ec17eaa..b3d3207d7 100644 --- a/examples/docs-examples/.env.development +++ b/examples/docs-examples/.env.development @@ -1,6 +1,6 @@ # Contains the credentials for the internal project of Stack's default development environment setup. # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com. -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only diff --git a/examples/e-commerce/.env.development b/examples/e-commerce/.env.development index 76ec17eaa..b3d3207d7 100644 --- a/examples/e-commerce/.env.development +++ b/examples/e-commerce/.env.development @@ -1,6 +1,6 @@ # Contains the credentials for the internal project of Stack's default development environment setup. # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com. -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only diff --git a/examples/middleware/.env b/examples/middleware/.env index 76163bb85..b86f8d97c 100644 --- a/examples/middleware/.env +++ b/examples/middleware/.env @@ -1,4 +1,4 @@ -NEXT_PUBLIC_STACK_URL=# enter your stack endpoint here, e.g. http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=# enter your stack endpoint here, e.g. http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=# enter your stack project id here NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# enter your stack publishable client key here STACK_SECRET_SERVER_KEY=# enter your stack secret server key here diff --git a/examples/middleware/.env.development b/examples/middleware/.env.development index 76ec17eaa..b3d3207d7 100644 --- a/examples/middleware/.env.development +++ b/examples/middleware/.env.development @@ -1,6 +1,6 @@ # Contains the credentials for the internal project of Stack's default development environment setup. # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com. -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only diff --git a/examples/partial-prerendering/.env b/examples/partial-prerendering/.env index 76163bb85..b86f8d97c 100644 --- a/examples/partial-prerendering/.env +++ b/examples/partial-prerendering/.env @@ -1,4 +1,4 @@ -NEXT_PUBLIC_STACK_URL=# enter your stack endpoint here, e.g. http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=# enter your stack endpoint here, e.g. http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=# enter your stack project id here NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# enter your stack publishable client key here STACK_SECRET_SERVER_KEY=# enter your stack secret server key here diff --git a/examples/partial-prerendering/.env.development b/examples/partial-prerendering/.env.development index 76ec17eaa..b3d3207d7 100644 --- a/examples/partial-prerendering/.env.development +++ b/examples/partial-prerendering/.env.development @@ -1,6 +1,6 @@ # Contains the credentials for the internal project of Stack's default development environment setup. # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com. -NEXT_PUBLIC_STACK_URL=http://localhost:8102 +NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_PROJECT_ID=internal NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only diff --git a/packages/stack-shared/src/utils/env.tsx b/packages/stack-shared/src/utils/env.tsx index 8f553a36f..dd51557fb 100644 --- a/packages/stack-shared/src/utils/env.tsx +++ b/packages/stack-shared/src/utils/env.tsx @@ -5,6 +5,11 @@ export function isBrowserLike() { return typeof window !== "undefined" && typeof document !== "undefined" && typeof document.createElement !== "undefined"; } +// newName: oldName +const ENV_VAR_RENAME: Record = { + NEXT_PUBLIC_STACK_API_URL: ['STACK_BASE_URL', 'NEXT_PUBLIC_STACK_URL'], +}; + /** * Returns the environment variable with the given name, returning the default (if given) or throwing an error (otherwise) if it's undefined or the empty string. */ @@ -24,7 +29,32 @@ export function getEnvVariable(name: string, defaultValue?: string | undefined): `); } - return ((process.env[name] || defaultValue) ?? throwErr(`Missing environment variable: ${name}`)) || (defaultValue ?? throwErr(`Empty environment variable: ${name}`)); + // throw error if the old name is used as the retrieve key + for (const [newName, oldNames] of Object.entries(ENV_VAR_RENAME)) { + if (oldNames.includes(name)) { + throwErr(`Environment variable ${name} has been renamed to ${newName}. Please update your configuration to use the new name.`); + } + } + + let value = process.env[name]; + + // check the key under the old name if the new name is not found + if (!value && ENV_VAR_RENAME[name] as any) { + for (const oldName of ENV_VAR_RENAME[name]) { + value = process.env[oldName]; + if (value) break; + } + } + + if (value === undefined) { + if (defaultValue !== undefined) { + value = defaultValue; + } else { + throwErr(`Missing environment variable: ${name}`); + } + } + + return value; } export function getNextRuntime() { diff --git a/packages/stack-shared/src/utils/jwt.tsx b/packages/stack-shared/src/utils/jwt.tsx index b529a9aaa..8e581433f 100644 --- a/packages/stack-shared/src/utils/jwt.tsx +++ b/packages/stack-shared/src/utils/jwt.tsx @@ -7,7 +7,7 @@ import { getEnvVariable } from "./env"; import { globalVar } from "./globals"; import { pick } from "./objects"; -const STACK_SERVER_SECRET = getEnvVariable("STACK_SERVER_SECRET"); +const STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? ""; try { jose.base64url.decode(STACK_SERVER_SECRET); } catch (e) { diff --git a/packages/stack/package.json b/packages/stack/package.json index fec9b20b1..5e1c8104d 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -18,7 +18,7 @@ "homepage": "https://stack-auth.com", "scripts": { "typecheck": "tsc --noEmit", - "build": "rimraf dist && pnpm run codegen && tsup-node", + "build": "rimraf dist && pnpm run css && tsup-node", "codegen": "pnpm run css && pnpm run quetzal", "codegen:watch": "concurrently -n \"css,quetzal\" -k \"pnpm run css:watch\" \"pnpm run quetzal:watch\"", "clean": "rimraf dist && rimraf node_modules", diff --git a/packages/stack/src/lib/stack-app.ts b/packages/stack/src/lib/stack-app.ts index 1ec346443..20e590b76 100644 --- a/packages/stack/src/lib/stack-app.ts +++ b/packages/stack/src/lib/stack-app.ts @@ -141,7 +141,7 @@ function getDefaultSuperSecretAdminKey() { } function getDefaultBaseUrl() { - return process.env.NEXT_PUBLIC_STACK_URL || defaultBaseUrl; + return process.env.NEXT_PUBLIC_STACK_API_URL || process.env.NEXT_PUBLIC_STACK_URL || defaultBaseUrl; } export type StackClientAppConstructorOptions = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc3959d6a..0fd266fce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,10 +124,10 @@ importers: version: 1.27.0 '@prisma/client': specifier: ^5.9.1 - version: 5.15.0(prisma@5.15.0) + version: 5.20.0(prisma@5.20.0) '@prisma/instrumentation': specifier: ^5.19.1 - version: 5.19.1 + version: 5.20.0 '@sentry/nextjs': specifier: ^8.40.0 version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.0)) @@ -229,11 +229,14 @@ importers: specifier: ^10.4.1 version: 10.4.1 prisma: - specifier: ^5.9.1 - version: 5.15.0 + specifier: ^5.20.0 + version: 5.20.0 rimraf: specifier: ^5.0.5 version: 5.0.7 + tsup: + specifier: ^8.3.0 + version: 8.3.5(@swc/core@1.3.101)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.15.5)(typescript@5.3.3)(yaml@2.4.5) tsx: specifier: ^4.7.2 version: 4.15.5 @@ -3617,8 +3620,8 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} - '@prisma/client@5.15.0': - resolution: {integrity: sha512-wPTeTjbd2Q0abOeffN7zCDCbkp9C9cF+e9HPiI64lmpehyq2TepgXE+sY7FXr7Rhbb21prLMnhXX27/E11V09w==} + '@prisma/client@5.20.0': + resolution: {integrity: sha512-CLv55ZuMuUawMsxoqxGtLT3bEZoa2W8L3Qnp6rDIFWy+ZBrUcOFKdoeGPSnbBqxc3SkdxJrF+D1veN/WNynZYA==} engines: {node: '>=16.13'} peerDependencies: prisma: '*' @@ -3626,24 +3629,27 @@ packages: prisma: optional: true - '@prisma/debug@5.15.0': - resolution: {integrity: sha512-QpEAOjieLPc/4sMny/WrWqtpIAmBYsgqwWlWwIctqZO0AbhQ9QcT6x2Ut3ojbDo/pFRCCA1Z1+xm2MUy7fAkZA==} + '@prisma/debug@5.20.0': + resolution: {integrity: sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ==} - '@prisma/engines-version@5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022': - resolution: {integrity: sha512-3BEgZ41Qb4oWHz9kZNofToRvNeS4LZYaT9pienR1gWkjhky6t6K1NyeWNBkqSj2llgraUNbgMOCQPY4f7Qp5wA==} + '@prisma/engines-version@5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284': + resolution: {integrity: sha512-Lg8AS5lpi0auZe2Mn4gjuCg081UZf88k3cn0RCwHgR+6cyHHpttPZBElJTHf83ZGsRNAmVCZCfUGA57WB4u4JA==} - '@prisma/engines@5.15.0': - resolution: {integrity: sha512-hXL5Sn9hh/ZpRKWiyPA5GbvF3laqBHKt6Vo70hYqqOhh5e0ZXDzHcdmxNvOefEFeqxra2DMz2hNbFoPvqrVe1w==} + '@prisma/engines@5.20.0': + resolution: {integrity: sha512-DtqkP+hcZvPEbj8t8dK5df2b7d3B8GNauKqaddRRqQBBlgkbdhJkxhoJTrOowlS3vaRt2iMCkU0+CSNn0KhqAQ==} - '@prisma/fetch-engine@5.15.0': - resolution: {integrity: sha512-z6AY5yyXxc20Klj7wwnfGP0iIUkVKzybqapT02zLYR/nf9ynaeN8bq73WRmi1TkLYn+DJ5Qy+JGu7hBf1pE78A==} + '@prisma/fetch-engine@5.20.0': + resolution: {integrity: sha512-JVcaPXC940wOGpCOwuqQRTz6I9SaBK0c1BAyC1pcz9xBi+dzFgUu3G/p9GV1FhFs9OKpfSpIhQfUJE9y00zhqw==} - '@prisma/get-platform@5.15.0': - resolution: {integrity: sha512-1GULDkW4+/VQb73vihxCBSc4Chc2x88MA+O40tcZFjmBzG4/fF44PaXFxUqKSFltxU9L9GIMLhh0Gfkk/pUbtg==} + '@prisma/get-platform@5.20.0': + resolution: {integrity: sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA==} '@prisma/instrumentation@5.19.1': resolution: {integrity: sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w==} + '@prisma/instrumentation@5.20.0': + resolution: {integrity: sha512-SHaI+3F4mu879fuD19qXKZGv+scgUOnFjX29/KFVwURpjz7trq3yfz91rwZaFuN4IAqUKJNcqEt4UOzoFHWklw==} + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -9286,8 +9292,8 @@ packages: peerDependencies: react: '>=16.0.0' - prisma@5.15.0: - resolution: {integrity: sha512-JA81ACQSCi3a7NUOgonOIkdx8PAVkO+HbUOxmd00Yb8DgIIEpr2V9+Qe/j6MLxIgWtE/OtVQ54rVjfYRbZsCfw==} + prisma@5.20.0: + resolution: {integrity: sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig==} engines: {node: '>=16.13'} hasBin: true @@ -11174,7 +11180,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-plugin-utils': 7.24.8 - debug: 4.3.5 + debug: 4.3.7 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -13133,7 +13139,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.52.1 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.0 + import-in-the-middle: 1.11.2 require-in-the-middle: 7.4.0 semver: 7.6.3 shimmer: 1.2.1 @@ -13275,30 +13281,30 @@ snapshots: '@polka/url@1.0.0-next.25': {} - '@prisma/client@5.15.0(prisma@5.15.0)': + '@prisma/client@5.20.0(prisma@5.20.0)': optionalDependencies: - prisma: 5.15.0 + prisma: 5.20.0 - '@prisma/debug@5.15.0': {} + '@prisma/debug@5.20.0': {} - '@prisma/engines-version@5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022': {} + '@prisma/engines-version@5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284': {} - '@prisma/engines@5.15.0': + '@prisma/engines@5.20.0': dependencies: - '@prisma/debug': 5.15.0 - '@prisma/engines-version': 5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022 - '@prisma/fetch-engine': 5.15.0 - '@prisma/get-platform': 5.15.0 + '@prisma/debug': 5.20.0 + '@prisma/engines-version': 5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284 + '@prisma/fetch-engine': 5.20.0 + '@prisma/get-platform': 5.20.0 - '@prisma/fetch-engine@5.15.0': + '@prisma/fetch-engine@5.20.0': dependencies: - '@prisma/debug': 5.15.0 - '@prisma/engines-version': 5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022 - '@prisma/get-platform': 5.15.0 + '@prisma/debug': 5.20.0 + '@prisma/engines-version': 5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284 + '@prisma/get-platform': 5.20.0 - '@prisma/get-platform@5.15.0': + '@prisma/get-platform@5.20.0': dependencies: - '@prisma/debug': 5.15.0 + '@prisma/debug': 5.20.0 '@prisma/instrumentation@5.19.1': dependencies: @@ -13308,6 +13314,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@prisma/instrumentation@5.20.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -19191,7 +19205,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.5 + debug: 4.3.7 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -20013,6 +20027,15 @@ snapshots: optionalDependencies: postcss: 8.4.47 + postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.47)(tsx@4.15.5)(yaml@2.4.5): + dependencies: + lilconfig: 3.1.2 + optionalDependencies: + jiti: 1.21.6 + postcss: 8.4.47 + tsx: 4.15.5 + yaml: 2.4.5 + postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.47)(tsx@4.16.2)(yaml@2.6.0): dependencies: lilconfig: 3.1.2 @@ -20130,9 +20153,11 @@ snapshots: clsx: 1.2.1 react: 18.2.0 - prisma@5.15.0: + prisma@5.20.0: dependencies: - '@prisma/engines': 5.15.0 + '@prisma/engines': 5.20.0 + optionalDependencies: + fsevents: 2.3.3 prismjs@1.29.0: {} @@ -21560,6 +21585,34 @@ snapshots: - supports-color - ts-node + tsup@8.3.5(@swc/core@1.3.101)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.15.5)(typescript@5.3.3)(yaml@2.4.5): + dependencies: + bundle-require: 5.0.0(esbuild@0.24.0) + cac: 6.7.14 + chokidar: 4.0.1 + consola: 3.2.3 + debug: 4.3.7 + esbuild: 0.24.0 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.47)(tsx@4.15.5)(yaml@2.4.5) + resolve-from: 5.0.0 + rollup: 4.24.4 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.1 + tinyglobby: 0.2.10 + tree-kill: 1.2.2 + optionalDependencies: + '@swc/core': 1.3.101(@swc/helpers@0.5.13) + postcss: 8.4.47 + typescript: 5.3.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsup@8.3.5(@swc/core@1.3.101)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.16.2)(typescript@5.3.3)(yaml@2.6.0): dependencies: bundle-require: 5.0.0(esbuild@0.24.0) diff --git a/turbo.json b/turbo.json index 704440755..e88236671 100644 --- a/turbo.json +++ b/turbo.json @@ -34,6 +34,29 @@ ], "outputLogs": "new-only" }, + "docker-build": { + "inputs": [ + "$TURBO_DEFAULT$", + ".env", + ".env.local", + ".env.development", + ".env.development.local", + ".env.production", + ".env.production.local" + ], + "dependsOn": [ + "^build" + ], + "outputs": [ + ".next", + "dist/**", + "lib/**", + "*.js", + "*.d.ts", + "*.d.ts.map" + ], + "outputLogs": "new-only" + }, "@stackframe/docs#build": { "dependsOn": [ "@stackframe/stack-backend#build"