Files
Randify.pro/src/lib/auth/env.ts
T
emilandClaude Opus 4.7 4b78e9edad fix(auth): defer env validation to first access so build works without secrets
`validateAuthEnv()` was running at module-load time, which fired during
`astro build` while constructing the route manifest — before any actual
request needed the secrets. CI build failed even though build-time code
doesn't use JWT_SECRET / OAuth secrets.

- src/lib/auth/env.ts: wrap authEnv in a Proxy that runs validation
  on first property read; cache the validated object after.
- src/lib/auth/jwt.ts: defer `new TextEncoder().encode(...)` of the
  secret behind a memoised getSecret() helper.
- src/lib/auth/oauth.ts: same for the JWT secret; vkOAuthConfig and
  yandexOAuthConfig switched to getter-based properties so credential
  access is also lazy.
- src/lib/auth/auth.test.ts: 3 tests previously asserted "throws at
  module load"; updated to assert "throws on first access" — semantic
  guarantee (invalid env throws) is preserved.

Runtime fail-fast is intact: any auth route reading authEnv.X will
throw with the same descriptive Zod error if a var is missing. Build
just no longer crashes when secrets aren't in env (e.g. CI deploy
pipeline running tsc/lint/build without secrets).

Verified: npm run build succeeds with no auth env vars set; tsc 0,
lint 0, vitest 339/339.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:45:03 +03:00

40 lines
1.3 KiB
TypeScript

import { z } from 'zod';
const envSchema = z.object({
JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'),
VK_CLIENT_ID: z.string().min(1, 'VK_CLIENT_ID is required'),
VK_CLIENT_SECRET: z.string().min(1, 'VK_CLIENT_SECRET is required'),
YANDEX_CLIENT_ID: z.string().min(1, 'YANDEX_CLIENT_ID is required'),
YANDEX_CLIENT_SECRET: z.string().min(1, 'YANDEX_CLIENT_SECRET is required'),
PUBLIC_APP_URL: z.string().url('PUBLIC_APP_URL must be a valid URL'),
});
type AuthEnv = z.infer<typeof envSchema>;
let cached: AuthEnv | null = null;
function loadAuthEnv(): AuthEnv {
if (cached) return cached;
cached = envSchema.parse({
JWT_SECRET: process.env.JWT_SECRET,
VK_CLIENT_ID: process.env.VK_CLIENT_ID,
VK_CLIENT_SECRET: process.env.VK_CLIENT_SECRET,
YANDEX_CLIENT_ID: process.env.YANDEX_CLIENT_ID,
YANDEX_CLIENT_SECRET: process.env.YANDEX_CLIENT_SECRET,
PUBLIC_APP_URL: process.env.PUBLIC_APP_URL,
});
return cached;
}
// Lazy proxy: validation fires only when a key is actually read at runtime,
// not when the module is first imported (which happens during `astro build`).
export const authEnv = new Proxy({} as AuthEnv, {
get(_target, key: string) {
return loadAuthEnv()[key as keyof AuthEnv];
},
}) as AuthEnv;
export function requireEnv(name: keyof AuthEnv): string {
return loadAuthEnv()[name];
}