Merge pull request #3352 from bluewave-labs/fix/env-var-validation

Fix/env var validation
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-03-02 14:01:35 -08:00
commit 6622a990be
5 files changed
+95 -23

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
# ==============================================
# Checkmate Server Configuration
# ==============================================
# Copy this file to .env and fill in your values
# Server Configuration
# --------------------
NODE_ENV=development
LOG_LEVEL=debug
# Database
# --------
DB_CONNECTION_STRING=mongodb://localhost:27017/uptime_db
# JWT Authentication
# ------------------
JWT_SECRET=my_secret
TOKEN_TTL=99d
# Client Configuration
# --------------------
CLIENT_HOST=http://localhost:5173
# Optional Configuration
# ----------------------
ORIGIN=localhost
+6 -2
View File
@@ -2,6 +2,7 @@ import { initializeServices } from "./config/services.js";
import { initializeControllers } from "./config/controllers.js";
import { createApp } from "./app.js";
import { initShutdownListener } from "./shutdown.js";
import { validateEnv } from "./validation/envValidation.js";
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs";
@@ -15,6 +16,9 @@ const SERVICE_NAME = "Server";
let logger: ILogger;
const startApp = async () => {
// Validate environment variables first
const env = validateEnv();
// FE path
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -23,7 +27,7 @@ const startApp = async () => {
// Create services
const settingsRepository = new MongoSettingsRepository();
const settingsService = new SettingsService(settingsRepository);
const settingsService = new SettingsService(settingsRepository, env);
const envSettings = settingsService.loadSettings();
@@ -46,7 +50,7 @@ const startApp = async () => {
openApiSpec,
});
const port = envSettings.port || 52345;
const port = 52345;
const server = app.listen(port, () => {
logger.info({ message: `Server started on port:${port}` });
});
+16 -21
View File
@@ -1,29 +1,17 @@
import { ISettingsRepository } from "@/repositories/index.js";
import { Settings } from "@/types/index.js";
import { AppError } from "@/utils/AppError.js";
import { ValidatedEnv } from "@/validation/envValidation.js";
const SERVICE_NAME = "SettingsService";
export type EnvConfig = {
jwtSecret: string | undefined;
jwtTTL: string | undefined;
systemEmailHost: string | undefined;
nodeEnv: string | undefined;
logLevel: string | undefined;
clientHost: string | undefined;
dbConnectionString: string | undefined;
port: string | undefined;
};
const envConfig: EnvConfig = {
jwtSecret: process.env.JWT_SECRET,
jwtTTL: process.env.TOKEN_TTL,
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
nodeEnv: process.env.NODE_ENV,
logLevel: process.env.LOG_LEVEL,
clientHost: process.env.CLIENT_HOST,
dbConnectionString: process.env.DB_CONNECTION_STRING,
port: process.env.PORT,
jwtSecret: string;
jwtTTL: string;
nodeEnv: string;
logLevel: string;
clientHost: string;
dbConnectionString: string;
};
export interface ISettingsService {
@@ -38,9 +26,16 @@ class SettingsService implements ISettingsService {
private settings: EnvConfig;
private settingsRepository: ISettingsRepository;
constructor(settingsRepository: ISettingsRepository) {
constructor(settingsRepository: ISettingsRepository, env: ValidatedEnv) {
this.settingsRepository = settingsRepository;
this.settings = { ...envConfig };
this.settings = {
jwtSecret: env.JWT_SECRET,
jwtTTL: env.TOKEN_TTL,
nodeEnv: env.NODE_ENV,
logLevel: env.LOG_LEVEL,
clientHost: env.CLIENT_HOST,
dbConnectionString: env.DB_CONNECTION_STRING,
};
}
get serviceName() {
+44
View File
@@ -0,0 +1,44 @@
import { z } from "zod";
const envSchema = z.object({
// Server Configuration
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
LOG_LEVEL: z.enum(["error", "warn", "info", "debug"]).default("debug"),
// Database
DB_CONNECTION_STRING: z.string().min(1, "Database connection string is required"),
// JWT Authentication
JWT_SECRET: z.string().min(1, "JWT_SECRET is required"),
TOKEN_TTL: z.string().default("99d"),
// Client Configuration
CLIENT_HOST: z.string().url("CLIENT_HOST must be a valid URL"),
// Optional
ORIGIN: z.string().optional(),
});
export type ValidatedEnv = z.infer<typeof envSchema>;
export const validateEnv = (): ValidatedEnv => {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error("Environment validation failed:");
console.error("");
const errors = result.error.format();
for (const [key, value] of Object.entries(errors)) {
if (key !== "_errors" && value && typeof value === "object" && "_errors" in value) {
console.error(` ${key}: ${value._errors.join(", ")}`);
}
}
console.error("Please check your .env file and ensure all required variables are set.");
process.exit(1);
}
console.log("Environment variables validated successfully");
return result.data;
};
+3
View File
@@ -8,6 +8,9 @@
// Shared utilities
export * from "./shared.js";
// Environment validation
export * from "./envValidation.js";
// Domain-specific validations
export * from "./authValidation.js";
export * from "./monitorValidation.js";