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:
Jordan Ramos
2026-06-24 11:36:25 -06:00
parent ab66d7d813
commit a003091b6a
20 changed files with 239 additions and 81 deletions

View File

@@ -1,7 +1,7 @@
// routes/ivantiTodoQueue.js
const express = require('express');
const pool = require('../db');
const { requireAuth, requireGroup } = require('../middleware/auth');
const { requireAuth, requireGroup, requireTeam } = require('../middleware/auth');
const logAudit = require('../helpers/auditLog');
const VALID_WORKFLOW_TYPES = ['FP', 'Archer', 'CARD', 'GRANITE', 'DECOM', 'Remediate'];
@@ -88,7 +88,7 @@ function createIvantiTodoQueueRouter() {
* @error 400 Invalid input
* @error 500 Internal server error
*/
router.post('/batch', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
router.post('/batch', requireAuth(), requireGroup('Admin', 'Standard_User'), requireTeam(), async (req, res) => {
const { findings, workflow_type, vendor } = req.body;
if (!Array.isArray(findings) || findings.length < 1 || findings.length > 200) {
@@ -102,6 +102,25 @@ function createIvantiTodoQueueRouter() {
}
}
// Verify findings belong to user's team scope (skip for admin)
if (req.teamScope) {
const findingIds = findings.map(f => f.finding_id.trim());
const patterns = req.teamScope.ivanti.map(t => `%${t}%`);
const { rows: validFindings } = await pool.query(
`SELECT id FROM ivanti_findings WHERE id = ANY($1) AND bu_ownership ILIKE ANY($2::text[])`,
[findingIds, patterns]
);
const validIds = new Set(validFindings.map(r => String(r.id)));
const blocked = findingIds.filter(id => !validIds.has(id));
if (blocked.length > 0) {
return res.status(403).json({
error: 'Some findings are outside your team scope and cannot be added to your queue.',
code: 'TEAM_ACCESS_DENIED',
blocked_findings: blocked
});
}
}
if (!VALID_WORKFLOW_TYPES.includes(workflow_type)) {
return res.status(400).json({ error: 'workflow_type must be FP, Archer, CARD, GRANITE, DECOM, or Remediate.' });
}