first commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# middleware/audit-middleware
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Builds the global Hono middleware that records one audit event per non-excluded request through the underlying `AuditLogger`. The middleware also installs the `c.set("audit", ...)` slot before invoking `next()` so handlers can fire explicit, semantic events through `c.get("audit").log(...)` regardless of whether the request itself was recorded. This file is the per-request hot path; everything else in the plugin exists to support it.
|
||||
|
||||
The middleware never throws into the request response — sink failures are caught, logged at warn, and surfaced via the `audit.sink_failures_total` counter. Telemetry must not 500 the caller.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-request event capture: method, path, status, duration (ms), request id, principal id, tenant id, response size, severity (info < 400, warning < 500, error otherwise).
|
||||
- Configurable redaction of query, headers, and (opt-in) JSON body via `redactPayload`.
|
||||
- Head-end sampling gate via `samplingRate` and caller-supplied (or default `Math.random`) RNG.
|
||||
- Exclude paths (exact match) skip recording while still installing the `audit` context slot.
|
||||
- Never throws — sink failures logged at warn, counted, propagated through `audit.requests_recorded_total{outcome="error",sampled="true"}`.
|
||||
- Re-throws any error the downstream handler raised AFTER recording the audit (so the host error-handler still runs).
|
||||
- Records four OTel observations: recorded counter (with `outcome` + `sampled` labels), excluded counter, sink-failures counter, duration histogram.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { AuditLogger, MemoryAuditAdapter } from "@suaveplan/audit-logging";
|
||||
import { createAuditMiddleware } from "@suaveplan/api-plugin-audit/middleware/audit-middleware";
|
||||
import { Hono } from "hono";
|
||||
|
||||
const app = new Hono();
|
||||
const logger = new AuditLogger({
|
||||
storage: new MemoryAuditAdapter(),
|
||||
secretKey: process.env.AUDIT_SECRET_KEY!,
|
||||
});
|
||||
|
||||
app.use(
|
||||
"*",
|
||||
createAuditMiddleware({
|
||||
logger,
|
||||
config: {
|
||||
sink: logger.getLogger ? logger : (undefined as never),
|
||||
secretKey: "",
|
||||
samplingRate: 1,
|
||||
excludePaths: ["/health"],
|
||||
redactQueryParams: ["token"],
|
||||
redactHeaders: ["authorization"],
|
||||
redactBodyFields: ["password"],
|
||||
recordResponseSize: true,
|
||||
recordRequestBody: false,
|
||||
enableChainIntegrity: false,
|
||||
rng: Math.random,
|
||||
},
|
||||
hostLogger: console as never,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { AuditLogger, MemoryAuditAdapter } from "@suaveplan/audit-logging";
|
||||
import { createAuditMiddleware } from "@suaveplan/api-plugin-audit/middleware/audit-middleware";
|
||||
|
||||
// Deterministic sampling for tests:
|
||||
let i = 0;
|
||||
const seq = [0.1, 0.9, 0.4, 0.8];
|
||||
const rng = () => seq[i++ % seq.length] as number;
|
||||
|
||||
const middleware = createAuditMiddleware({
|
||||
logger: new AuditLogger({
|
||||
storage: new MemoryAuditAdapter(),
|
||||
secretKey: "x".repeat(32),
|
||||
}),
|
||||
config: {
|
||||
sink: new MemoryAuditAdapter(),
|
||||
secretKey: "x".repeat(32),
|
||||
samplingRate: 0.5,
|
||||
excludePaths: [],
|
||||
redactQueryParams: ["secret"],
|
||||
redactHeaders: ["authorization"],
|
||||
redactBodyFields: ["password"],
|
||||
recordResponseSize: true,
|
||||
recordRequestBody: true, // includes JSON bodies in the audit detail
|
||||
enableChainIntegrity: true,
|
||||
rng,
|
||||
},
|
||||
hostLogger: console as never,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createAuditMiddleware(deps: { logger: AuditLogger; config: AuditPluginConfig; hostLogger: LoggerLike }): MiddlewareHandler`
|
||||
- `logger` — the underlying `AuditLogger` constructed in `onInit`.
|
||||
- `config` — the resolved `AuditPluginConfig`.
|
||||
- `hostLogger` — the logger used to surface sink-write failures at warn.
|
||||
|
||||
The returned middleware is a standard Hono `MiddlewareHandler` you can register via `app.use("*", ...)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`c.set("audit", ...)` is set BEFORE the excluded-path / sampling gates so handlers on excluded paths can still emit explicit events. The middleware reads the response status from `c.res?.status` after `next()`; when the downstream handler throws, the middleware captures the throw, sets status to 500 in the audit record, then re-throws so the host error handler still produces the actual response. JSON body capture clones `c.req.raw` to avoid consuming the body the handler needs to read; non-JSON bodies are not captured (binary uploads would balloon the audit volume). Sink-write failures never propagate to the request — they update three OTel counters and a warn-level log instead.
|
||||
Reference in New Issue
Block a user