61 lines
2.6 KiB
JavaScript
61 lines
2.6 KiB
JavaScript
import { decideChangeRequest, getChangeRequest, listChangeRequests } from '../models/changeRequestModel.js';
|
|
import { getConnectionGovernance, intuneReportingOverview, listGraphAudit } from '../models/graphModel.js';
|
|
import { loadIntuneDeployment } from '../models/psadtModel.js';
|
|
import { canApprove } from '../services/graphRbac.js';
|
|
|
|
export function changeRequests(req, res) {
|
|
res.json(listChangeRequests(req.user.id, { status: req.query.status, isAdmin: req.user.role === 'admin' }));
|
|
}
|
|
|
|
// Admins can always decide; otherwise the user must be a named approver on the
|
|
// request's Graph connection.
|
|
function authorizedApprover(req, request) {
|
|
if (req.user.role === 'admin') return true;
|
|
const governance = request.connectionId ? getConnectionGovernance(request.connectionId) : null;
|
|
return canApprove({ approverIds: governance?.approverIds || [] }, req.user);
|
|
}
|
|
|
|
function decide(req, res, status) {
|
|
const request = getChangeRequest(req.params.id);
|
|
if (!request || request.status !== 'pending') return res.status(404).json({ error: 'Pending change request not found' });
|
|
if (!authorizedApprover(req, request)) return res.status(403).json({ error: 'You are not an authorized approver for this connection.' });
|
|
const decided = decideChangeRequest(req.params.id, {
|
|
status,
|
|
approverUserId: req.user.id,
|
|
approverEmail: req.user.email,
|
|
reason: req.body?.reason || ''
|
|
});
|
|
if (!decided) return res.status(404).json({ error: 'Pending change request not found' });
|
|
res.json(decided);
|
|
}
|
|
|
|
export function approveChangeRequest(req, res) {
|
|
decide(req, res, 'approved');
|
|
}
|
|
|
|
export function rejectChangeRequest(req, res) {
|
|
decide(req, res, 'rejected');
|
|
}
|
|
|
|
export function reportingOverview(req, res) {
|
|
res.json(intuneReportingOverview(req.user.id, req.user.role === 'admin'));
|
|
}
|
|
|
|
function toCsv(rows) {
|
|
const headers = ['createdAt', 'action', 'status', 'actorEmail', 'targetGraphId', 'message'];
|
|
const escape = (value) => `"${String(value ?? '').replace(/"/g, '""')}"`;
|
|
const lines = [headers.join(',')];
|
|
for (const row of rows) lines.push(headers.map((h) => escape(row[h])).join(','));
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// CSV export of the tenant write-back audit trail for one deployment.
|
|
export function auditExport(req, res) {
|
|
const deployment = loadIntuneDeployment(req.params.id, req.user.id);
|
|
if (!deployment) return res.status(404).json({ error: 'Intune deployment not found' });
|
|
const rows = listGraphAudit({ deploymentId: deployment.id, limit: 1000 });
|
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
res.setHeader('Content-Disposition', `attachment; filename="audit-${deployment.id}.csv"`);
|
|
res.send(toCsv(rows));
|
|
}
|