Extend team enforcement to Atlas and Archive routes, update schema reference

- Atlas: add requireTeam() at router level; replace client ?teams= param
  parsing with req.teamScope in /metrics, /status, and /sync endpoints
- Archive: add requireTeam() at router level; replace client ?teams= param
  parsing with req.teamScope in GET / and GET /stats endpoints
- db-schema.sql: add impersonate_user_id column to sessions table reference

The frontend still sends ?teams= as a query param to these endpoints
(harmless no-op since backend ignores it). Frontend cleanup deferred
to avoid churn in the 7000-line ReportingPage component.
This commit is contained in:
Jordan Ramos
2026-06-24 13:41:16 -06:00
parent 0e17318cba
commit e34f9e567c
3 changed files with 71 additions and 110 deletions

View File

@@ -1,7 +1,7 @@
// Ivanti Archive Routes — list, stats, and transition history for archived findings
const express = require('express');
const pool = require('../db');
const { requireAuth } = require('../middleware/auth');
const { requireAuth, requireTeam } = require('../middleware/auth');
const VALID_STATES = ['ACTIVE', 'ARCHIVED', 'RETURNED', 'CLOSED'];
@@ -30,18 +30,18 @@ function findRelatedActive(archive, activeFindings) {
function createIvantiArchiveRouter() {
const router = express.Router();
// All routes require authentication
// All routes require authentication and team scoping
router.use(requireAuth());
router.use(requireTeam());
/**
* GET /
* List archive records with optional state and teams filtering.
* List archive records with optional state filtering.
* Team scoping enforced by requireTeam() middleware via req.teamScope.
*
* @query {string} [state] - Filter by lifecycle state. Valid values: ACTIVE, ARCHIVED, RETURNED, CLOSED.
* When state=ACTIVE, returns live open findings from ivanti_findings instead of archives.
* When state=CLOSED, includes both CLOSED and CLOSED_GONE records.
* @query {string} [teams] - Comma-separated BU team names (e.g. 'STEAM,ACCESS-ENG').
* Filters results to findings whose bu_ownership contains one of the specified teams.
*
* @response {object} 200
* { archives: Array<{ id, finding_id, finding_title, host_name, ip_address, current_state, last_severity, first_archived_at, last_transition_at, created_at, related_active: object|null }>, total: number }
@@ -51,7 +51,7 @@ function createIvantiArchiveRouter() {
* { error: string }
*/
router.get('/', async (req, res) => {
const { state, teams } = req.query;
const { state } = req.query;
if (state && !VALID_STATES.includes(state)) {
return res.status(400).json({
@@ -59,9 +59,9 @@ function createIvantiArchiveRouter() {
});
}
// Parse teams filter into ILIKE patterns
const teamPatterns = teams
? teams.split(',').map(t => `%${t.trim()}%`).filter(p => p !== '%%')
// Build team patterns from middleware (null = admin, no filter)
const teamPatterns = req.teamScope
? req.teamScope.ivanti.map(t => `%${t}%`)
: [];
try {
@@ -148,9 +148,7 @@ function createIvantiArchiveRouter() {
/**
* GET /stats
* Summary counts of archive records grouped by lifecycle state.
*
* @query {string} [teams] - Comma-separated BU team names (e.g. 'STEAM,ACCESS-ENG').
* Filters counts to findings whose bu_ownership contains one of the specified teams.
* Team scoping enforced by requireTeam() middleware via req.teamScope.
*
* @response {object} 200
* { ACTIVE: number, ARCHIVED: number, RETURNED: number, CLOSED: number, total: number }
@@ -159,9 +157,9 @@ function createIvantiArchiveRouter() {
*/
router.get('/stats', async (req, res) => {
try {
const { teams } = req.query;
const teamPatterns = teams
? teams.split(',').map(t => `%${t.trim()}%`).filter(p => p !== '%%')
// Build team patterns from middleware (null = admin, no filter)
const teamPatterns = req.teamScope
? req.teamScope.ivanti.map(t => `%${t}%`)
: [];
let archiveQuery, archiveParams = [];
@@ -190,7 +188,7 @@ function createIvantiArchiveRouter() {
}
}
// ACTIVE = total live findings count (scoped by teams if provided)
// ACTIVE = total live findings count (scoped by teams)
let activeQuery, activeParams = [];
if (teamPatterns.length > 0) {
activeQuery = `SELECT COUNT(*) as total FROM ivanti_findings WHERE state = 'open' AND bu_ownership ILIKE ANY($1::text[])`;