Files
Vector/packages/shared/src/env.ts
T
josh 23bd0f0c6a
CI / Playwright (smoke) (push) Has been skipped
CI / Lint · Typecheck · Test · Build (push) Successful in 44s
CI / Build & push images (push) Successful in 1m8s
fix(deploy): auth/CSRF cookies dropped on plain-HTTP prod
Every cookie was flagged Secure whenever NODE_ENV=production. Over
plain HTTP (single-host compose deploy without TLS) browsers silently
discard Secure cookies, so the access token, refresh token, and CSRF
cookie all vanished after login — producing 401 Unauthorized on every
GET and 403 "CSRF token missing or invalid" on every mutation.

Add COOKIE_SECURE to ApiEnv: optional boolean, falls back to
NODE_ENV === 'production' when unset. Controllers and middleware now
read env.COOKIE_SECURE instead of the NODE_ENV shortcut. The compose
file sets it to false by default with a comment to flip once TLS is in
front; HTTPS deployments can override via .env or drop the override to
pick up the secure default.
2026-04-17 08:31:12 -04:00

23 lines
976 B
TypeScript

import { z } from 'zod';
const defaultSecret = /change[-_ ]?me/i;
export const ApiEnv = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().int().positive().default(3001),
DATABASE_URL: z.string().min(1, 'DATABASE_URL is required'),
JWT_SECRET: z
.string()
.min(32, 'JWT_SECRET must be at least 32 characters')
.refine((v) => !defaultSecret.test(v), {
message: 'JWT_SECRET still matches the default placeholder — generate a real secret',
}),
CLIENT_ORIGIN: z.string().url().default('http://localhost:5173'),
// Whether to mark auth + CSRF cookies Secure. Must be false for plain-HTTP
// deployments (browsers silently drop Secure cookies over http://). Leave
// unset to fall back to NODE_ENV === 'production'.
COOKIE_SECURE: z
.preprocess((v) => (typeof v === 'string' ? v === 'true' : v), z.boolean().optional()),
});
export type ApiEnv = z.infer<typeof ApiEnv>;