@kysera/rls 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +819 -3
- package/dist/index.d.ts +2824 -8
- package/dist/index.js +2453 -2
- package/dist/index.js.map +1 -1
- package/dist/native/index.d.ts +1 -1
- package/dist/{types-Dowjd6zG.d.ts → types-CyqksFKU.d.ts} +72 -1
- package/package.json +6 -6
- package/src/audit/index.ts +25 -0
- package/src/audit/logger.ts +465 -0
- package/src/audit/types.ts +625 -0
- package/src/composition/builder.ts +556 -0
- package/src/composition/index.ts +43 -0
- package/src/composition/types.ts +415 -0
- package/src/field-access/index.ts +38 -0
- package/src/field-access/processor.ts +442 -0
- package/src/field-access/registry.ts +259 -0
- package/src/field-access/types.ts +453 -0
- package/src/index.ts +180 -2
- package/src/plugin.ts +3 -1
- package/src/policy/builder.ts +187 -10
- package/src/policy/types.ts +84 -0
- package/src/rebac/index.ts +30 -0
- package/src/rebac/registry.ts +303 -0
- package/src/rebac/transformer.ts +391 -0
- package/src/rebac/types.ts +412 -0
- package/src/resolvers/index.ts +30 -0
- package/src/resolvers/manager.ts +507 -0
- package/src/resolvers/types.ts +447 -0
- package/src/testing/index.ts +554 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Trail Types
|
|
3
|
+
*
|
|
4
|
+
* Provides type definitions for auditing RLS policy decisions.
|
|
5
|
+
*
|
|
6
|
+
* @module @kysera/rls/audit/types
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Operation } from '../policy/types.js'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Audit Event Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* RLS policy decision result
|
|
17
|
+
*/
|
|
18
|
+
export type AuditDecision = 'allow' | 'deny' | 'filter'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* RLS audit event
|
|
22
|
+
*
|
|
23
|
+
* Represents a single policy evaluation event for audit logging.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const event: RLSAuditEvent = {
|
|
28
|
+
* timestamp: new Date(),
|
|
29
|
+
* userId: '123',
|
|
30
|
+
* operation: 'update',
|
|
31
|
+
* table: 'posts',
|
|
32
|
+
* policyName: 'ownership-allow',
|
|
33
|
+
* decision: 'allow',
|
|
34
|
+
* context: { rowId: '456', tenantId: 'acme' }
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface RLSAuditEvent {
|
|
39
|
+
/**
|
|
40
|
+
* Timestamp of the event
|
|
41
|
+
*/
|
|
42
|
+
timestamp: Date
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* User ID who performed the action
|
|
46
|
+
*/
|
|
47
|
+
userId: string | number
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Tenant ID (if multi-tenant)
|
|
51
|
+
*/
|
|
52
|
+
tenantId?: string | number
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Database operation
|
|
56
|
+
*/
|
|
57
|
+
operation: Operation
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Table name
|
|
61
|
+
*/
|
|
62
|
+
table: string
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Name of the policy that made the decision
|
|
66
|
+
*/
|
|
67
|
+
policyName?: string
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Decision result
|
|
71
|
+
*/
|
|
72
|
+
decision: AuditDecision
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Reason for the decision (especially for denials)
|
|
76
|
+
*/
|
|
77
|
+
reason?: string
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Additional context about the event
|
|
81
|
+
*/
|
|
82
|
+
context?: Record<string, unknown>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Row ID(s) affected
|
|
86
|
+
*/
|
|
87
|
+
rowIds?: (string | number)[]
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Hash of the query (for grouping similar queries)
|
|
91
|
+
*/
|
|
92
|
+
queryHash?: string
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Request ID for tracing
|
|
96
|
+
*/
|
|
97
|
+
requestId?: string
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* IP address of the requester
|
|
101
|
+
*/
|
|
102
|
+
ipAddress?: string
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* User agent string
|
|
106
|
+
*/
|
|
107
|
+
userAgent?: string
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Duration of policy evaluation in milliseconds
|
|
111
|
+
*/
|
|
112
|
+
durationMs?: number
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether this event was filtered from logging
|
|
116
|
+
* (set by filtering rules but still available for debugging)
|
|
117
|
+
*/
|
|
118
|
+
filtered?: boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Audit Adapter Interface
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Adapter for persisting audit events
|
|
127
|
+
*
|
|
128
|
+
* Implement this interface to store audit events in your preferred backend.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* class DatabaseAuditAdapter implements RLSAuditAdapter {
|
|
133
|
+
* constructor(private db: Kysely<AuditDB>) {}
|
|
134
|
+
*
|
|
135
|
+
* async log(event: RLSAuditEvent): Promise<void> {
|
|
136
|
+
* await this.db.insertInto('rls_audit_log')
|
|
137
|
+
* .values({
|
|
138
|
+
* user_id: event.userId,
|
|
139
|
+
* operation: event.operation,
|
|
140
|
+
* table_name: event.table,
|
|
141
|
+
* decision: event.decision,
|
|
142
|
+
* context: JSON.stringify(event.context),
|
|
143
|
+
* created_at: event.timestamp
|
|
144
|
+
* })
|
|
145
|
+
* .execute();
|
|
146
|
+
* }
|
|
147
|
+
*
|
|
148
|
+
* async logBatch(events: RLSAuditEvent[]): Promise<void> {
|
|
149
|
+
* await this.db.insertInto('rls_audit_log')
|
|
150
|
+
* .values(events.map(e => ({
|
|
151
|
+
* user_id: e.userId,
|
|
152
|
+
* operation: e.operation,
|
|
153
|
+
* table_name: e.table,
|
|
154
|
+
* decision: e.decision,
|
|
155
|
+
* context: JSON.stringify(e.context),
|
|
156
|
+
* created_at: e.timestamp
|
|
157
|
+
* })))
|
|
158
|
+
* .execute();
|
|
159
|
+
* }
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export interface RLSAuditAdapter {
|
|
164
|
+
/**
|
|
165
|
+
* Log a single audit event
|
|
166
|
+
*
|
|
167
|
+
* @param event - Event to log
|
|
168
|
+
*/
|
|
169
|
+
log(event: RLSAuditEvent): Promise<void>
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Log multiple audit events (for batch processing)
|
|
173
|
+
*
|
|
174
|
+
* @param events - Events to log
|
|
175
|
+
*/
|
|
176
|
+
logBatch?(events: RLSAuditEvent[]): Promise<void>
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Flush any buffered events
|
|
180
|
+
*/
|
|
181
|
+
flush?(): Promise<void>
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Close the adapter and release resources
|
|
185
|
+
*/
|
|
186
|
+
close?(): Promise<void>
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// Audit Configuration
|
|
191
|
+
// ============================================================================
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Configuration for table-specific audit settings
|
|
195
|
+
*/
|
|
196
|
+
export interface TableAuditConfig {
|
|
197
|
+
/**
|
|
198
|
+
* Whether audit is enabled for this table
|
|
199
|
+
* @default true (if audit is globally enabled)
|
|
200
|
+
*/
|
|
201
|
+
enabled?: boolean
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Log allowed decisions
|
|
205
|
+
* @default false
|
|
206
|
+
*/
|
|
207
|
+
logAllowed?: boolean
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Log denied decisions
|
|
211
|
+
* @default true
|
|
212
|
+
*/
|
|
213
|
+
logDenied?: boolean
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Log filter applications
|
|
217
|
+
* @default false
|
|
218
|
+
*/
|
|
219
|
+
logFilters?: boolean
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Context fields to include in audit logs
|
|
223
|
+
* If empty, includes all available context
|
|
224
|
+
*/
|
|
225
|
+
includeContext?: string[]
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Context fields to exclude from audit logs
|
|
229
|
+
*/
|
|
230
|
+
excludeContext?: string[]
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Whether to include row data in audit logs
|
|
234
|
+
* @default false (for privacy)
|
|
235
|
+
*/
|
|
236
|
+
includeRowData?: boolean
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Whether to include mutation data in audit logs
|
|
240
|
+
* @default false (for privacy)
|
|
241
|
+
*/
|
|
242
|
+
includeMutationData?: boolean
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Custom filter function to determine if an event should be logged
|
|
246
|
+
*/
|
|
247
|
+
filter?: (event: RLSAuditEvent) => boolean
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Global audit configuration
|
|
252
|
+
*/
|
|
253
|
+
export interface AuditConfig {
|
|
254
|
+
/**
|
|
255
|
+
* Audit adapter for persisting events
|
|
256
|
+
*/
|
|
257
|
+
adapter: RLSAuditAdapter
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Whether audit is enabled globally
|
|
261
|
+
* @default true
|
|
262
|
+
*/
|
|
263
|
+
enabled?: boolean
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Default settings for all tables
|
|
267
|
+
*/
|
|
268
|
+
defaults?: Omit<TableAuditConfig, 'enabled'>
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Table-specific audit configurations
|
|
272
|
+
*/
|
|
273
|
+
tables?: Record<string, TableAuditConfig>
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Buffer size for batch logging
|
|
277
|
+
* Events are batched until this size is reached
|
|
278
|
+
* @default 100
|
|
279
|
+
*/
|
|
280
|
+
bufferSize?: number
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Maximum time to buffer events before flushing (ms)
|
|
284
|
+
* @default 5000 (5 seconds)
|
|
285
|
+
*/
|
|
286
|
+
flushInterval?: number
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Whether to log asynchronously (fire-and-forget)
|
|
290
|
+
* @default true (for performance)
|
|
291
|
+
*/
|
|
292
|
+
async?: boolean
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Error handler for audit failures
|
|
296
|
+
*/
|
|
297
|
+
onError?: (error: Error, events: RLSAuditEvent[]) => void
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Sample rate for audit logging (0.0 to 1.0)
|
|
301
|
+
* Use for high-traffic systems to reduce log volume
|
|
302
|
+
* @default 1.0 (log all)
|
|
303
|
+
*/
|
|
304
|
+
sampleRate?: number
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// Audit Query Types
|
|
309
|
+
// ============================================================================
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Query parameters for retrieving audit events
|
|
313
|
+
*/
|
|
314
|
+
export interface AuditQueryParams {
|
|
315
|
+
/**
|
|
316
|
+
* Filter by user ID
|
|
317
|
+
*/
|
|
318
|
+
userId?: string | number
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Filter by tenant ID
|
|
322
|
+
*/
|
|
323
|
+
tenantId?: string | number
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Filter by table name
|
|
327
|
+
*/
|
|
328
|
+
table?: string
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Filter by operation
|
|
332
|
+
*/
|
|
333
|
+
operation?: Operation
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Filter by decision
|
|
337
|
+
*/
|
|
338
|
+
decision?: AuditDecision
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Start timestamp (inclusive)
|
|
342
|
+
*/
|
|
343
|
+
startTime?: Date
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* End timestamp (exclusive)
|
|
347
|
+
*/
|
|
348
|
+
endTime?: Date
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Filter by request ID
|
|
352
|
+
*/
|
|
353
|
+
requestId?: string
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Maximum results to return
|
|
357
|
+
*/
|
|
358
|
+
limit?: number
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Offset for pagination
|
|
362
|
+
*/
|
|
363
|
+
offset?: number
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Aggregated audit statistics
|
|
368
|
+
*/
|
|
369
|
+
export interface AuditStats {
|
|
370
|
+
/**
|
|
371
|
+
* Total number of events
|
|
372
|
+
*/
|
|
373
|
+
totalEvents: number
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Events by decision type
|
|
377
|
+
*/
|
|
378
|
+
byDecision: Record<AuditDecision, number>
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Events by operation
|
|
382
|
+
*/
|
|
383
|
+
byOperation: Record<Operation, number>
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Events by table
|
|
387
|
+
*/
|
|
388
|
+
byTable: Record<string, number>
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Top denied users
|
|
392
|
+
*/
|
|
393
|
+
topDeniedUsers?: { userId: string | number; count: number }[]
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Time range of stats
|
|
397
|
+
*/
|
|
398
|
+
timeRange: {
|
|
399
|
+
start: Date
|
|
400
|
+
end: Date
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ============================================================================
|
|
405
|
+
// Console Audit Adapter
|
|
406
|
+
// ============================================================================
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Simple console-based audit adapter for development/testing
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* const adapter = new ConsoleAuditAdapter({
|
|
414
|
+
* format: 'json',
|
|
415
|
+
* colors: true
|
|
416
|
+
* });
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
export interface ConsoleAuditAdapterOptions {
|
|
420
|
+
/**
|
|
421
|
+
* Output format
|
|
422
|
+
* @default 'text'
|
|
423
|
+
*/
|
|
424
|
+
format?: 'text' | 'json'
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Use colors in output (for text format)
|
|
428
|
+
* @default true
|
|
429
|
+
*/
|
|
430
|
+
colors?: boolean
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Include timestamp in output
|
|
434
|
+
* @default true
|
|
435
|
+
*/
|
|
436
|
+
includeTimestamp?: boolean
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Console audit adapter implementation
|
|
441
|
+
*/
|
|
442
|
+
export class ConsoleAuditAdapter implements RLSAuditAdapter {
|
|
443
|
+
private options: Required<ConsoleAuditAdapterOptions>
|
|
444
|
+
|
|
445
|
+
constructor(options: ConsoleAuditAdapterOptions = {}) {
|
|
446
|
+
this.options = {
|
|
447
|
+
format: options.format ?? 'text',
|
|
448
|
+
colors: options.colors ?? true,
|
|
449
|
+
includeTimestamp: options.includeTimestamp ?? true
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
log(event: RLSAuditEvent): Promise<void> {
|
|
454
|
+
if (this.options.format === 'json') {
|
|
455
|
+
// eslint-disable-next-line no-console
|
|
456
|
+
console.log(JSON.stringify(event))
|
|
457
|
+
} else {
|
|
458
|
+
const prefix = this.getPrefix(event.decision)
|
|
459
|
+
const timestamp = this.options.includeTimestamp ? `[${event.timestamp.toISOString()}] ` : ''
|
|
460
|
+
// eslint-disable-next-line no-console
|
|
461
|
+
console.log(
|
|
462
|
+
`${timestamp}${prefix} RLS ${event.decision.toUpperCase()}: ${event.operation} on ${event.table}` +
|
|
463
|
+
(event.policyName ? ` (policy: ${event.policyName})` : '') +
|
|
464
|
+
(event.reason ? ` - ${event.reason}` : '') +
|
|
465
|
+
(event.userId ? ` [user: ${event.userId}]` : '')
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
return Promise.resolve()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async logBatch(events: RLSAuditEvent[]): Promise<void> {
|
|
472
|
+
for (const event of events) {
|
|
473
|
+
await this.log(event)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private getPrefix(decision: AuditDecision): string {
|
|
478
|
+
if (!this.options.colors) {
|
|
479
|
+
return decision === 'allow' ? '✓' : decision === 'deny' ? '✗' : '~'
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
switch (decision) {
|
|
483
|
+
case 'allow':
|
|
484
|
+
return '\x1b[32m✓\x1b[0m' // Green
|
|
485
|
+
case 'deny':
|
|
486
|
+
return '\x1b[31m✗\x1b[0m' // Red
|
|
487
|
+
case 'filter':
|
|
488
|
+
return '\x1b[33m~\x1b[0m' // Yellow
|
|
489
|
+
default:
|
|
490
|
+
return '?'
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ============================================================================
|
|
496
|
+
// In-Memory Audit Adapter
|
|
497
|
+
// ============================================================================
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* In-memory audit adapter for testing
|
|
501
|
+
*
|
|
502
|
+
* Stores events in memory for later retrieval and assertion.
|
|
503
|
+
*/
|
|
504
|
+
export class InMemoryAuditAdapter implements RLSAuditAdapter {
|
|
505
|
+
private events: RLSAuditEvent[] = []
|
|
506
|
+
private maxSize: number
|
|
507
|
+
|
|
508
|
+
constructor(maxSize = 10000) {
|
|
509
|
+
this.maxSize = maxSize
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
log(event: RLSAuditEvent): Promise<void> {
|
|
513
|
+
this.events.push(event)
|
|
514
|
+
// Trim if exceeds max size
|
|
515
|
+
if (this.events.length > this.maxSize) {
|
|
516
|
+
this.events = this.events.slice(-this.maxSize)
|
|
517
|
+
}
|
|
518
|
+
return Promise.resolve()
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
logBatch(events: RLSAuditEvent[]): Promise<void> {
|
|
522
|
+
this.events.push(...events)
|
|
523
|
+
if (this.events.length > this.maxSize) {
|
|
524
|
+
this.events = this.events.slice(-this.maxSize)
|
|
525
|
+
}
|
|
526
|
+
return Promise.resolve()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get all logged events
|
|
531
|
+
*/
|
|
532
|
+
getEvents(): RLSAuditEvent[] {
|
|
533
|
+
return [...this.events]
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Query events
|
|
538
|
+
*/
|
|
539
|
+
query(params: AuditQueryParams): RLSAuditEvent[] {
|
|
540
|
+
let results = [...this.events]
|
|
541
|
+
|
|
542
|
+
if (params.userId !== undefined) {
|
|
543
|
+
results = results.filter(e => e.userId === params.userId)
|
|
544
|
+
}
|
|
545
|
+
if (params.tenantId !== undefined) {
|
|
546
|
+
results = results.filter(e => e.tenantId === params.tenantId)
|
|
547
|
+
}
|
|
548
|
+
if (params.table) {
|
|
549
|
+
results = results.filter(e => e.table === params.table)
|
|
550
|
+
}
|
|
551
|
+
if (params.operation) {
|
|
552
|
+
results = results.filter(e => e.operation === params.operation)
|
|
553
|
+
}
|
|
554
|
+
if (params.decision) {
|
|
555
|
+
results = results.filter(e => e.decision === params.decision)
|
|
556
|
+
}
|
|
557
|
+
if (params.startTime) {
|
|
558
|
+
results = results.filter(e => e.timestamp >= params.startTime!)
|
|
559
|
+
}
|
|
560
|
+
if (params.endTime) {
|
|
561
|
+
results = results.filter(e => e.timestamp < params.endTime!)
|
|
562
|
+
}
|
|
563
|
+
if (params.requestId) {
|
|
564
|
+
results = results.filter(e => e.requestId === params.requestId)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (params.offset) {
|
|
568
|
+
results = results.slice(params.offset)
|
|
569
|
+
}
|
|
570
|
+
if (params.limit) {
|
|
571
|
+
results = results.slice(0, params.limit)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return results
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get statistics
|
|
579
|
+
*/
|
|
580
|
+
getStats(params?: Pick<AuditQueryParams, 'startTime' | 'endTime'>): AuditStats {
|
|
581
|
+
let events = this.events
|
|
582
|
+
|
|
583
|
+
if (params?.startTime) {
|
|
584
|
+
events = events.filter(e => e.timestamp >= params.startTime!)
|
|
585
|
+
}
|
|
586
|
+
if (params?.endTime) {
|
|
587
|
+
events = events.filter(e => e.timestamp < params.endTime!)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const byDecision: Record<AuditDecision, number> = { allow: 0, deny: 0, filter: 0 }
|
|
591
|
+
const byOperation: Record<Operation, number> = { read: 0, create: 0, update: 0, delete: 0, all: 0 }
|
|
592
|
+
const byTable: Record<string, number> = {}
|
|
593
|
+
|
|
594
|
+
for (const event of events) {
|
|
595
|
+
byDecision[event.decision]++
|
|
596
|
+
byOperation[event.operation]++
|
|
597
|
+
byTable[event.table] = (byTable[event.table] ?? 0) + 1
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
totalEvents: events.length,
|
|
602
|
+
byDecision,
|
|
603
|
+
byOperation,
|
|
604
|
+
byTable,
|
|
605
|
+
timeRange: {
|
|
606
|
+
start: events[0]?.timestamp ?? new Date(),
|
|
607
|
+
end: events[events.length - 1]?.timestamp ?? new Date()
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Clear all events
|
|
614
|
+
*/
|
|
615
|
+
clear(): void {
|
|
616
|
+
this.events = []
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Get event count
|
|
621
|
+
*/
|
|
622
|
+
get size(): number {
|
|
623
|
+
return this.events.length
|
|
624
|
+
}
|
|
625
|
+
}
|