@usebetterdev/audit-core 0.4.0-beta.1
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/dist/index.cjs +1267 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +524 -0
- package/dist/index.d.ts +524 -0
- package/dist/index.js +1227 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { ConsoleRegistration, ConsoleProductEndpoint } from '@usebetterdev/console-contract';
|
|
2
|
+
export { ConsoleRegistration } from '@usebetterdev/console-contract';
|
|
3
|
+
|
|
4
|
+
/** Identifies a specific resource (table + optional record). */
|
|
5
|
+
interface ResourceFilter {
|
|
6
|
+
tableName: string;
|
|
7
|
+
recordId?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Time-based filter. Discriminated union:
|
|
11
|
+
* - `{ date: Date }` — absolute cutoff
|
|
12
|
+
* - `{ duration: string }` — relative duration string (e.g. "30d"), resolved at query time
|
|
13
|
+
*/
|
|
14
|
+
type TimeFilter = {
|
|
15
|
+
date: Date;
|
|
16
|
+
duration?: never;
|
|
17
|
+
} | {
|
|
18
|
+
duration: string;
|
|
19
|
+
date?: never;
|
|
20
|
+
};
|
|
21
|
+
/** All optional filter fields. OR within arrays, AND across fields. */
|
|
22
|
+
interface AuditQueryFilters {
|
|
23
|
+
resource?: ResourceFilter;
|
|
24
|
+
/** Actor IDs to include (OR semantics — entry matches any). */
|
|
25
|
+
actorIds?: string[];
|
|
26
|
+
/** Severity levels to include (OR semantics — entry matches any). */
|
|
27
|
+
severities?: AuditSeverity[];
|
|
28
|
+
/** Compliance tags (AND semantics — entry must have ALL listed tags). */
|
|
29
|
+
compliance?: string[];
|
|
30
|
+
since?: TimeFilter;
|
|
31
|
+
until?: TimeFilter;
|
|
32
|
+
/**
|
|
33
|
+
* Full-text search filter. Adapters MUST use parameterized queries when
|
|
34
|
+
* passing this value to the database — never interpolate into raw SQL.
|
|
35
|
+
*/
|
|
36
|
+
searchText?: string;
|
|
37
|
+
/** Operation types to include (OR semantics — entry matches any). */
|
|
38
|
+
operations?: AuditOperation[];
|
|
39
|
+
}
|
|
40
|
+
/** Full query specification: filters + pagination controls. */
|
|
41
|
+
interface AuditQuerySpec {
|
|
42
|
+
filters: AuditQueryFilters;
|
|
43
|
+
limit?: number;
|
|
44
|
+
/** Opaque cursor string — adapters define their own encoding. */
|
|
45
|
+
cursor?: string;
|
|
46
|
+
/** Sort direction for results. When undefined, the adapter chooses its default. */
|
|
47
|
+
sortOrder?: "asc" | "desc";
|
|
48
|
+
}
|
|
49
|
+
/** Paginated query result. */
|
|
50
|
+
interface AuditQueryResult {
|
|
51
|
+
entries: AuditLog[];
|
|
52
|
+
/** If present, more results are available. Pass to `.after()` for the next page. */
|
|
53
|
+
nextCursor?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Callback that executes a query spec against the adapter. */
|
|
57
|
+
type QueryExecutor = (spec: AuditQuerySpec) => Promise<AuditQueryResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Fluent, immutable query builder for audit logs.
|
|
60
|
+
*
|
|
61
|
+
* Each method returns a **new** instance — safe to fork and share.
|
|
62
|
+
* Call `.list()` to execute, or `.toSpec()` to inspect without executing.
|
|
63
|
+
*/
|
|
64
|
+
declare class AuditQueryBuilder {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(executor: QueryExecutor, filters?: AuditQueryFilters, limit?: number, cursor?: string, maxLimit?: number, sortOrder?: "asc" | "desc");
|
|
67
|
+
/** Filter by table name and optional record ID. Last-write-wins. */
|
|
68
|
+
resource(tableName: string, recordId?: string): AuditQueryBuilder;
|
|
69
|
+
/** Filter by actor IDs (OR semantics, deduplicates). */
|
|
70
|
+
actor(...ids: string[]): AuditQueryBuilder;
|
|
71
|
+
/** Add severity levels to the filter (OR semantics, deduplicates). */
|
|
72
|
+
severity(...levels: AuditSeverity[]): AuditQueryBuilder;
|
|
73
|
+
/** Add compliance tags to the filter (AND semantics, deduplicates). */
|
|
74
|
+
compliance(...tags: string[]): AuditQueryBuilder;
|
|
75
|
+
/**
|
|
76
|
+
* Filter entries created after a point in time.
|
|
77
|
+
*
|
|
78
|
+
* Accepts a `Date` or a duration string (e.g. "4h", "30d", "2w", "3m", "1y").
|
|
79
|
+
* Duration strings are eagerly validated but resolved at query time for a fresh "now".
|
|
80
|
+
* Last-write-wins.
|
|
81
|
+
*/
|
|
82
|
+
since(value: Date | string): AuditQueryBuilder;
|
|
83
|
+
/**
|
|
84
|
+
* Filter entries created before a point in time.
|
|
85
|
+
*
|
|
86
|
+
* Accepts a `Date` or a duration string (e.g. "4h", "30d", "2w", "3m", "1y").
|
|
87
|
+
* Duration strings are eagerly validated but resolved at query time for a fresh "now".
|
|
88
|
+
* Last-write-wins.
|
|
89
|
+
*/
|
|
90
|
+
until(value: Date | string): AuditQueryBuilder;
|
|
91
|
+
/** Full-text search filter. Last-write-wins. Max 500 characters. */
|
|
92
|
+
search(text: string): AuditQueryBuilder;
|
|
93
|
+
/** Add operation types to the filter (OR semantics, deduplicates). */
|
|
94
|
+
operation(...ops: AuditOperation[]): AuditQueryBuilder;
|
|
95
|
+
/** Set maximum number of entries to return. Must be > 0 and <= maxLimit. */
|
|
96
|
+
limit(n: number): AuditQueryBuilder;
|
|
97
|
+
/** Set the pagination cursor for fetching the next page. */
|
|
98
|
+
after(cursor: string): AuditQueryBuilder;
|
|
99
|
+
/** Set the sort direction for results. */
|
|
100
|
+
order(direction: "asc" | "desc"): AuditQueryBuilder;
|
|
101
|
+
/** Returns the query specification without executing. Useful for tests and debugging. */
|
|
102
|
+
toSpec(): AuditQuerySpec;
|
|
103
|
+
/** Execute the query against the adapter. */
|
|
104
|
+
list(): Promise<AuditQueryResult>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type AuditOperation = "INSERT" | "UPDATE" | "DELETE";
|
|
108
|
+
type AuditSeverity = "low" | "medium" | "high" | "critical";
|
|
109
|
+
/** A single audit log entry as stored in the database. */
|
|
110
|
+
interface AuditLog {
|
|
111
|
+
id: string;
|
|
112
|
+
timestamp: Date;
|
|
113
|
+
tableName: string;
|
|
114
|
+
operation: AuditOperation;
|
|
115
|
+
recordId: string;
|
|
116
|
+
beforeData?: Record<string, unknown>;
|
|
117
|
+
afterData?: Record<string, unknown>;
|
|
118
|
+
diff?: {
|
|
119
|
+
changedFields: string[];
|
|
120
|
+
};
|
|
121
|
+
actorId?: string;
|
|
122
|
+
label?: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
severity?: AuditSeverity;
|
|
125
|
+
compliance?: string[];
|
|
126
|
+
notify?: boolean;
|
|
127
|
+
reason?: string;
|
|
128
|
+
metadata?: Record<string, unknown>;
|
|
129
|
+
/** Field names that were removed by enrichment redaction. Only set when fields were actually redacted. */
|
|
130
|
+
redactedFields?: string[];
|
|
131
|
+
}
|
|
132
|
+
/** Context propagated via AsyncLocalStorage for the duration of a request. */
|
|
133
|
+
interface AuditContext {
|
|
134
|
+
/** User or system identifier performing the action. */
|
|
135
|
+
actorId?: string;
|
|
136
|
+
/** Human-readable label for the event (e.g., "User updated"). */
|
|
137
|
+
label?: string;
|
|
138
|
+
/** Justification or reason for the action. */
|
|
139
|
+
reason?: string;
|
|
140
|
+
/** Compliance framework tags (e.g., ["soc2", "gdpr"]). */
|
|
141
|
+
compliance?: string[];
|
|
142
|
+
/** Arbitrary key-value metadata. Redaction rules do not apply to metadata. */
|
|
143
|
+
metadata?: Record<string, unknown>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Database adapter provided by ORM packages (e.g. drizzle, prisma).
|
|
147
|
+
* Passed as the `database` field in `BetterAuditConfig`.
|
|
148
|
+
*
|
|
149
|
+
* `writeLog` is required. `queryLogs` is optional — only needed when `query()` is used.
|
|
150
|
+
*/
|
|
151
|
+
/** Aggregated statistics returned by `AuditApi.getStats()`. */
|
|
152
|
+
interface AuditStats {
|
|
153
|
+
totalLogs: number;
|
|
154
|
+
tablesAudited: number;
|
|
155
|
+
eventsPerDay: Array<{
|
|
156
|
+
date: string;
|
|
157
|
+
count: number;
|
|
158
|
+
}>;
|
|
159
|
+
topActors: Array<{
|
|
160
|
+
actorId: string;
|
|
161
|
+
count: number;
|
|
162
|
+
}>;
|
|
163
|
+
topTables: Array<{
|
|
164
|
+
tableName: string;
|
|
165
|
+
count: number;
|
|
166
|
+
}>;
|
|
167
|
+
operationBreakdown: Record<string, number>;
|
|
168
|
+
severityBreakdown: Record<string, number>;
|
|
169
|
+
}
|
|
170
|
+
interface AuditDatabaseAdapter {
|
|
171
|
+
writeLog(log: AuditLog): Promise<void>;
|
|
172
|
+
queryLogs?(spec: AuditQuerySpec): Promise<AuditQueryResult>;
|
|
173
|
+
getLogById?(id: string): Promise<AuditLog | null>;
|
|
174
|
+
getStats?(options?: {
|
|
175
|
+
since?: Date;
|
|
176
|
+
tenantId?: string;
|
|
177
|
+
}): Promise<AuditStats>;
|
|
178
|
+
purgeLogs?(options: {
|
|
179
|
+
before: Date;
|
|
180
|
+
tableName?: string;
|
|
181
|
+
}): Promise<{
|
|
182
|
+
deletedCount: number;
|
|
183
|
+
}>;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Hook that runs before a log is written. May mutate the log. Errors abort the write.
|
|
187
|
+
*
|
|
188
|
+
* The log has already been enriched (labels, severity, redaction) by the time
|
|
189
|
+
* beforeLog hooks run. Hooks see post-redaction data.
|
|
190
|
+
*/
|
|
191
|
+
type BeforeLogHook = (log: AuditLog) => void | Promise<void>;
|
|
192
|
+
/** Hook that runs after a log is written. Receives a readonly snapshot. */
|
|
193
|
+
type AfterLogHook = (log: Readonly<AuditLog>) => void | Promise<void>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Top-level config passed to `betterAudit()`.
|
|
197
|
+
*
|
|
198
|
+
* Async error handling: when `asyncWrite` is true (or overridden per-call), write
|
|
199
|
+
* failures and afterLog hook errors are caught. If `onError` is set, it receives
|
|
200
|
+
* the error. Otherwise, a sanitized message is logged to `console.error`.
|
|
201
|
+
*/
|
|
202
|
+
interface BetterAuditConfig {
|
|
203
|
+
/** Database adapter (from an ORM package) that handles writing and querying audit logs. */
|
|
204
|
+
database: AuditDatabaseAdapter;
|
|
205
|
+
/** Tables to audit. Only events for these tables are captured; others are silently skipped. */
|
|
206
|
+
auditTables: string[];
|
|
207
|
+
/** When true, writes are fire-and-forget by default. Per-call `asyncWrite` overrides this. */
|
|
208
|
+
asyncWrite?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Hooks that run before each log is written. Sequential, may mutate the log.
|
|
211
|
+
* Hooks see post-enrichment, post-redaction data.
|
|
212
|
+
*/
|
|
213
|
+
beforeLog?: BeforeLogHook[];
|
|
214
|
+
/** Hooks that run after each log is written. Sequential, observational. */
|
|
215
|
+
afterLog?: AfterLogHook[];
|
|
216
|
+
/**
|
|
217
|
+
* Hard upper-bound for query results. Defaults to 1000.
|
|
218
|
+
* `.limit(n)` will throw if `n` exceeds this value.
|
|
219
|
+
* When no explicit limit is set, this value is used as the default.
|
|
220
|
+
*/
|
|
221
|
+
maxQueryLimit?: number;
|
|
222
|
+
/**
|
|
223
|
+
* Called when an async write or afterLog hook fails. Receives the raw error.
|
|
224
|
+
* If not set, falls back to `console.error` with sanitized output (no PII).
|
|
225
|
+
*/
|
|
226
|
+
onError?: (error: unknown) => void;
|
|
227
|
+
/** Console integration. Pass a BetterConsoleInstance to register audit dashboard endpoints. */
|
|
228
|
+
console?: ConsoleRegistration;
|
|
229
|
+
}
|
|
230
|
+
/** Input to a manual captureLog call. Context fields override the current AsyncLocalStorage scope. */
|
|
231
|
+
interface CaptureLogInput {
|
|
232
|
+
tableName: string;
|
|
233
|
+
operation: AuditOperation;
|
|
234
|
+
recordId: string;
|
|
235
|
+
before?: Record<string, unknown>;
|
|
236
|
+
after?: Record<string, unknown>;
|
|
237
|
+
actorId?: string;
|
|
238
|
+
label?: string;
|
|
239
|
+
/** Capture-time description (not stored in AuditContext). */
|
|
240
|
+
description?: string;
|
|
241
|
+
/** Capture-time severity override (not stored in AuditContext). */
|
|
242
|
+
severity?: AuditSeverity;
|
|
243
|
+
compliance?: string[];
|
|
244
|
+
notify?: boolean;
|
|
245
|
+
reason?: string;
|
|
246
|
+
metadata?: Record<string, unknown>;
|
|
247
|
+
/** Override the config-level asyncWrite for this specific call. */
|
|
248
|
+
asyncWrite?: boolean;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Context passed to an enrichment description function.
|
|
252
|
+
*
|
|
253
|
+
* **Important:** The `before`, `after`, and `diff` fields contain **post-redaction**
|
|
254
|
+
* data. If a field was redacted, its value will be absent from these objects.
|
|
255
|
+
* Use `diff.changedFields` for field names — redacted fields are excluded from
|
|
256
|
+
* that list as well.
|
|
257
|
+
*/
|
|
258
|
+
interface EnrichmentDescriptionContext {
|
|
259
|
+
before: Record<string, unknown> | undefined;
|
|
260
|
+
after: Record<string, unknown> | undefined;
|
|
261
|
+
diff: {
|
|
262
|
+
changedFields: string[];
|
|
263
|
+
} | undefined;
|
|
264
|
+
actorId: string | undefined;
|
|
265
|
+
metadata: Record<string, unknown> | undefined;
|
|
266
|
+
}
|
|
267
|
+
/** Declarative enrichment config registered via audit.enrich(). */
|
|
268
|
+
interface EnrichmentConfig {
|
|
269
|
+
label?: string;
|
|
270
|
+
description?: (context: EnrichmentDescriptionContext) => string;
|
|
271
|
+
severity?: AuditSeverity;
|
|
272
|
+
compliance?: string[];
|
|
273
|
+
notify?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Fields to remove from beforeData, afterData, and diff. Mutually exclusive with `include`.
|
|
276
|
+
*
|
|
277
|
+
* Redaction does NOT apply to `metadata` — avoid putting PII in metadata directly.
|
|
278
|
+
* Only top-level keys are matched. To redact a nested field like `profile.ssn`,
|
|
279
|
+
* list the parent key `"profile"`.
|
|
280
|
+
*/
|
|
281
|
+
redact?: string[];
|
|
282
|
+
/**
|
|
283
|
+
* Fields to keep in beforeData, afterData, and diff — all others are removed.
|
|
284
|
+
* Mutually exclusive with `redact`.
|
|
285
|
+
*
|
|
286
|
+
* Redaction does NOT apply to `metadata` — avoid putting PII in metadata directly.
|
|
287
|
+
* Only top-level keys are matched. To keep a nested field like `profile.name`,
|
|
288
|
+
* list the parent key `"profile"`.
|
|
289
|
+
*/
|
|
290
|
+
include?: string[];
|
|
291
|
+
}
|
|
292
|
+
interface BetterAuditInstance {
|
|
293
|
+
captureLog(input: CaptureLogInput): Promise<void>;
|
|
294
|
+
/** Returns a fluent query builder. Requires `queryAdapter` in config. */
|
|
295
|
+
query(): AuditQueryBuilder;
|
|
296
|
+
withContext<T>(context: AuditContext, fn: () => Promise<T>): Promise<T>;
|
|
297
|
+
/**
|
|
298
|
+
* Register an enrichment config for a table/operation pair.
|
|
299
|
+
*
|
|
300
|
+
* Table names are case-sensitive and must match the `tableName` passed to
|
|
301
|
+
* `captureLog()` exactly. Use `"*"` for wildcard matching.
|
|
302
|
+
*/
|
|
303
|
+
enrich(table: string, operation: AuditOperation | "*", config: EnrichmentConfig): void;
|
|
304
|
+
/** Register a hook that runs before each log is written. Returns a dispose function to unregister the hook. */
|
|
305
|
+
onBeforeLog(hook: BeforeLogHook): () => void;
|
|
306
|
+
/** Register a hook that runs after each log is written. Returns a dispose function to unregister the hook. */
|
|
307
|
+
onAfterLog(hook: AfterLogHook): () => void;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
declare function betterAudit(config: BetterAuditConfig): BetterAuditInstance;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns the current audit context, or undefined when not inside a request
|
|
314
|
+
* scope (middleware or withContext).
|
|
315
|
+
*/
|
|
316
|
+
declare function getAuditContext(): AuditContext | undefined;
|
|
317
|
+
/**
|
|
318
|
+
* Run fn inside a scope where getAuditContext() returns the given context.
|
|
319
|
+
*/
|
|
320
|
+
declare function runWithAuditContext<T>(context: AuditContext, fn: () => T | Promise<T>): Promise<T>;
|
|
321
|
+
/**
|
|
322
|
+
* Merge additional context into the current scope and run fn.
|
|
323
|
+
* If no scope exists, a new one is created from the partial context.
|
|
324
|
+
* Properties in override take precedence over the existing context.
|
|
325
|
+
*/
|
|
326
|
+
declare function mergeAuditContext<T>(override: Partial<AuditContext>, fn: () => T | Promise<T>): Promise<T>;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Normalize before/after data based on the operation type.
|
|
330
|
+
*
|
|
331
|
+
* - **INSERT** → `before` is dropped (only `after` is meaningful)
|
|
332
|
+
* - **DELETE** → `after` is dropped (only `before` is meaningful)
|
|
333
|
+
* - **UPDATE** → both are kept as-is
|
|
334
|
+
*/
|
|
335
|
+
declare function normalizeInput(operation: AuditOperation, before: Record<string, unknown> | undefined, after: Record<string, unknown> | undefined): {
|
|
336
|
+
before: Record<string, unknown> | undefined;
|
|
337
|
+
after: Record<string, unknown> | undefined;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Logical schema for the `audit_logs` table.
|
|
342
|
+
*
|
|
343
|
+
* ORM adapters translate this into their own migration format.
|
|
344
|
+
* Core never runs SQL — this is a declarative data structure only.
|
|
345
|
+
*/
|
|
346
|
+
type ColumnType = "uuid" | "timestamptz" | "text" | "jsonb" | "boolean";
|
|
347
|
+
interface ColumnDefinition {
|
|
348
|
+
type: ColumnType;
|
|
349
|
+
nullable: boolean;
|
|
350
|
+
primaryKey?: boolean;
|
|
351
|
+
defaultExpression?: string;
|
|
352
|
+
indexed?: boolean;
|
|
353
|
+
description: string;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Column names in the `audit_logs` table use snake_case.
|
|
357
|
+
* These map to camelCase properties in the `AuditLog` TypeScript type:
|
|
358
|
+
*
|
|
359
|
+
* | Column (snake_case) | AuditLog property (camelCase) |
|
|
360
|
+
* |---------------------|-------------------------------|
|
|
361
|
+
* | table_name | tableName |
|
|
362
|
+
* | record_id | recordId |
|
|
363
|
+
* | actor_id | actorId |
|
|
364
|
+
* | before_data | beforeData |
|
|
365
|
+
* | after_data | afterData |
|
|
366
|
+
* | redacted_fields | redactedFields |
|
|
367
|
+
*/
|
|
368
|
+
type AuditLogColumnName = "id" | "timestamp" | "table_name" | "operation" | "record_id" | "actor_id" | "before_data" | "after_data" | "diff" | "label" | "description" | "severity" | "compliance" | "notify" | "reason" | "metadata" | "redacted_fields";
|
|
369
|
+
interface AuditLogSchema {
|
|
370
|
+
tableName: string;
|
|
371
|
+
columns: Record<string, ColumnDefinition>;
|
|
372
|
+
}
|
|
373
|
+
declare const AUDIT_LOG_SCHEMA: AuditLogSchema;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Parses a duration string (e.g. "30d", "2w", "3m", "1y") and returns
|
|
377
|
+
* a Date that is `duration` before `referenceDate`.
|
|
378
|
+
*
|
|
379
|
+
* Supported units: h (hours), d (days), w (weeks), m (months), y (years).
|
|
380
|
+
* Zero values are rejected.
|
|
381
|
+
*/
|
|
382
|
+
declare function parseDuration(input: string, referenceDate?: Date): Date;
|
|
383
|
+
|
|
384
|
+
/** Resolved enrichment config after merging all matching tiers. */
|
|
385
|
+
interface ResolvedEnrichment {
|
|
386
|
+
label?: string;
|
|
387
|
+
description?: (context: EnrichmentDescriptionContext) => string;
|
|
388
|
+
severity?: AuditSeverity;
|
|
389
|
+
compliance?: string[];
|
|
390
|
+
notify?: boolean;
|
|
391
|
+
redact?: string[];
|
|
392
|
+
include?: string[];
|
|
393
|
+
}
|
|
394
|
+
declare class EnrichmentRegistry {
|
|
395
|
+
private readonly entries;
|
|
396
|
+
register(table: string, operation: AuditOperation | "*", config: EnrichmentConfig): void;
|
|
397
|
+
getEntries(): Array<{
|
|
398
|
+
table: string;
|
|
399
|
+
operation: string;
|
|
400
|
+
configs: EnrichmentConfig[];
|
|
401
|
+
}>;
|
|
402
|
+
resolve(table: string, operation: string): ResolvedEnrichment | undefined;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Flat console-friendly query filters. Single-value fields translated to multi-value internal filters. */
|
|
406
|
+
interface ConsoleQueryFilters {
|
|
407
|
+
tableName?: string;
|
|
408
|
+
operation?: string;
|
|
409
|
+
actorId?: string;
|
|
410
|
+
severity?: string;
|
|
411
|
+
compliance?: string;
|
|
412
|
+
since?: Date;
|
|
413
|
+
until?: Date;
|
|
414
|
+
search?: string;
|
|
415
|
+
limit?: number;
|
|
416
|
+
cursor?: string;
|
|
417
|
+
}
|
|
418
|
+
/** Serializable summary of an enrichment config (function fields stripped). */
|
|
419
|
+
interface EnrichmentSummary {
|
|
420
|
+
table: string;
|
|
421
|
+
operation: string;
|
|
422
|
+
label?: string;
|
|
423
|
+
severity?: AuditSeverity;
|
|
424
|
+
compliance?: string[];
|
|
425
|
+
notify?: boolean;
|
|
426
|
+
redact?: string[];
|
|
427
|
+
include?: string[];
|
|
428
|
+
}
|
|
429
|
+
/** Query result extended with a convenience `hasNextPage` flag. */
|
|
430
|
+
interface ConsoleQueryResult extends AuditQueryResult {
|
|
431
|
+
hasNextPage: boolean;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* High-level API consumed by console endpoints.
|
|
435
|
+
*
|
|
436
|
+
* Wraps the low-level `AuditDatabaseAdapter` methods with sensible defaults
|
|
437
|
+
* (e.g. clamping `limit` to the configured maximum).
|
|
438
|
+
*/
|
|
439
|
+
interface AuditApi {
|
|
440
|
+
/** Query audit log entries with optional flat filters and cursor-based pagination. */
|
|
441
|
+
queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult>;
|
|
442
|
+
/** Retrieve a single audit log entry by its ID. Returns `null` when not found. */
|
|
443
|
+
getLog(id: string): Promise<AuditLog | null>;
|
|
444
|
+
/** Get aggregated audit statistics. Requires adapter.getStats. */
|
|
445
|
+
getStats(options?: {
|
|
446
|
+
since?: Date;
|
|
447
|
+
tenantId?: string;
|
|
448
|
+
}): Promise<AuditStats>;
|
|
449
|
+
/** Get serializable summaries of all registered enrichments. */
|
|
450
|
+
getEnrichments(): EnrichmentSummary[];
|
|
451
|
+
/** Export logs as CSV or JSON string. */
|
|
452
|
+
exportLogs(filters?: ConsoleQueryFilters, format?: "csv" | "json"): Promise<string>;
|
|
453
|
+
/** Purge audit logs before a given date. Requires adapter.purgeLogs. */
|
|
454
|
+
purgeLogs(options: {
|
|
455
|
+
before: Date;
|
|
456
|
+
tableName?: string;
|
|
457
|
+
}): Promise<{
|
|
458
|
+
deletedCount: number;
|
|
459
|
+
}>;
|
|
460
|
+
}
|
|
461
|
+
declare function createAuditApi(adapter: AuditDatabaseAdapter, registry: EnrichmentRegistry, maxQueryLimit?: number): AuditApi;
|
|
462
|
+
|
|
463
|
+
declare function createAuditConsoleEndpoints(api: AuditApi): ConsoleProductEndpoint[];
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Function that extracts a string value from a Web Request.
|
|
467
|
+
* Return undefined if the value cannot be extracted.
|
|
468
|
+
* May be sync or async.
|
|
469
|
+
*/
|
|
470
|
+
type ValueExtractor = (request: Request) => string | undefined | Promise<string | undefined>;
|
|
471
|
+
/**
|
|
472
|
+
* Describes how to extract audit identity from an incoming HTTP request.
|
|
473
|
+
* All fields are optional — if omitted, the corresponding context field
|
|
474
|
+
* will be undefined.
|
|
475
|
+
*/
|
|
476
|
+
interface ContextExtractor {
|
|
477
|
+
/** Extracts the actor identifier (user / service account). */
|
|
478
|
+
actor?: ValueExtractor;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Options for `handleMiddleware`.
|
|
482
|
+
*/
|
|
483
|
+
interface MiddlewareHandlerOptions {
|
|
484
|
+
/** Called when an extractor throws. Defaults to silent fail-open. */
|
|
485
|
+
onError?: (error: unknown) => void;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Extracts a JWT claim from the `Authorization: Bearer <token>` header.
|
|
489
|
+
*
|
|
490
|
+
* Decodes the token **without** signature verification — signing is the
|
|
491
|
+
* auth layer's responsibility. This is intentional: the audit layer only
|
|
492
|
+
* needs the identity, not proof of authenticity.
|
|
493
|
+
*
|
|
494
|
+
* @param claim - JWT claim name to extract (e.g. `"sub"`, `"tenant_id"`)
|
|
495
|
+
*/
|
|
496
|
+
declare function fromBearerToken(claim: string): ValueExtractor;
|
|
497
|
+
/**
|
|
498
|
+
* Extracts a value from a named cookie in the `Cookie` header.
|
|
499
|
+
*
|
|
500
|
+
* The raw cookie value is returned as-is. To resolve it into a user identity,
|
|
501
|
+
* compose with a resolver function (e.g. look up a session in a database).
|
|
502
|
+
*
|
|
503
|
+
* @param cookieName - Name of the cookie to read
|
|
504
|
+
*/
|
|
505
|
+
declare function fromCookie(cookieName: string): ValueExtractor;
|
|
506
|
+
/**
|
|
507
|
+
* Extracts a value from a custom request header.
|
|
508
|
+
*
|
|
509
|
+
* @param headerName - Header name (case-insensitive per the Web API)
|
|
510
|
+
*/
|
|
511
|
+
declare function fromHeader(headerName: string): ValueExtractor;
|
|
512
|
+
/**
|
|
513
|
+
* Shared middleware handler used by all framework adapters.
|
|
514
|
+
*
|
|
515
|
+
* 1. Extracts actor from the request using the provided extractor
|
|
516
|
+
* 2. Builds an `AuditContext` (undefined if nothing was extracted)
|
|
517
|
+
* 3. Wraps `next()` inside `runWithAuditContext()` if context is available
|
|
518
|
+
* 4. Calls `next()` without context if extraction yields nothing
|
|
519
|
+
*
|
|
520
|
+
* Extraction failures never break the request (fail open).
|
|
521
|
+
*/
|
|
522
|
+
declare function handleMiddleware(extractor: ContextExtractor, request: Request, next: () => Promise<void>, options?: MiddlewareHandlerOptions): Promise<void>;
|
|
523
|
+
|
|
524
|
+
export { AUDIT_LOG_SCHEMA, type AfterLogHook, type AuditApi, type AuditContext, type AuditDatabaseAdapter, type AuditLog, type AuditLogColumnName, type AuditLogSchema, type AuditOperation, AuditQueryBuilder, type AuditQueryFilters, type AuditQueryResult, type AuditQuerySpec, type AuditSeverity, type AuditStats, type BeforeLogHook, type BetterAuditConfig, type BetterAuditInstance, type CaptureLogInput, type ColumnDefinition, type ColumnType, type ConsoleQueryFilters, type ConsoleQueryResult, type ContextExtractor, type EnrichmentConfig, type EnrichmentDescriptionContext, type EnrichmentSummary, type MiddlewareHandlerOptions, type QueryExecutor, type ResourceFilter, type TimeFilter, type ValueExtractor, betterAudit, createAuditApi, createAuditConsoleEndpoints, fromBearerToken, fromCookie, fromHeader, getAuditContext, handleMiddleware, mergeAuditContext, normalizeInput, parseDuration, runWithAuditContext };
|