first commit
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# context/audit-context
|
||||
|
||||
## Purpose
|
||||
|
||||
Builds the per-request `AuditContext` handle the audit middleware attaches to `c.set("audit", context)` before invoking `next()`. Handlers call `c.get("audit").log({...})` to fire explicit, semantic audit events alongside the implicit per-request audit that the middleware writes. This is the public extension point for application code — instead of importing `auditService` and threading it through every route, handlers reach into the Hono context.
|
||||
|
||||
The two-method surface (`log` + `getLogger`) is intentionally small so handler code stays focused on the event payload rather than the plumbing. The same underlying `AuditLogger` instance is reused across every request — there is one logger per app, not one per request — so chain-integrity links hold across handler-level and middleware-level events.
|
||||
|
||||
## Features
|
||||
|
||||
- Stable two-method surface (`log`, `getLogger`).
|
||||
- Hono `ContextVariableMap` augmentation declared in `./context-augmentation.ts` so `c.get("audit")` types as `AuditContext` (not `unknown`).
|
||||
- Zero per-request allocation beyond the lightweight wrapper object (no closures over the request, so the object can be cached if needed).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
// In a route handler:
|
||||
app.post("/users/:id/delete", async (c) => {
|
||||
const audit = c.get("audit");
|
||||
await audit.log({
|
||||
type: "user.deleted",
|
||||
severity: "warning",
|
||||
actor: { userId: c.get("user")?.id },
|
||||
target: { type: "user", id: c.req.param("id") },
|
||||
});
|
||||
return c.json({ deleted: true });
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
// Reach the underlying logger for advanced ops:
|
||||
app.get("/admin/audit-chain/verify", async (c) => {
|
||||
const logger = c.get("audit").getLogger();
|
||||
const events = await logger.query({ limit: 1000, order: "asc" });
|
||||
const verdict = await logger.verifyChainIntegrity(events);
|
||||
return c.json(verdict);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createAuditContext(deps: { logger: AuditLogger }): AuditContext`
|
||||
- `log(event)` → `Promise<AuditEvent>`
|
||||
- `getLogger()` → `AuditLogger`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`AuditContext` does not carry the request, the principal, or the tenant. Handlers fill `actor.userId`, `actor.sessionId`, `tenantId`, and so on from `c.get("user")` / `c.get("tenant")` themselves. The middleware's per-request event populates those fields automatically; handler-level events stay explicit because the handler knows the semantic of the event (which user is the actor versus the target) better than the middleware does.
|
||||
@@ -0,0 +1,58 @@
|
||||
# context/context-augmentation
|
||||
|
||||
## Purpose
|
||||
|
||||
TypeScript module augmentation that adds the `audit: AuditContext` slot to Hono's `ContextVariableMap`. Without it, `c.get("audit")` types as `unknown` and every handler call site needs an `as AuditContext` cast — defeating the point of the per-request helper. Importing the package barrel of `@suaveplan/api-plugin-audit` pulls this file in as a side effect, so handlers and downstream middlewares see the typed slot automatically.
|
||||
|
||||
The augmentation is intentionally narrow: it declares only `audit`. The `user`, `tenant`, and `requestId` slots that the middleware reads are owned by sibling plugins (`@suaveplan/api-plugin-auth`, `@suaveplan/api-tenant`, `@suaveplan/api`). TypeScript merges declarations across modules, so coexistence is safe — every plugin contributes its own slot to the same map.
|
||||
|
||||
## Features
|
||||
|
||||
- Declares `audit: AuditContext` on Hono's `ContextVariableMap`.
|
||||
- Zero runtime cost — pure TypeScript.
|
||||
- Side-effect imported by the package barrel.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
// Anywhere in the consuming app:
|
||||
import "@suaveplan/api-plugin-audit";
|
||||
|
||||
app.post("/things", async (c) => {
|
||||
const audit = c.get("audit"); // typed AuditContext
|
||||
await audit.log({ type: "thing.created", severity: "info", actor: {} });
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
// In a handler that combines `user` (auth plugin) and `audit`:
|
||||
import "@suaveplan/api-plugin-auth";
|
||||
import "@suaveplan/api-plugin-audit";
|
||||
|
||||
app.delete("/vaults/:id", async (c) => {
|
||||
const principal = c.get("user");
|
||||
const audit = c.get("audit");
|
||||
if (principal === null) return c.text("unauthenticated", 401);
|
||||
|
||||
await audit.log({
|
||||
type: "vault.deleted",
|
||||
severity: "warning",
|
||||
actor: { userId: principal.id },
|
||||
target: { type: "vault", id: c.req.param("id") },
|
||||
});
|
||||
return c.json({ deleted: true });
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
This file contributes one TypeScript `declare module "hono"` block adding:
|
||||
|
||||
- `audit: AuditContext` on `interface ContextVariableMap`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The augmentation declares `audit` as a non-optional slot. The middleware always sets it before calling `next()` (even for excluded paths — the slot is populated so handlers that opt into auditing on a normally-excluded path can still call `c.get("audit").log(...)`). If you mount the plugin AFTER user handlers in your middleware chain, `c.get("audit")` will be `undefined` at runtime even though TypeScript types it as `AuditContext`. The recommended fix is to register the audit plugin first; the `definePlugin` priority field can enforce this.
|
||||
Reference in New Issue
Block a user