diff --git a/apps/server/.env b/apps/server/.env index 3d8b381ee..716d12b75 100644 --- a/apps/server/.env +++ b/apps/server/.env @@ -1,16 +1,18 @@ DATABASE_URL="postgresql://postgres:example-PAssw0rd-xHjDYR.b7N@db:5432/postgres" REDIS_URL="redis://localhost:6379" -AUTH_GOOGLE_CLIENT_ID="change-me" -AUTH_GOOGLE_CLIENT_SECRET="change-me" +BETTER_AUTH_SECRET="" -AUTH_GITHUB_CLIENT_ID="change-me" -AUTH_GITHUB_CLIENT_SECRET="change-me" +AUTH_GOOGLE_CLIENT_ID="" +AUTH_GOOGLE_CLIENT_SECRET="" -STRIPE_SECRET_KEY="change-me" -STRIPE_WEBHOOK_SECRET="change-me" +AUTH_GITHUB_CLIENT_ID="" +AUTH_GITHUB_CLIENT_SECRET="" -CLIENT_URL="change-me" -API_SERVER_URL="change-me" +STRIPE_SECRET_KEY="" +STRIPE_WEBHOOK_SECRET="" + +CLIENT_URL="" +API_SERVER_URL="" # OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" diff --git a/apps/server/src/libs/auth.ts b/apps/server/src/libs/auth.ts index 3284696fe..b71472aa7 100644 --- a/apps/server/src/libs/auth.ts +++ b/apps/server/src/libs/auth.ts @@ -296,6 +296,8 @@ export async function seedTrustedClients(db: Database, env: Env): Promise export function createAuth(db: Database, env: Env, metrics?: AuthMetrics | null) { return betterAuth({ + secret: env.BETTER_AUTH_SECRET, + database: drizzleAdapter(db, { provider: 'pg', schema: { diff --git a/apps/server/src/libs/env.test.ts b/apps/server/src/libs/env.test.ts index a04b3de48..edd641c60 100644 --- a/apps/server/src/libs/env.test.ts +++ b/apps/server/src/libs/env.test.ts @@ -7,6 +7,7 @@ describe('parseEnv', () => { const env = parseEnv({ DATABASE_URL: 'postgres://example', REDIS_URL: 'redis://example', + BETTER_AUTH_SECRET: 'test-secret-at-least-32-characters-long', AUTH_GOOGLE_CLIENT_ID: 'google-client', AUTH_GOOGLE_CLIENT_SECRET: 'google-secret', AUTH_GITHUB_CLIENT_ID: 'github-client', diff --git a/apps/server/src/libs/env.ts b/apps/server/src/libs/env.ts index 2d827154e..5749fbff0 100644 --- a/apps/server/src/libs/env.ts +++ b/apps/server/src/libs/env.ts @@ -43,6 +43,11 @@ const EnvSchema = object({ DATABASE_URL: pipe(string(), nonEmpty('DATABASE_URL is required')), REDIS_URL: pipe(string(), nonEmpty('REDIS_URL is required')), + // Required: signs session cookies and encrypts JWKS private keys in DB. + // Must be stable across deploys/instances, otherwise every redeploy invalidates + // all existing sessions and forces users to re-login. + BETTER_AUTH_SECRET: pipe(string(), nonEmpty('BETTER_AUTH_SECRET is required')), + AUTH_GOOGLE_CLIENT_ID: pipe(string(), nonEmpty('AUTH_GOOGLE_CLIENT_ID is required')), AUTH_GOOGLE_CLIENT_SECRET: pipe(string(), nonEmpty('AUTH_GOOGLE_CLIENT_SECRET is required')), AUTH_GITHUB_CLIENT_ID: pipe(string(), nonEmpty('AUTH_GITHUB_CLIENT_ID is required')),