From f0c5a21ab5b65a9bfcc9a6af4b60c589540cfacd Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 23 Feb 2023 20:39:23 -0600 Subject: [PATCH] Essential environment variable parsing ^& validation --- src/env/schema.mjs | 18 ++++++++++++++++++ src/env/server.mjs | 40 ++++++++++++++++++++++++++++++++++++++++ src/env/util.mjs | 10 ++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/env/schema.mjs create mode 100644 src/env/server.mjs create mode 100644 src/env/util.mjs diff --git a/src/env/schema.mjs b/src/env/schema.mjs new file mode 100644 index 0000000..4cbffaf --- /dev/null +++ b/src/env/schema.mjs @@ -0,0 +1,18 @@ +// @ts-check +import { z } from "zod"; + +/** + * Specify your server-side environment variables schema here. + * This way you can ensure the app isn't built with invalid env vars. + */ +export const serverSchema = z.object({ + API_KEY: z.string(), + CRONITOR_ACCOUNT_ID: z.string(), + CRONITOR_JOB_ID: z.string(), + LIFE360_USERNAME: z.string(), + LIFE360_PASSWORD: z.string(), + LIFE360_MEMBER_ID: z.string(), + MAX_DISTANCE: z.coerce.number().positive(), + CENTER_LATITUDE: z.coerce.number().min(-90).max(90), + CENTER_LONGITUDE: z.coerce.number().min(-180).max(180), +}); diff --git a/src/env/server.mjs b/src/env/server.mjs new file mode 100644 index 0000000..6f7d50b --- /dev/null +++ b/src/env/server.mjs @@ -0,0 +1,40 @@ +// @ts-check +/** + * This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars. + * It has to be a `.mjs`-file to be imported there. + */ +import { serverSchema } from "./schema.mjs"; +import { formatErrors } from "./util.mjs"; + +const _serverEnv = serverSchema.safeParse({ + API_KEY: process.env.API_KEY, + CRONITOR_ACCOUNT_ID: process.env.CRONITOR_ACCOUNT_ID, + CRONITOR_JOB_ID: process.env.CRONITOR_JOB_ID, + LIFE360_USERNAME: process.env.LIFE360_USERNAME, + LIFE360_PASSWORD: process.env.LIFE360_PASSWORD, + LIFE360_MEMBER_ID: process.env.LIFE360_MEMBER_ID, + MAX_DISTANCE: process.env.MAX_DISTANCE, + CENTER_LATITUDE: process.env.CENTER_LATITUDE, + CENTER_LONGITUDE: process.env.CENTER_LONGITUDE, +}); + +if (_serverEnv.success === false) { + console.error( + "❌ Invalid environment variables:\n", + ...formatErrors(_serverEnv.error.format()) + ); + throw new Error("Invalid environment variables"); +} + +/** + * Validate that server-side environment variables are not exposed to the client. + */ +for (let key of Object.keys(_serverEnv.data)) { + if (key.startsWith("NEXT_PUBLIC_")) { + console.warn("❌ You are exposing a server-side env-variable:", key); + + throw new Error("You are exposing a server-side env-variable"); + } +} + +export const env = { ..._serverEnv.data }; diff --git a/src/env/util.mjs b/src/env/util.mjs new file mode 100644 index 0000000..d3238a5 --- /dev/null +++ b/src/env/util.mjs @@ -0,0 +1,10 @@ +export const formatErrors = ( + /** @type {import('zod').ZodFormattedError,string>} */ + errors, + ) => + Object.entries(errors) + .map(([name, value]) => { + if (value && "_errors" in value) + return `${name}: ${value._errors.join(", ")}\n`; + }) + .filter(Boolean); \ No newline at end of file