Add backend team enforcement via requireTeam() middleware
Introduce server-side team-scoped data access enforcement: - Add TEAM_TO_IVANTI/IVANTI_TO_TEAM mapping to helpers/teams.js - Add requireTeam() middleware to middleware/auth.js - Admin bypass (req.teamScope = null) - 403 for users with no team assignment - Populates req.teamScope with short and ivanti name arrays - Ivanti findings: replace client ?teams= param with req.teamScope filtering on GET /, /counts, /counts/history, /fp-workflow-counts, POST /sync - Override and note endpoints verify finding is in team scope - Compliance: add requireTeam() router-level, validate ?team= param against scope on GET /items and GET /summary - CARD: validate teamName param on GET /teams/:teamName/assets - Todo queue: verify findings belong to user's teams on POST /batch - Clarify IVANTI_BU_FILTER comment (sync-level vs query-time filtering) - Update 14 test files to include requireTeam in auth middleware mocks
This commit is contained in:
@@ -7,7 +7,7 @@ const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const { spawn } = require('child_process');
|
||||
const pool = require('../db');
|
||||
const { requireAuth, requireGroup } = require('../middleware/auth');
|
||||
const { requireAuth, requireGroup, requireTeam } = require('../middleware/auth');
|
||||
const { loadConfig, compareSchemaToDrift, reconcileConfig } = require('../helpers/driftChecker');
|
||||
const { isValidDateString, validateRemediationPlan, computeVCLStats, categorizeNonCompliant, rankHeavyHitters, computeForecastBurndown, matchByHostname, computeBulkDiff, mapColumnHeaders } = require('../helpers/vclHelpers');
|
||||
const logAudit = require('../helpers/auditLog');
|
||||
@@ -288,8 +288,9 @@ function computeWaterfall(uploads) {
|
||||
function createComplianceRouter(upload) {
|
||||
const router = express.Router();
|
||||
|
||||
// All compliance routes require authentication
|
||||
// All compliance routes require authentication and team assignment
|
||||
router.use(requireAuth());
|
||||
router.use(requireTeam());
|
||||
|
||||
/**
|
||||
* POST /preview
|
||||
@@ -537,6 +538,16 @@ function createComplianceRouter(upload) {
|
||||
const team = req.query.team;
|
||||
if (team && !ALLOWED_TEAMS.has(team)) return res.status(400).json({ error: 'Invalid team' });
|
||||
|
||||
// Validate requested team is in user's scope
|
||||
if (team && req.teamScope && !req.teamScope.short.includes(team)) {
|
||||
return res.status(403).json({
|
||||
error: 'Access denied. You do not have access to the requested team.',
|
||||
code: 'TEAM_ACCESS_DENIED',
|
||||
requested: team,
|
||||
allowed: req.teamScope.short
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Try AEO uploads first (vertical IS NULL), fall back to NTS_AEO multi-vertical upload
|
||||
let { rows: latestRows } = await pool.query(
|
||||
@@ -600,6 +611,16 @@ function createComplianceRouter(upload) {
|
||||
if (!ALLOWED_TEAMS.has(team)) return res.status(400).json({ error: 'Invalid team' });
|
||||
if (!['active', 'resolved'].includes(status)) return res.status(400).json({ error: 'Invalid status' });
|
||||
|
||||
// Validate requested team is in user's scope
|
||||
if (req.teamScope && !req.teamScope.short.includes(team)) {
|
||||
return res.status(403).json({
|
||||
error: 'Access denied. You do not have access to the requested team.',
|
||||
code: 'TEAM_ACCESS_DENIED',
|
||||
requested: team,
|
||||
allowed: req.teamScope.short
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Include items from both AEO uploads (vertical IS NULL) and NTS_AEO multi-vertical uploads
|
||||
// DISTINCT ON deduplicates cross-vertical (hostname, metric_id) pairs, keeping the representative row
|
||||
|
||||
Reference in New Issue
Block a user