require("dotenv").config(); // Fail fast if critical secrets are missing or left as placeholders — this // is a cheap, high-value guard against accidentally deploying with // insecure defaults. const REQUIRED_IN_PRODUCTION = [ "MONGODB_URI", "JWT_ACCESS_SECRET", "JWT_REFRESH_SECRET", "FIELD_ENCRYPTION_KEY", "SMTP_HOST", "SMTP_USER", "SMTP_PASS", "CLOUDINARY_CLOUD_NAME", "CLOUDINARY_API_KEY", "CLOUDINARY_API_SECRET", ]; const env = { nodeEnv: process.env.NODE_ENV || "development", port: parseInt(process.env.PORT, 10) || 5000, clientOrigins: (process.env.CLIENT_ORIGINS || "http://localhost:5173") .split(",") .map((s) => s.trim()) .filter(Boolean), // Base URL used to build absolute redirect links (e.g. Payoneer // Checkout success/cancel URLs) sent to third-party APIs — falls // back to the first allowed CORS origin if not set explicitly. frontendUrl: process.env.FRONTEND_URL || (process.env.CLIENT_ORIGINS || "http://localhost:5173").split(",")[0].trim(), mongoUri: process.env.MONGODB_URI, jwtAccessSecret: process.env.JWT_ACCESS_SECRET, jwtRefreshSecret: process.env.JWT_REFRESH_SECRET, jwtAccessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || "15m", jwtRefreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || "7d", fieldEncryptionKey: process.env.FIELD_ENCRYPTION_KEY, smtp: { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT, 10) || 587, secure: process.env.SMTP_SECURE === "true", user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, }, emailFrom: process.env.EMAIL_FROM || "Aynwerd Academy ", adminNotifyEmail: process.env.ADMIN_NOTIFY_EMAIL, bank: { name: process.env.BANK_NAME || "", accountName: process.env.BANK_ACCOUNT_NAME || "", accountNumber: process.env.BANK_ACCOUNT_NUMBER || "", routingNumber: process.env.BANK_ROUTING_NUMBER || "", swiftBic: process.env.BANK_SWIFT_BIC || "", iban: process.env.BANK_IBAN || "", address: process.env.BANK_ADDRESS || "", }, payoneer: { paymentUrl: process.env.PAYONEER_PAYMENT_URL || "https://www.payoneer.com/", accountEmail: process.env.PAYONEER_ACCOUNT_EMAIL || "", }, // Payoneer Checkout (automated card/wallet checkout) — separate from // the manual "payoneer" bank-style transfer above. These credentials // are issued directly by Payoneer only after your business is // approved as a Payoneer Checkout merchant; see // utils/payoneerCheckout.js for what's still a placeholder pending // that approval and your integration-specific API docs from them. payoneerCheckout: { apiBase: process.env.PAYONEER_CHECKOUT_API_BASE || "", apiKey: process.env.PAYONEER_CHECKOUT_API_KEY || "", apiSecret: process.env.PAYONEER_CHECKOUT_API_SECRET || "", webhookSecret: process.env.PAYONEER_CHECKOUT_WEBHOOK_SECRET || "", }, seedAdmin: { name: process.env.SEED_ADMIN_NAME || "Site Admin", email: process.env.SEED_ADMIN_EMAIL, password: process.env.SEED_ADMIN_PASSWORD, }, // Cloudinary: free-tier media storage used exclusively for admin-uploaded // course videos/documents. Files are uploaded with delivery type // "authenticated" so nothing is reachable without a freshly signed, // short-lived URL minted server-side for a verified, paying learner. cloudinary: { cloudName: process.env.CLOUDINARY_CLOUD_NAME, apiKey: process.env.CLOUDINARY_API_KEY, apiSecret: process.env.CLOUDINARY_API_SECRET, }, }; function assertProductionSafety() { if (env.nodeEnv !== "production") return; const missing = REQUIRED_IN_PRODUCTION.filter((key) => { const value = process.env[key]; return !value || value.startsWith("replace_with_") || value.includes("<"); }); if (missing.length > 0) { // eslint-disable-next-line no-console console.error( `FATAL: refusing to start in production with missing/placeholder env vars: ${missing.join(", ")}` ); process.exit(1); } if (Buffer.from(env.fieldEncryptionKey || "", "hex").length !== 32) { // eslint-disable-next-line no-console console.error("FATAL: FIELD_ENCRYPTION_KEY must be a 32-byte (64 hex character) value."); process.exit(1); } } module.exports = { env, assertProductionSafety };