first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.