Skip to content

Commit 999fc5c

Browse files
authored
feat: validate server and client env variables (#149)
1 parent 1e7f453 commit 999fc5c

File tree

7 files changed

+126
-7
lines changed

7 files changed

+126
-7
lines changed

.env.example

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
ENCRYPTION_KEY=""
1+
ENCRYPTION_KEY="" # generate using `openssl rand -base64 32`
22
DATABASE_URL="postgresql://easy-invoice:easy-invoice@localhost:7406/easy-invoice"
33
GOOGLE_CLIENT_ID=""
44
GOOGLE_CLIENT_SECRET=""
55
GOOGLE_REDIRECT_URI="http://localhost:3000/login/google/callback"
66
CURRENT_ENCRYPTION_VERSION="v1"
77
REQUEST_API_URL=""
8-
REQUEST_API_KEY=""
8+
REQUEST_API_KEY="" # get it on our API portal
99
NEXT_PUBLIC_REOWN_PROJECT_ID=""
10-
WEBHOOK_SECRET=""
10+
WEBHOOK_SECRET="" # get it on our API portal
1111
NEXT_PUBLIC_API_TERMS_CONDITIONS="https://request.network/api-terms"
1212

13-
FEE_PERCENTAGE_FOR_PAYMENT=""
14-
FEE_ADDRESS_FOR_PAYMENT=""
13+
FEE_PERCENTAGE_FOR_PAYMENT=
14+
FEE_ADDRESS_FOR_PAYMENT=
1515
REDIS_URL=redis://localhost:7379
1616

1717
# Optional
1818
# NEXT_PUBLIC_GTM_ID=""
1919
# NEXT_PUBLIC_CRYPTO_TO_FIAT_TRUSTED_ORIGINS=""
20-
INVOICE_PROCESSING_TTL=""
20+
INVOICE_PROCESSING_TTL="60"

.github/workflows/build-and-lint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ jobs:
7171
name: Build
7272
needs: install
7373
runs-on: ubuntu-latest
74+
env:
75+
SKIP_ENV_VALIDATION: "true"
7476
steps:
7577
- uses: actions/checkout@v4
7678

next.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import { validateClientEnv } from "./src/lib/env/client.js";
2+
import { validateServerEnv } from "./src/lib/env/server.js";
3+
4+
validateServerEnv();
5+
validateClientEnv();
16
/** @type {import('next').NextConfig} */
27
const nextConfig = {};
38

src/lib/env/client.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { z } from "zod";
2+
import { shouldSkipValidation } from "./helpers.js";
3+
4+
const clientEnvSchema = z.object({
5+
NEXT_PUBLIC_REOWN_PROJECT_ID: z
6+
.string()
7+
.min(1, "NEXT_PUBLIC_REOWN_PROJECT_ID is required"),
8+
NEXT_PUBLIC_GTM_ID: z.string().min(1, "NEXT_PUBLIC_GTM_ID is required"),
9+
NEXT_PUBLIC_DEMO_MEETING: z.string().url().optional(),
10+
NEXT_PUBLIC_API_TERMS_CONDITIONS: z.string().url().optional(),
11+
NEXT_PUBLIC_CRYPTO_TO_FIAT_TRUSTED_ORIGINS: z.string().optional(),
12+
});
13+
14+
export function validateClientEnv() {
15+
if (shouldSkipValidation()) {
16+
console.warn("⚠️ Skipping client environment variable validation.");
17+
return;
18+
}
19+
20+
const env = {
21+
NEXT_PUBLIC_REOWN_PROJECT_ID: process.env.NEXT_PUBLIC_REOWN_PROJECT_ID,
22+
NEXT_PUBLIC_GTM_ID: process.env.NEXT_PUBLIC_GTM_ID,
23+
NEXT_PUBLIC_DEMO_MEETING: process.env.NEXT_PUBLIC_DEMO_MEETING,
24+
NEXT_PUBLIC_API_TERMS_CONDITIONS:
25+
process.env.NEXT_PUBLIC_API_TERMS_CONDITIONS,
26+
NEXT_PUBLIC_CRYPTO_TO_FIAT_TRUSTED_ORIGINS:
27+
process.env.NEXT_PUBLIC_CRYPTO_TO_FIAT_TRUSTED_ORIGINS,
28+
};
29+
30+
const result = clientEnvSchema.safeParse(env);
31+
32+
if (!result.success) {
33+
console.error(
34+
"❌ Invalid client environment variables:",
35+
result.error.flatten().fieldErrors,
36+
);
37+
throw new Error("Invalid client environment variables");
38+
}
39+
}

src/lib/env/helpers.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function shouldSkipValidation() {
2+
return process.env.SKIP_ENV_VALIDATION === "true";
3+
}

src/lib/env/server.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { z } from "zod";
2+
import { shouldSkipValidation } from "./helpers.js";
3+
4+
const serverEnvSchema = z.object({
5+
DATABASE_URL: z.string().min(1, "DATABASE_URL is required"),
6+
GOOGLE_CLIENT_ID: z.string().min(1, "GOOGLE_CLIENT_ID is required"),
7+
GOOGLE_CLIENT_SECRET: z.string().min(1, "GOOGLE_CLIENT_SECRET is required"),
8+
REQUEST_API_URL: z.string().url("REQUEST_API_URL must be a valid URL"),
9+
REQUEST_API_KEY: z.string().min(1, "REQUEST_API_KEY is required"),
10+
WEBHOOK_SECRET: z.string().min(1, "WEBHOOK_SECRET is required"),
11+
GOOGLE_REDIRECT_URI: z
12+
.string()
13+
.url("GOOGLE_REDIRECT_URI must be a valid URL"),
14+
ENCRYPTION_KEY: z.string().min(1, "ENCRYPTION_KEY is required"),
15+
CURRENT_ENCRYPTION_VERSION: z.union([z.literal("v1")], {
16+
errorMap: () => ({
17+
message: 'CURRENT_ENCRYPTION_VERSION must be "v1"',
18+
}),
19+
}),
20+
FEE_PERCENTAGE_FOR_PAYMENT: z.coerce.string().min(1).max(100).optional(),
21+
FEE_ADDRESS_FOR_PAYMENT: z
22+
.string()
23+
.trim()
24+
.min(1, "FEE_ADDRESS_FOR_PAYMENT cannot be empty")
25+
.optional(),
26+
REDIS_URL: z.string().trim().url().optional(),
27+
INVOICE_PROCESSING_TTL: z.coerce
28+
.number()
29+
.int()
30+
.positive("INVOICE_PROCESSING_TTL must be a positive integer"),
31+
});
32+
33+
export function validateServerEnv() {
34+
if (shouldSkipValidation()) {
35+
console.warn("⚠️ Skipping server environment variable validation.");
36+
return;
37+
}
38+
39+
const env = {
40+
DATABASE_URL: process.env.DATABASE_URL,
41+
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
42+
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
43+
REQUEST_API_URL: process.env.REQUEST_API_URL,
44+
REQUEST_API_KEY: process.env.REQUEST_API_KEY,
45+
WEBHOOK_SECRET: process.env.WEBHOOK_SECRET,
46+
GOOGLE_REDIRECT_URI: process.env.GOOGLE_REDIRECT_URI,
47+
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
48+
CURRENT_ENCRYPTION_VERSION: process.env.CURRENT_ENCRYPTION_VERSION,
49+
FEE_PERCENTAGE_FOR_PAYMENT: process.env.FEE_PERCENTAGE_FOR_PAYMENT,
50+
FEE_ADDRESS_FOR_PAYMENT: process.env.FEE_ADDRESS_FOR_PAYMENT,
51+
REDIS_URL: process.env.REDIS_URL,
52+
INVOICE_PROCESSING_TTL: process.env.INVOICE_PROCESSING_TTL,
53+
};
54+
55+
const result = serverEnvSchema.safeParse(env);
56+
57+
if (!result.success) {
58+
console.error(
59+
"❌ Invalid server environment variables:",
60+
result.error.flatten().fieldErrors,
61+
);
62+
throw new Error("Invalid server environment variables");
63+
}
64+
}

tsconfig.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
"@/*": ["./src/*"]
2222
}
2323
},
24-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
24+
"include": [
25+
"next-env.d.ts",
26+
"**/*.ts",
27+
"**/*.tsx",
28+
".next/types/**/*.ts",
29+
"src/lib/env/*.js"
30+
],
2531
"exclude": ["node_modules"]
2632
}

0 commit comments

Comments
 (0)