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
105 lines
3.3 KiB
JavaScript
105 lines
3.3 KiB
JavaScript
// Authentication Middleware
|
|
const pool = require('../db');
|
|
const { teamToIvanti } = require('../helpers/teams');
|
|
|
|
// Require authenticated user — no parameters needed, pool is imported directly
|
|
function requireAuth() {
|
|
return async (req, res, next) => {
|
|
const sessionId = req.cookies?.session_id;
|
|
|
|
if (!sessionId) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT s.*, u.id as user_id, u.username, u.email, u.role, u.user_group, u.bu_teams, u.is_active
|
|
FROM sessions s
|
|
JOIN users u ON s.user_id = u.id
|
|
WHERE s.session_id = $1 AND s.expires_at > NOW()`,
|
|
[sessionId]
|
|
);
|
|
|
|
const session = rows[0];
|
|
|
|
if (!session) {
|
|
return res.status(401).json({ error: 'Session expired or invalid' });
|
|
}
|
|
|
|
if (!session.is_active) {
|
|
return res.status(401).json({ error: 'Account is disabled' });
|
|
}
|
|
|
|
// Attach user to request
|
|
req.user = {
|
|
id: session.user_id,
|
|
username: session.username,
|
|
email: session.email,
|
|
role: session.role,
|
|
group: session.user_group,
|
|
teams: session.bu_teams ? session.bu_teams.split(',').filter(Boolean) : []
|
|
};
|
|
|
|
next();
|
|
} catch (err) {
|
|
console.error('Auth middleware error:', err);
|
|
return res.status(500).json({ error: 'Authentication error' });
|
|
}
|
|
};
|
|
}
|
|
|
|
// Require specific group(s)
|
|
function requireGroup(...allowedGroups) {
|
|
return (req, res, next) => {
|
|
if (!req.user) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
if (!allowedGroups.includes(req.user.group)) {
|
|
return res.status(403).json({
|
|
error: 'Insufficient permissions',
|
|
required: allowedGroups,
|
|
current: req.user.group
|
|
});
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
// Require team assignment — enforces team-scoped data access.
|
|
// Admin group bypasses (req.teamScope = null means "no filter").
|
|
// Non-admin users without teams get 403.
|
|
// Non-admin users with teams get req.teamScope = { short: [...], ivanti: [...] }.
|
|
function requireTeam() {
|
|
return (req, res, next) => {
|
|
if (!req.user) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
// Admin bypass — full access to all teams
|
|
if (req.user.group === 'Admin') {
|
|
req.teamScope = null;
|
|
return next();
|
|
}
|
|
|
|
// No teams assigned — block access
|
|
if (!req.user.teams || req.user.teams.length === 0) {
|
|
return res.status(403).json({
|
|
error: 'No team assignment. Contact an administrator to assign BU teams to your account.',
|
|
code: 'NO_TEAM_ASSIGNMENT'
|
|
});
|
|
}
|
|
|
|
// Build scope with both naming conventions
|
|
req.teamScope = {
|
|
short: req.user.teams,
|
|
ivanti: req.user.teams.map(t => teamToIvanti(t))
|
|
};
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
module.exports = { requireAuth, requireGroup, requireTeam };
|