Skip to content

Commit

Permalink
Validate env vars with Zod (#2362)
Browse files Browse the repository at this point in the history
  • Loading branch information
infomiho authored Jan 15, 2025
1 parent 8a1a38c commit 8f3384e
Show file tree
Hide file tree
Showing 352 changed files with 5,145 additions and 3,918 deletions.
8 changes: 4 additions & 4 deletions waspc/data/Generator/templates/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ WORKDIR /app
# Copying the top level 'node_modules' because it contains the Prisma packages
# necessary for migrating the database.
COPY --from=server-builder /app/node_modules ./node_modules
# Copying the SDK because 'validate-env.mjs' executes independent of the bundle
# Copying the SDK because the server bundle doesn't bundle the SDK
# and references the 'wasp' package.
COPY --from=server-builder /app/.wasp/out/sdk .wasp/out/sdk
# Copying 'server/node_modules' because 'validate-env.mjs' executes independent
# of the bundle and references the dotenv package.
# Copying 'server/node_modules' because we require dotenv package to
# load environment variables
# TODO: replace dotenv with native Node.js environment variable loading
COPY --from=server-builder /app/.wasp/build/server/node_modules .wasp/build/server/node_modules
COPY --from=server-builder /app/.wasp/build/server/bundle .wasp/build/server/bundle
COPY --from=server-builder /app/.wasp/build/server/package*.json .wasp/build/server/
COPY --from=server-builder /app/.wasp/build/server/scripts .wasp/build/server/scripts
COPY db/ .wasp/build/db/
EXPOSE ${PORT}
WORKDIR /app/.wasp/build/server
Expand Down
5 changes: 2 additions & 3 deletions waspc/data/Generator/templates/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
{=& depsChunk =},
{=& devDepsChunk =},
"scripts": {
"start": "npm run validate-env && vite",
"build": "npm run validate-env && tsc && vite build",
"validate-env": "node -r dotenv/config ./scripts/validate-env.mjs"
"start": "vite",
"build": "tsc && vite build"
},
"engineStrict": true,
"engines": {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export function DefaultRootErrorBoundary() {
console.error(error)
return (
<FullPageWrapper>
<div>There was an error rendering this page. Check the browser console for more information.</div>
<div>
There was an error rendering this page. Check the browser console for
more information.
</div>
</FullPageWrapper>
)
}
4 changes: 2 additions & 2 deletions waspc/data/Generator/templates/sdk/wasp/client/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{={= =}=}}
import { stripTrailingSlash } from '../universal/url.js'
import { env } from './env.js'

const apiUrl = stripTrailingSlash(import.meta.env.REACT_APP_API_URL) || '{= defaultServerUrl =}';
const apiUrl = stripTrailingSlash(env.REACT_APP_API_URL)

// PUBLIC API
export type ClientConfig = {
Expand Down
25 changes: 25 additions & 0 deletions waspc/data/Generator/templates/sdk/wasp/client/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{={= =}=}}
import * as z from 'zod'

import { ensureEnvSchema } from '../env/validation.js'

{=# envValidationSchema.isDefined =}
{=& envValidationSchema.importStatement =}
const userClientEnvSchema = {= envValidationSchema.importIdentifier =}
{=/ envValidationSchema.isDefined =}
{=^ envValidationSchema.isDefined =}
const userClientEnvSchema = z.object({})
{=/ envValidationSchema.isDefined =}

const waspClientEnvSchema = z.object({
REACT_APP_API_URL: z
.string({
required_error: 'REACT_APP_API_URL is required',
})
.default('{= defaultServerUrl =}')
})

const clientEnvSchema = userClientEnvSchema.merge(waspClientEnvSchema)

// PUBLIC API
export const env = ensureEnvSchema(import.meta.env, clientEnvSchema)
5 changes: 4 additions & 1 deletion waspc/data/Generator/templates/sdk/wasp/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ export enum HttpMethod {
export type Route = { method: HttpMethod; path: string }

// PUBLIC API
export { config, ClientConfig } from './config'
export { config, ClientConfig } from './config.js'

// PUBLIC API
export { env } from './env.js'
11 changes: 11 additions & 0 deletions waspc/data/Generator/templates/sdk/wasp/env/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as z from 'zod'

// PUBLIC API
export function defineEnvValidationSchema<Schema extends z.ZodObject<any>>(
schema: Schema,
): Schema {
return schema
}

// PRIVATE API (SDK, Vite config)
export { ensureEnvSchema } from './validation.js'
25 changes: 25 additions & 0 deletions waspc/data/Generator/templates/sdk/wasp/env/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as z from 'zod'

const redColor = '\x1b[31m'

export function ensureEnvSchema<Schema extends z.ZodTypeAny>(
data: unknown,
schema: Schema
): z.infer<Schema> {
try {
return schema.parse(data)
} catch (e) {
if (e instanceof z.ZodError) {
const errorOutput = ['', '══ Env vars validation failed ══', '']
for (const error of e.errors) {
errorOutput.push(` - ${error.message}`)
}
errorOutput.push('')
errorOutput.push('════════════════════════════════')
console.error(redColor, errorOutput.join('\n'))
throw new Error('Error parsing environment variables')
} else {
throw e
}
}
}
1 change: 1 addition & 0 deletions waspc/data/Generator/templates/sdk/wasp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"./client/test": "./dist/client/test/index.js",
"./client": "./dist/client/index.js",
"./dev": "./dist/dev/index.js",
"./env": "./dist/env/index.js",

{=! todo(filip): Fixes below are for type errors in 0.13.1, remove ASAP =}
{=! Used by our code (SDK for full-stack type safety), uncodumented (but accessible) for users. =}
Expand Down
11 changes: 9 additions & 2 deletions waspc/data/Generator/templates/sdk/wasp/server/HttpError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ export class HttpError extends Error {
public statusCode: number
public data: unknown

constructor (statusCode: number, message?: string, data?: Record<string, unknown>, options?: ErrorOptions) {
constructor(
statusCode: number,
message?: string,
data?: Record<string, unknown>,
options?: ErrorOptions
) {
super(message, options)

if (Error.captureStackTrace) {
Expand All @@ -11,7 +16,9 @@ export class HttpError extends Error {

this.name = this.constructor.name

if (!(Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600)) {
if (
!(Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600)
) {
throw new Error('statusCode has to be integer in range [400, 600).')
}
this.statusCode = statusCode
Expand Down
15 changes: 0 additions & 15 deletions waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/env.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import { OAuth2Provider, OAuth2ProviderWithPKCE } from "arctic";

export function defineProvider<
OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE,
Env extends Record<string, string>
OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE
>({
id,
displayName,
env,
oAuthClient,
}: {
id: string;
displayName: string;
env: Env;
oAuthClient: OAuthClient;
}) {
return {
id,
displayName,
env,
oAuthClient,
};
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
{{={= =}=}}
import { Discord } from "arctic";
import { Discord } from 'arctic';

import { defineProvider } from "../provider.js";
import { ensureEnvVarsForProvider } from "../env.js";
import { getRedirectUriForCallback } from "../redirect.js";
import { defineProvider } from '../provider.js';
import { getRedirectUriForCallback } from '../redirect.js';
import { env } from '../../../env.js';

const id = "{= providerId =}";
const displayName = "{= displayName =}";

const env = ensureEnvVarsForProvider(
["DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET"],
displayName
);
const id = '{= providerId =}';
const displayName = '{= displayName =}';

const oAuthClient = new Discord(
env.DISCORD_CLIENT_ID,
Expand All @@ -23,6 +18,5 @@ const oAuthClient = new Discord(
export const discord = defineProvider({
id,
displayName,
env,
oAuthClient,
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
{{={= =}=}}
import { GitHub } from "arctic";
import { GitHub } from 'arctic';

import { ensureEnvVarsForProvider } from "../env.js";
import { defineProvider } from "../provider.js";
import { defineProvider } from '../provider.js';
import { env } from '../../../env.js';

const id = "{= providerId =}";
const displayName = "{= displayName =}";

const env = ensureEnvVarsForProvider(
["GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET"],
displayName
);
const id = '{= providerId =}';
const displayName = '{= displayName =}';

const oAuthClient = new GitHub(
env.GITHUB_CLIENT_ID,
Expand All @@ -21,6 +16,5 @@ const oAuthClient = new GitHub(
export const github = defineProvider({
id,
displayName,
env,
oAuthClient,
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
{{={= =}=}}
import { Google } from "arctic";
import { Google } from 'arctic';

import { ensureEnvVarsForProvider } from "../env.js";
import { getRedirectUriForCallback } from "../redirect.js";
import { defineProvider } from "../provider.js";
import { getRedirectUriForCallback } from '../redirect.js';
import { defineProvider } from '../provider.js';
import { env } from '../../../env.js';

const id = "{= providerId =}";
const displayName = "{= displayName =}";

const env = ensureEnvVarsForProvider(
["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"],
displayName,
);
const id = '{= providerId =}';
const displayName = '{= displayName =}';

const oAuthClient = new Google(
env.GOOGLE_CLIENT_ID,
Expand All @@ -23,6 +18,5 @@ const oAuthClient = new Google(
export const google = defineProvider({
id,
displayName,
env,
oAuthClient,
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
{{={= =}=}}
import { Keycloak } from "arctic";
import { Keycloak } from 'arctic';

import { ensureEnvVarsForProvider } from "../env.js";
import { getRedirectUriForCallback } from "../redirect.js";
import { defineProvider } from "../provider.js";
import { getRedirectUriForCallback } from '../redirect.js';
import { defineProvider } from '../provider.js';
import { env } from '../../../env.js';

const id = "{= providerId =}";
const displayName = "{= displayName =}";

const env = ensureEnvVarsForProvider(
["KEYCLOAK_REALM_URL", "KEYCLOAK_CLIENT_ID", "KEYCLOAK_CLIENT_SECRET"],
displayName,
);
const id = '{= providerId =}';
const displayName = '{= displayName =}';

const oAuthClient = new Keycloak(
env.KEYCLOAK_REALM_URL,
Expand All @@ -24,6 +19,5 @@ const oAuthClient = new Keycloak(
export const keycloak = defineProvider({
id,
displayName,
env,
oAuthClient,
});
Loading

0 comments on commit 8f3384e

Please sign in to comment.