Skip to main content

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;
}