diff --git a/src/middleware/authCheck.js b/src/middleware/authCheck.js new file mode 100644 index 0000000..4660673 --- /dev/null +++ b/src/middleware/authCheck.js @@ -0,0 +1,86 @@ +// middleware/authCheck.js +const fetch = require("node-fetch"); + +const VERIFY_URL = process.env.AUTH_VERIFY; +const CACHE_TTL = parseInt(process.env.AUTH_CACHE_TTL) || 120000; // 2 minutes default + +// Simple in-memory cache +const authCache = new Map(); + +// Helper to generate cache key +function getCacheKey(cookie, authHeader) { + return `${cookie}:${authHeader}`; +} + +// Helper to check if cache entry is valid +function isCacheValid(entry) { + return entry && Date.now() - entry.timestamp < CACHE_TTL; +} + +// Clean expired cache entries periodically +setInterval(() => { + const now = Date.now(); + for (const [key, entry] of authCache.entries()) { + if (now - entry.timestamp >= CACHE_TTL) { + authCache.delete(key); + } + } +}, CACHE_TTL); // Clean up when entries would expire + +module.exports = async (req, res, next) => { + const cookie = req.headers["cookie"] || ""; + const authHeader = req.headers["authorization"] || ""; + const cacheKey = getCacheKey(cookie, authHeader); + + // Check cache first + const cached = authCache.get(cacheKey); + if (isCacheValid(cached)) { + req.isAuthenticated = cached.isAuthenticated; + return next(); + } + + // Default to unauthenticated + req.isAuthenticated = false; + + try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout + + const resVerify = await fetch(VERIFY_URL, { + headers: { + cookie, + authorization: authHeader, + }, + credentials: "include", + signal: controller.signal, + }); + + clearTimeout(timeout); + + const isAuthenticated = resVerify.status === 200; + + // Cache the result + authCache.set(cacheKey, { + isAuthenticated, + timestamp: Date.now(), + }); + + req.isAuthenticated = isAuthenticated; + } catch (err) { + // Auth server down/timeout - silently fail, don't crash the app + req.isAuthenticated = false; + + // Optional: Log for debugging, but don't spam logs + if (req.log) { + req.log.warn( + "[AuthCheck] Auth server unavailable, continuing unauthenticated" + ); + } else { + console.warn( + "[AuthCheck] Auth server unavailable, continuing unauthenticated" + ); + } + } + + next(); +}; diff --git a/src/middleware/index.js b/src/middleware/index.js index 2cd52b9..ebce2ee 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -11,7 +11,7 @@ const errorHandler = require("./errorHandler"); const baseContext = require("./baseContext"); const hbs = require("./hbs"); -const authentication = require("./authentication.js"); +const authCheck = require("./authCheck"); const { loggingMiddleware, @@ -22,7 +22,7 @@ function setupApp() { const app = express(); - const excludedPaths = ['/contact', '/analytics', '/track']; + const excludedPaths = ["/contact", "/analytics", "/track"]; const DATA_LIMIT_BYTES = 10 * 1024; // 10k // General parsers for non-excluded routes @@ -30,18 +30,22 @@ if (excludedPaths.includes(req.path)) return next(); express.json({ limit: DATA_LIMIT_BYTES })(req, res, (err) => { if (err) return next(err); - express.urlencoded({ extended: false, limit: DATA_LIMIT_BYTES })(req, res, next); + express.urlencoded({ extended: false, limit: DATA_LIMIT_BYTES })( + req, + res, + next + ); }); }); // Raw parser + manual truncation for excluded routes - const rawBodyParser = express.raw({ type: '*/*', limit: '100kb' }); + const rawBodyParser = express.raw({ type: "*/*", limit: "100kb" }); app.use((req, res, next) => { if (!excludedPaths.includes(req.path)) return next(); rawBodyParser(req, res, (err) => { if (err) return next(err); try { - const raw = req.body.toString('utf8'); + const raw = req.body.toString("utf8"); const truncated = raw.slice(0, DATA_LIMIT_BYTES); req.body = JSON.parse(truncated); } catch (e) { @@ -51,17 +55,15 @@ }); }); - - app.use(hbs); // Setup logging app.use(logEvent, morganInfo, morganWarn, morganError, loggingMiddleware); - app.use(authentication); - + app.use(authCheck); + // Setup handlebars - app.use(baseContext) + app.use(baseContext); // Setup production environment if (process.env.NODE_ENV === "production") {