diff --git a/client/src/components/dms-log-events/dms-log-events.component.jsx b/client/src/components/dms-log-events/dms-log-events.component.jsx index 916d577d1..075d2f6eb 100644 --- a/client/src/components/dms-log-events/dms-log-events.component.jsx +++ b/client/src/components/dms-log-events/dms-log-events.component.jsx @@ -20,7 +20,8 @@ export function DmsLogEvents({ detailsNonce, isDarkMode, colorizeJson = false, - showDetails = true + showDetails = true, + allowXmlPayload = true }) { const { t } = useTranslation(); const [openSet, setOpenSet] = useState(() => new Set()); @@ -75,7 +76,7 @@ export function DmsLogEvents({ // Only treat meta as "present" when we are allowed to show details const hasMeta = !isEmpty(meta) && showDetails; const isOpen = hasMeta && openSet.has(idx); - const xml = hasMeta ? extractXmlFromMeta(meta) : { request: null, response: null }; + const xml = hasMeta && allowXmlPayload ? extractXmlFromMeta(meta) : { request: null, response: null }; const hasRequestXml = !!xml.request; const hasResponseXml = !!xml.response; const copyPayload = hasMeta ? getCopyPayload(meta) : null; @@ -175,7 +176,7 @@ export function DmsLogEvents({ ) }; }), - [logs, openSet, colorizeJson, copiedKey, isDarkMode, showDetails, t] + [logs, openSet, colorizeJson, copiedKey, isDarkMode, showDetails, allowXmlPayload, t] ); return ; diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx index 89e44d1a2..d3a1e374d 100644 --- a/client/src/pages/dms/dms.container.jsx +++ b/client/src/pages/dms/dms.container.jsx @@ -11,7 +11,7 @@ import { useSocket } from "../../contexts/SocketIO/useSocket.js"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries"; -import { selectBodyshop } from "../../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; @@ -29,7 +29,8 @@ import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms- import RrAllocationsSummary from "../../components/dms-allocations-summary/rr-dms-allocations-summary.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ @@ -65,7 +66,41 @@ const DMS_SOCKET_EVENTS = { } }; -export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) { +const stripRrXmlFromPayload = (input) => { + if (input == null || typeof input !== "object") return input; + + let target = null; + try { + target = JSON.parse(JSON.stringify(input)); + } catch { + // Fallback to in-place scrub if cloning fails. + target = input; + } + + const scrub = (node) => { + if (node == null || typeof node !== "object") return; + if (Array.isArray(node)) { + node.forEach(scrub); + return; + } + + delete node.requestXml; + delete node.responseXml; + + if (node.xml && typeof node.xml === "object") { + delete node.xml.request; + delete node.xml.response; + if (Object.keys(node.xml).length === 0) delete node.xml; + } + + Object.values(node).forEach(scrub); + }; + + scrub(target); + return target; +}; + +export function DmsContainer({ bodyshop, currentUser, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) { const { treatments: { Fortellis } } = useTreatmentsWithConfig({ @@ -79,6 +114,15 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse const [allocationsSummary, setAllocationsSummary] = useState(null); const [reconnectNonce, setReconnectNonce] = useState(0); + const isDevEnv = import.meta.env.DEV; + const isProdEnv = import.meta.env.PROD; + const userEmail = (currentUser?.email || "").toLowerCase(); + + const devEmails = ["imex.dev", "rome.dev"]; + const prodEmails = ["imex.prod", "rome.prod", "imex.test", "rome.test"]; + const hasValidEmail = (emails) => emails.some((email) => userEmail.endsWith(email)); + const canViewSensitiveRrXml = (isDevEnv && hasValidEmail(devEmails)) || (isProdEnv && hasValidEmail(prodEmails)); + // Compute a single normalized mode and pick the proper socket const mode = getDmsMode(bodyshop, Fortellis.treatment); // "rr" | "fortellis" | "cdk" | "pbs" | "none" @@ -241,6 +285,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse }, [jobId, mode, activeSocket]); const handleExportFailed = (payload = {}) => { + const safePayload = canViewSensitiveRrXml ? payload : stripRrXmlFromPayload(payload); const { title, friendlyMessage, error: errText, severity, errorCode, vendorStatusCode } = payload; const msg = @@ -271,7 +316,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse timestamp: new Date(), level: (sev || "error").toUpperCase(), message: `${vendorTitle}: ${msg}`, - meta: { errorCode, vendorStatusCode, raw: payload, blockedByOpenRoLimit: !!isRrOpenRoLimit } + meta: { errorCode, vendorStatusCode, raw: safePayload, blockedByOpenRoLimit: !!isRrOpenRoLimit } } ]); }; @@ -344,11 +389,16 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse // Logs const onLog = isRrMode ? (payload = {}) => { + const safePayload = canViewSensitiveRrXml ? payload : stripRrXmlFromPayload(payload); const normalized = { - timestamp: payload.timestamp ? new Date(payload.timestamp) : payload.ts ? new Date(payload.ts) : new Date(), - level: (payload.level || "INFO").toUpperCase(), - message: payload.message || payload.msg || "", - meta: payload.meta ?? payload.ctx ?? payload.details ?? null + timestamp: safePayload.timestamp + ? new Date(safePayload.timestamp) + : safePayload.ts + ? new Date(safePayload.ts) + : new Date(), + level: (safePayload.level || "INFO").toUpperCase(), + message: safePayload.message || safePayload.msg || "", + meta: safePayload.meta ?? safePayload.ctx ?? safePayload.details ?? null }; setLogs((prev) => [...prev, normalized]); } @@ -429,7 +479,19 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse activeSocket.disconnect(); } }; - }, [mode, activeSocket, channels, logLevel, notification, t, insertAuditTrail, history, isRrMode, providerLabel]); + }, [ + mode, + activeSocket, + channels, + logLevel, + notification, + t, + insertAuditTrail, + history, + isRrMode, + providerLabel, + canViewSensitiveRrXml + ]); // RR finalize callback (unchanged public behavior) const handleRrValidationFinished = () => { @@ -588,6 +650,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse detailsNonce={detailsNonce} colorizeJson={isRrMode ? colorizeJson : false} showDetails={isRrMode} + allowXmlPayload={canViewSensitiveRrXml} />