Newer
Older
express-blog / src / middleware / authCheck.js
// middleware/authCheck.js
const fetch = require("node-fetch");
const { auth } = require("../config/loader");
const { verify: verify_url, cache_ttl, timeout_ms } = auth;
const { LOG_MESSAGES } = require("../constants/authConstants");

// Simple in-memory cache
const authCache = new Map();

function getCacheKey(cookie, authHeader) {
  return `${cookie}:${authHeader}`;
}

function isCacheValid(entry) {
  return entry && Date.now() - entry.timestamp < cache_ttl;
}

setInterval(() => {
  const now = Date.now();
  for (const [key, entry] of authCache.entries()) {
    if (now - entry.timestamp >= cache_ttl) {
      authCache.delete(key);
    }
  }
}, cache_ttl);
// const SAFE_IPS = ["192.168.1.200", "192.168.1.50"];
const SAFE_IPS = [];

module.exports = async (req, res, next) => {
  // Determine the client IP address.
  // req.ip is often provided by Express and correctly handles X-Forwarded-For if Express is configured for it.
  // If not, you might need to manually check req.headers['x-forwarded-for']
  const clientIp = req.ip; // Or req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress;
  // --- Bypass Logic ---
  // Check if the client IP is in the list of safe IPs
  if (SAFE_IPS.includes(clientIp)) {
    // -- fixme; harden for production by disabling this
    res.locals.session = {
      isAuthenticated: true,
      user: "local-admin",
      groups: ["admin", "guests"], // Assign groups needed for menu visibility
    };
    if (req.log) {
      req.log.security(`Bypassing authentication for safe IP: ${clientIp}`);
    } else {
      console.security(`Bypassing authentication for safe IP: ${clientIp}`);
    }
    return next(); // Proceed to the next middleware/route handler
  }
  // --- End Bypass Logic ---

  const cookie = req.headers["cookie"] || "";
  const authHeader = req.headers["authorization"] || "";
  const cacheKey = getCacheKey(cookie, authHeader);

  const cached = authCache.get(cacheKey);
  if (isCacheValid(cached)) {
    res.locals.session = cached.session;
    return next();
  }

  res.locals.session = { isAuthenticated: false, user: null, groups: [] };

  try {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), auth.timeout_ms);

    const resVerify = await fetch(verify_url, {
      headers: { cookie, authorization: authHeader },
      credentials: "include",
      signal: controller.signal,
    });

    clearTimeout(timeout);

    if (resVerify.status === 200) {
      // Extract Authelia identity headers from the verification response
      const user = resVerify.headers.get("remote-user");
      const groupsHeader = resVerify.headers.get("remote-groups") || "";
      const groups = groupsHeader
        ? groupsHeader.split(",").map((g) => g.trim())
        : [];

      res.locals.session = {
        isAuthenticated: true,
        user: user,
        groups: groups,
      };
    }

    authCache.set(cacheKey, {
      session: res.locals.session,
      timestamp: Date.now(),
    });
  } catch (e) {
    req.isAuthenticated = false;
    if (req.log) {
      req.log.warn(LOG_MESSAGES.AUTH_SERVER_UNAVAILABLE, e.stack);
    } else {
      console.warn(LOG_MESSAGES.AUTH_SERVER_UNAVAILABLE, e.stack);
    }
  }

  next();
};