/* =========================================================== SYLVAN GRAIN — ONE-FILE MAINTENANCE LOCK =========================================================== WHAT THIS DOES: ✓ Locks the entire public website ✓ /admin and /admin/* remain accessible ✓ Direct URL access is blocked ✓ Returns HTTP 503 while maintenance is active ✓ Premium Apple-style glass maintenance screen ✓ Floating glass header ✓ Responsive/mobile design ✓ Email notification subscription ✓ Subscription rate limiting ✓ Security headers ✓ Noindex / nofollow ✓ Can be turned OFF with one environment variable ENVIRONMENT VARIABLE: MAINTENANCE_MODE=true When ready: MAINTENANCE_MODE=false =========================================================== */ const express = require("express"); const crypto = require("crypto"); const router = express.Router(); /* ======================================================== CONFIGURATION ======================================================== */ const MAINTENANCE_MODE = String(process.env.MAINTENANCE_MODE ?? "true").toLowerCase() === "true"; const BRAND_NAME = process.env.MAINTENANCE_BRAND || "Sylvan Grain"; const CONTACT_EMAIL = process.env.MAINTENANCE_EMAIL || "hello@sylvangrain.com"; /* ======================================================== ADMIN BYPASS ======================================================== */ function isAdminRoute(path) { if (path === "/admin") return true; if (path.startsWith("/admin/")) return true; /* Keep admin authentication/session/API routes working. Adjust these if your project uses different admin endpoints. */ if (path === "/api/admin") return true; if (path.startsWith("/api/admin/")) return true; return false; } /* ======================================================== SUBSCRIPTION STORAGE This is a temporary in-memory store. IMPORTANT: For production, replace this section with your existing database. The maintenance page itself will still work without changing anything else. ======================================================== */ const subscribers = new Map(); /* ======================================================== BASIC RATE LIMITER ======================================================== */ const attempts = new Map(); function subscriptionRateLimit(req, res, next) { const ip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "unknown"; const now = Date.now(); const existing = attempts.get(ip); if (!existing || now - existing.time > 15 * 60 * 1000) { attempts.set(ip, { count: 1, time: now }); return next(); } if (existing.count >= 5) { return res.status(429).json({ success: false, message: "Too many subscription attempts. Please try again later." }); } existing.count++; return next(); } /* ======================================================== SECURITY HEADERS ======================================================== */ function securityHeaders(res) { res.setHeader( "X-Content-Type-Options", "nosniff" ); res.setHeader( "X-Frame-Options", "DENY" ); res.setHeader( "Referrer-Policy", "strict-origin-when-cross-origin" ); res.setHeader( "Permissions-Policy", "camera=(), microphone=(), geolocation=()" ); res.setHeader( "Cross-Origin-Opener-Policy", "same-origin" ); res.setHeader( "Cross-Origin-Resource-Policy", "same-origin" ); res.setHeader( "Content-Security-Policy", [ "default-src 'self'", "style-src 'self' 'unsafe-inline'", "script-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self' data:", "connect-src 'self'", "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'none'" ].join("; ") ); } /* ======================================================== EMAIL SUBSCRIPTION API ======================================================== */ router.post( "/api/maintenance/subscribe", subscriptionRateLimit, express.json({ limit: "10kb" }), (req, res) => { try { const email = String(req.body?.email || "") .trim() .toLowerCase(); /* Strict enough for a simple notification signup. */ if ( !email || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ) { return res.status(400).json({ success: false, message: "Please enter a valid email address." }); } /* Create a non-sensitive internal ID. */ const id = crypto.randomUUID(); /* Save subscriber. Replace this with your database call. Example: await db.subscribers.create({ id, email, source: "maintenance", createdAt: new Date() }); */ if (!subscribers.has(email)) { subscribers.set(email, { id, email, source: "maintenance_page", createdAt: new Date().toISOString() }); } return res.status(200).json({ success: true, message: "You're on the list. We'll notify you when we launch." }); } catch (error) { console.error( "[MAINTENANCE SUBSCRIBE]", error ); return res.status(500).json({ success: false, message: "Unable to subscribe right now." }); } } ); /* ======================================================== MAINTENANCE HTML ======================================================== */ function maintenanceHTML() { return ` ${escapeHTML(BRAND_NAME)} — Coming Soon
${escapeHTML(BRAND_NAME)}
Preparing something special
A little patience

We’re carefully preparing the next chapter of ${escapeHTML(BRAND_NAME)}.

Our online experience is currently being crafted. Leave your email below and we’ll let you know when everything is ready.

For enquiries, contact ${escapeHTML(CONTACT_EMAIL)}
`; } /* ======================================================== HTML ESCAPE ======================================================== */ function escapeHTML(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /* ======================================================== MAINTENANCE MIDDLEWARE ======================================================== */ function maintenanceMiddleware(req, res, next) { /* Always allow admin. */ if (isAdminRoute(req.path)) { return next(); } /* If maintenance is disabled, everything works normally. */ if (!MAINTENANCE_MODE) { return next(); } /* API requests other than subscription/admin receive JSON instead of HTML. */ if (req.path.startsWith("/api/")) { return res .status(503) .json({ success: false, maintenance: true, message: "Website temporarily unavailable." }); } /* Security headers. */ securityHeaders(res); /* Tell browsers/search engines this is temporary. */ res.status(503); res.setHeader( "Retry-After", "3600" ); res.setHeader( "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate" ); res.setHeader( "Pragma", "no-cache" ); res.setHeader( "Expires", "0" ); /* Send maintenance page. */ return res.send( maintenanceHTML() ); } /* ======================================================== INSTALL FUNCTION ======================================================== */ function installMaintenanceLock(app) { /* Subscription endpoint must be registered before the global maintenance middleware. */ app.use(router); /* Global maintenance protection. IMPORTANT: This must be placed BEFORE your normal public website routes. */ app.use(maintenanceMiddleware); } /* ======================================================== EXPORT ======================================================== */ module.exports = { installMaintenanceLock, maintenanceMiddleware, subscribers };