/***********************************************************************************************************************************************************************
* File Name: originGuard.middleware.js
* Type of Program: Middleware
* Description: Three-layer server-side guard that blocks non-browser clients from reaching any API route.
*
* ── Layer 1 — Fetch Metadata family (ALL methods including GET) ─────────────────────────────────
*
* 1a) Sec-Fetch-Site presence + value
* Browsers (Chrome 76+, Firefox 90+, Safari 16.4+) automatically attach Sec-Fetch-Site on
* every request. It is a forbidden request header — JavaScript cannot set, override, or
* remove it. Its absence reliably signals a non-browser client. The value cross-site is
* also rejected; only same-origin, same-site, and none (direct navigation) are accepted.
*
* Tools blocked (default configurations):
* ✓ Metasploit (Rex HTTP client) — no Sec-Fetch-Site
* ✓ BurpSuite Repeater / Scanner — no Sec-Fetch-Site
* ✓ Postman — no Sec-Fetch-Site
* ✓ curl / wget / httpie / python-requests — no Sec-Fetch-Site
* ✓ Nikto — no Sec-Fetch-Site
* ✓ sqlmap — no Sec-Fetch-Site
* ✓ dirb / gobuster / feroxbuster — no Sec-Fetch-Site
* ✓ nmap HTTP scripts — no Sec-Fetch-Site
*
* 1b) Sec-Fetch-Mode + Sec-Fetch-Dest presence + valid combination
* Browsers that send Sec-Fetch-Site always send Mode and Dest too (Chrome 80+,
* Firefox 90+, Safari 16.4+). Missing headers or impossible combinations signal
* manual header injection. Only combinations expected on an API server are allowed:
* cors|empty — standard fetch() call from a cross-origin SPA
* same-origin|empty — same-origin fetch()
* navigate|document — direct browser navigation to an API URL
*
* Additional tools blocked:
* ✓ Scripts that fake only Sec-Fetch-Site — missing Mode or Dest
* ✓ Scripts with wrong Mode+Dest combos — no-cors, cors+document, etc.
*
* ── Layer 2 — Browser presence signals (ALL methods including GET) ─────────────────────────────
*
* At least one browser-native header must be present:
* Sec-CH-UA — Chromium client hint, forbidden in non-browser contexts
* Accept-Language — sent by all browsers (Chrome, Firefox, Safari)
* Absence of both is a strong automation signal that catches tools sophisticated enough to
* replicate the Sec-Fetch-* family but not the full browser header profile.
*
* ── Layer 3 — Origin allowlist (POST / PUT / PATCH / DELETE only) ──────────────────────────────
*
* State-mutating requests must also carry an Origin header that matches ALLOWED_ORIGINS.
* Stops credential-stuffing and cross-origin mutation attempts from unlisted domains,
* even if an attacker replicated all browser headers above.
*
* ── What this does NOT stop ──────────────────────────────────────────────────────────────────────
*
* ✗ BurpSuite running as MITM proxy through a real browser session.
* ✗ Playwright / Puppeteer / Selenium controlling a real browser — they produce all correct
* Sec-Fetch-* headers, Sec-CH-UA, and Accept-Language automatically.
* ✗ A determined attacker who manually replicates all required headers.
* The only defences at that point are rate limiting and valid credentials.
*
* ── Browser compatibility note ───────────────────────────────────────────────────────────────────
*
* Sec-Fetch-Site is supported by Chrome 76+ (Aug 2019), Firefox 90+ (Jul 2021),
* and Safari 16.4+ (Mar 2023). Requests from browsers older than these thresholds
* will be rejected. Given this is a modern SPA (Vite + React), this is acceptable.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 20, 2026
* Date Modified: Jun. 29, 2026
***********************************************************************************************************************************************************************/
"use strict";
const R = require('../utils/response.util');
const ALLOWED = (process.env.ALLOWED_ORIGINS || process.env.APP_URL || '')
.split(',')
.map(o => o.trim())
.filter(Boolean);
const MUTATION_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
// Routes that legitimately receive a top-level cross-site navigation — a third-party
// IdP (Google) redirects the browser here after consent, so Sec-Fetch-Site is
// correctly "cross-site" even though the request is a real browser, not an attacker.
// Still gated on GET + navigate|document below, so this doesn't open the route to
// cross-site fetch()/XHR — only actual browser navigations.
const CROSS_SITE_NAVIGATION_PATHS = new Set(['/google/callback']);
// Valid Sec-Fetch-Mode + Sec-Fetch-Dest combinations expected on this API server.
const VALID_FETCH_COMBOS = new Set([
'cors|empty', // standard fetch() from cross-origin SPA
'same-origin|empty', // same-origin fetch()
'navigate|document', // direct browser navigation to an API URL
'no-cors|image', // loading a media/asset route
'no-cors|video', //