feat: add userName tracking to audit events
- Add userName column to AuditEvent schema with index and migration - Add GET /api/v1/auth/me endpoint returning current user identity - AuditCollector auto-fills userName from session→user map, resolved lazily via /auth/me on first session creation - Support userName and date range (from/to) filtering on audit events and sessions endpoints - Audit console sidebar groups sessions by project → user - Add date filter presets (d key: all/today/1h/24h/7d) to console - Add scrolling and page up/down to sidebar navigation - Tests: auth-me (4), audit-username collector (4), route filters (2), smoke tests (2) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,14 +41,30 @@ interface AuditEvent {
|
||||
projectName: string;
|
||||
source: string;
|
||||
verified: boolean;
|
||||
userName?: string | null;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface AuditSession {
|
||||
sessionId: string;
|
||||
projectName: string;
|
||||
userName: string | null;
|
||||
firstSeen: string;
|
||||
lastSeen: string;
|
||||
eventCount: number;
|
||||
eventKinds: string[];
|
||||
}
|
||||
|
||||
interface AuditQueryResult {
|
||||
events: AuditEvent[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface AuditSessionResult {
|
||||
sessions: AuditSession[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
/** Fetch JSON from mcpd REST API (with auth from credentials). */
|
||||
function mcpdGet<T>(path: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -263,4 +279,40 @@ describe('Smoke: Audit events', () => {
|
||||
expect(result).toHaveProperty('total');
|
||||
console.log(` ✓ Audit API returned ${result.events.length} events (total: ${result.total})`);
|
||||
}, 10_000);
|
||||
|
||||
it('sessions endpoint returns userName field', async () => {
|
||||
if (!available) return;
|
||||
|
||||
const result = await mcpdGet<AuditSessionResult>(
|
||||
`/api/v1/audit/sessions?limit=5`,
|
||||
);
|
||||
|
||||
expect(result).toHaveProperty('sessions');
|
||||
expect(Array.isArray(result.sessions)).toBe(true);
|
||||
if (result.sessions.length > 0) {
|
||||
const session = result.sessions[0]!;
|
||||
// userName should be present in the response (may be null for old sessions)
|
||||
expect(session).toHaveProperty('userName');
|
||||
expect(session).toHaveProperty('projectName');
|
||||
expect(session).toHaveProperty('eventCount');
|
||||
console.log(` ✓ Session ${session.sessionId.slice(0, 8)} — userName: ${session.userName ?? '(null)'}, events: ${session.eventCount}`);
|
||||
} else {
|
||||
console.log(' No sessions available');
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
it('sessions endpoint supports date range filter', async () => {
|
||||
if (!available) return;
|
||||
|
||||
// Query with a very recent "from" — should return fewer sessions
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const result = await mcpdGet<AuditSessionResult>(
|
||||
`/api/v1/audit/sessions?from=${oneHourAgo}&limit=50`,
|
||||
);
|
||||
|
||||
expect(result).toHaveProperty('sessions');
|
||||
expect(Array.isArray(result.sessions)).toBe(true);
|
||||
expect(result).toHaveProperty('total');
|
||||
console.log(` ✓ Sessions in last hour: ${result.sessions.length} (total: ${result.total})`);
|
||||
}, 10_000);
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('Smoke: Security — mcplocal unauthenticated endpoints', () => {
|
||||
// Should be accessible without auth (documenting the vulnerability)
|
||||
expect(res.status).toBeLessThan(400);
|
||||
console.log(` ⚠ /inspect accessible without auth (status ${res.status})`);
|
||||
}, 5_000);
|
||||
}, 10_000);
|
||||
|
||||
it('/health/detailed leaks system info without authentication', async () => {
|
||||
if (!available) return;
|
||||
|
||||
Reference in New Issue
Block a user