Change Tracking
CORTEX tracks the before and after values for all update operations, enabling detailed change analysis.
How It Works
When a resource is updated, the audit log captures:
- oldValue: The previous state of changed fields
- newValue: The new state of changed fields
- metadata.changedFields: List of field names that changed
Example: User Update
Before Update
{
"id": "user-id",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"status": "ACTIVE"
}
Update Request
{
"firstName": "Jonathan",
"lastName": "Smith"
}
Audit Log Entry
{
"action": "UPDATE",
"resourceType": "USER",
"resourceId": "user-id",
"oldValue": {
"firstName": "John",
"lastName": "Doe"
},
"newValue": {
"firstName": "Jonathan",
"lastName": "Smith"
},
"metadata": {
"changedFields": ["firstName", "lastName"]
}
}
Querying Changes
Get All Changes to a Resource
GET /audit-logs?resourceType=USER&resourceId=user-id&action=UPDATE
Build Change History
interface Change {
timestamp: string;
userId: string;
field: string;
oldValue: any;
newValue: any;
}
async function getChangeHistory(
accessToken: string,
resourceType: string,
resourceId: string
): Promise<Change[]> {
const response = await fetch(
`http://localhost:8091/audit-logs?resourceType=${resourceType}&resourceId=${resourceId}&action=UPDATE`,
{ headers: { 'Authorization': `Bearer ${accessToken}` } }
);
const data = await response.json();
const changes: Change[] = [];
for (const log of data.data) {
const changedFields = log.metadata?.changedFields || Object.keys(log.newValue || {});
for (const field of changedFields) {
changes.push({
timestamp: log.createdAt,
userId: log.userId,
field,
oldValue: log.oldValue?.[field],
newValue: log.newValue?.[field],
});
}
}
return changes;
}
Sensitive Data Handling
Certain fields are automatically redacted:
Password Fields
{
"oldValue": { "password": "[REDACTED]" },
"newValue": { "password": "[REDACTED]" }
}
Tokens
{
"oldValue": { "refreshToken": "[REDACTED]" },
"newValue": { "refreshToken": "[REDACTED]" }
}
Diff Visualization
Simple Diff
function formatDiff(log: AuditLog): string {
const lines: string[] = [];
for (const field of log.metadata?.changedFields || []) {
const old = JSON.stringify(log.oldValue?.[field]);
const new_ = JSON.stringify(log.newValue?.[field]);
lines.push(`${field}: ${old} → ${new_}`);
}
return lines.join('\n');
}
// Output:
// firstName: "John" → "Jonathan"
// lastName: "Doe" → "Smith"
React Diff Component
interface DiffViewerProps {
oldValue: Record<string, any>;
newValue: Record<string, any>;
changedFields: string[];
}
function DiffViewer({ oldValue, newValue, changedFields }: DiffViewerProps) {
return (
<table className="diff-table">
<thead>
<tr>
<th>Field</th>
<th>Old Value</th>
<th>New Value</th>
</tr>
</thead>
<tbody>
{changedFields.map(field => (
<tr key={field}>
<td>{field}</td>
<td className="old-value">{JSON.stringify(oldValue[field])}</td>
<td className="new-value">{JSON.stringify(newValue[field])}</td>
</tr>
))}
</tbody>
</table>
);
}
Use Cases
1. Compliance Reporting
Generate reports showing all changes to sensitive data:
async function getSensitiveChanges(accessToken: string, startDate: string) {
const sensitiveFields = ['email', 'status', 'roleAssignments'];
const logs = await queryAuditLogs(accessToken, {
action: 'UPDATE',
startDate,
});
return logs.data.filter(log =>
log.metadata?.changedFields?.some(f => sensitiveFields.includes(f))
);
}
2. Incident Investigation
Track all changes made by a specific user:
async function getUserChanges(accessToken: string, userId: string, dateRange: { start: string; end: string }) {
return queryAuditLogs(accessToken, {
userId,
action: 'UPDATE',
startDate: dateRange.start,
endDate: dateRange.end,
});
}
3. Rollback Information
Get the previous state of a resource:
async function getPreviousState(
accessToken: string,
resourceType: string,
resourceId: string
): Promise<any> {
const logs = await queryAuditLogs(accessToken, {
resourceType,
resourceId,
action: 'UPDATE',
limit: 1,
});
if (logs.data.length > 0) {
return logs.data[0].oldValue;
}
return null;
}