Source: middleware/requireRole.js

/**
 * @module middleware/requireRole
 * @description Role-based access control middleware (ACL-002).
 *
 * Creates Express middleware that checks `req.userRole` (injected by the
 * workspace-aware auth flow in `authenticate.js`) against a minimum role
 * level.
 *
 * ### Role hierarchy
 * ```
 * admin > qa_lead > viewer
 * ```
 *
 * `requireRole('qa_lead')` allows `admin` and `qa_lead` but blocks `viewer`.
 * `requireRole('admin')` allows only `admin`.
 *
 * ### Usage
 * ```js
 * import { requireRole } from "../middleware/requireRole.js";
 *
 * router.delete("/:id", requireRole("admin"), (req, res) => { … });
 * router.post("/",      requireRole("qa_lead"), (req, res) => { … });
 * ```
 *
 * ### Keeping `permissions.json` in sync
 * Every `requireRole(...)` call site is mirrored in `./permissions.json`,
 * which is the canonical machine-readable RBAC matrix consumed by agents
 * and reviewers (see QA.md, AGENT.md, REVIEW.md). Whenever you add, remove,
 * or change a role gate, update the corresponding entry in `permissions.json`.
 * Audit drift with: `grep -rn 'requireRole(' backend/src/routes/`.
 *
 * @param {string} minimumRole — The minimum role required ('admin' | 'qa_lead' | 'viewer').
 * @returns {Function} Express middleware `(req, res, next)`.
 */

/** Numeric weight per role — higher = more privileged. */
const ROLE_WEIGHT = {
  admin:   30,
  qa_lead: 20,
  viewer:  10,
};

/** Valid role names for input validation. */
export const VALID_ROLES = new Set(Object.keys(ROLE_WEIGHT));

/**
 * Create an Express middleware that enforces a minimum role.
 *
 * Expects `req.userRole` to be set by the auth middleware.  If missing,
 * returns 401.  If the role is below the minimum, returns 403.
 *
 * @param {string} minimumRole — 'admin' | 'qa_lead' | 'viewer'
 * @returns {Function} Express middleware
 */
export function requireRole(minimumRole) {
  const minWeight = ROLE_WEIGHT[minimumRole];
  if (minWeight === undefined) {
    throw new Error(`[requireRole] Unknown role: "${minimumRole}". Valid roles: ${[...VALID_ROLES].join(", ")}`);
  }

  return (req, res, next) => {
    const userRole = req.userRole;
    if (!userRole) {
      return res.status(401).json({ error: "Authentication required." });
    }

    const userWeight = ROLE_WEIGHT[userRole];
    if (userWeight === undefined || userWeight < minWeight) {
      return res.status(403).json({
        error: `This action requires ${minimumRole} permissions.`,
        requiredRole: minimumRole,
        currentRole: userRole || "unknown",
      });
    }

    next();
  };
}