Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
# @suaveplan/api-plugin-vault - Per-user vault HTTP plugin for @suaveplan/api. Wraps @suaveplan/vault as a @suaveplan/api-plugin definePlugin descriptor, applies a SurrealQL ownership schema (vault_item + vault_share) at boot via @suaveplan/api-migrations, mounts 5 OpenAPI routes through @suaveplan/api-openapi (CRUD over /vault/items), registers a typed vaultService in the host ServiceRegistry, and scopes every operation to c.get('user').id supplied by @suaveplan/api-plugin-auth. Third instance of the 3-way plug-and-play contract (.surql DDL + Zod boundary schemas + OpenAPI route registration).
Per-user vault HTTP plugin for `@suaveplan/api`. Third instance of the
3-way plug-and-play contract every `@suaveplan/api-plugin-*` package
follows: the plugin owns its `.surql` DDL, its Zod boundary schemas,
and its OpenAPI route definitions — all wired into the host's
`PluginRegistry`, `MigrationRunner`, and `OpenAPIRegistry` from a
single `definePlugin` descriptor. Wraps `@suaveplan/vault` for the
encrypted container and scopes every operation to `c.get("user").id`
supplied by `@suaveplan/api-plugin-auth`.
<!-- begin:auto-docs -->
## Documentation tree
- [error-codes](./src/error-codes/error-codes.md) — Stable enum of the 9 error codes raised by `@suaveplan/api-plugin-vault`. Codes are namespaced under the `VAULT_PLUGIN.…
- [error-codes](./src/error-codes/error-codes.md) — Stable enum of the 9 error codes raised by `@suaveplan/api-plugin-vault`. Codes are namespaced under the `VAULT_PLUGIN.…
- [otel](./src/otel/instruments.md) — OpenTelemetry instrument bundle for the vault plugin: four counters and one histogram covering the full CRUD surface. E…
- [instruments](./src/otel/instruments.md) — OpenTelemetry instrument bundle for the vault plugin: four counters and one histogram covering the full CRUD surface. E…
- [plugin](./src/plugin/plugin.md) — `createVaultPlugin(options)` is the factory that returns a typed `PluginDescriptor<VaultPluginConfig>` ready to registe…
- [plugin](./src/plugin/plugin.md) — `createVaultPlugin(options)` is the factory that returns a typed `PluginDescriptor<VaultPluginConfig>` ready to registe…
- [unlock](./src/plugin/unlock.md) — Tiny helper module that owns the "where does the vault unlock secret come from?" question. The plugin's default `unlock…
- [routes](./src/routes/build-routes.md) — Builds the 5 OpenAPI route entries that `createVaultPlugin` mounts on the host's Hono app: list, create, read, update,…
- [build-routes](./src/routes/build-routes.md) — Builds the 5 OpenAPI route entries that `createVaultPlugin` mounts on the host's Hono app: list, create, read, update,…
- [schema](./src/schema/schema.md) — Owns the SurrealQL DDL the vault plugin applies during `onInit` — the first leg of the 3-way plug-and-play contract. `V…
- [schema](./src/schema/schema.md) — Owns the SurrealQL DDL the vault plugin applies during `onInit` — the first leg of the 3-way plug-and-play contract. `V…
- [options](./src/schemas/options.md) — Zod schema validating the input options to `createVaultPlugin`. Every field has a Zod-derived default (or `optional()`)…
- [vault-item](./src/schemas/vault-item.md) — Zod schemas describing the wire shape of every body, parameter, and response handled by the 5 vault routes. Every schem…
- [registry-augmentation](./src/services/registry-augmentation.md) — Side-effect module that augments `@suaveplan/api-plugin`'s `ServiceRegistry` interface with the `vaultService` slot. Wi…
- [vault-service](./src/services/vault-service.md) — Canonical `VaultService` implementation registered by `createVaultPlugin` in the host's `ServiceRegistry` under the `va…
- [types](./src/types/types.md) — Public TypeScript surface for `@suaveplan/api-plugin-vault`. Every interface here is consumed by sibling modules (plugi…
- [types](./src/types/types.md) — Public TypeScript surface for `@suaveplan/api-plugin-vault`. Every interface here is consumed by sibling modules (plugi…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/api-plugin-vault
```
## Quick start
```ts
import { defineApp } from "@suaveplan/api";
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
import { createVaultPlugin } from "@suaveplan/api-plugin-vault";
const driveAdapter = /* @suaveplan/storage-s3-backed @suaveplan/drive, etc */;
const app = await defineApp()
.name("my-api")
.use(
createAuthPlugin({
betterAuth: {
/* ... Better-Auth options ... */
},
})
)
.use(
createVaultPlugin({
storage: driveAdapter,
serverPepper: process.env["VAULT_SERVER_PEPPER"]!,
})
)
.start();
```
`createVaultPlugin` returns a typed `PluginDescriptor` declaring
`dependencies: ["auth"]`. The host's `PluginRegistry` calls `onInit(ctx)`
which:
1. Resolves `app`, `openAPIRegistry`, `migrationRunner`, and `db` from
the host `ServiceRegistry`.
2. Applies `VAULT_OWNERSHIP_SCHEMA` (the `vault_item` + `vault_share`
tables plus their indexes).
3. Constructs the `vaultService` backed by the configured `storage` +
`serverPepper`.
4. Registers `vaultService` in the typed `ServiceRegistry`.
5. Mounts 5 OpenAPI routes via `definePluginRoutes` so they appear in
the unified `/openapi.json` doc with full Zod schemas — every route
is gated by `requireAuth()`.
## The 3-way plug-and-play contract
| Surface | Implementation |
|---|---|
| `.surql` DDL | `VAULT_OWNERSHIP_SCHEMA` defines `vault_item` and `vault_share` plus three indexes; applied via `MigrationRunner` (preferred) or direct `db.execute` fallback |
| Zod schemas | `CreateVaultItemBodySchema`, `UpdateVaultItemBodySchema` (strict), `VaultItemResponseSchema`, `VaultItemListResponseSchema`, etc. |
| OpenAPI routes | `buildVaultRoutes(vaultService, config)` returns 5 `RouteBuilder` entries; `definePluginRoutes` writes both Hono app AND `OpenAPIRegistry` |
## Routes
| Method | Path | Description |
|---|---|---|
| `GET` | `/vault/items` | List items owned by the principal |
| `POST` | `/vault/items` | Create a new item |
| `GET` | `/vault/items/{id}` | Fetch a single item |
| `PATCH` | `/vault/items/{id}` | Update fields (strict-mode body) |
| `DELETE` | `/vault/items/{id}` | Remove an item |
Every route requires `requireAuth()` upstream. Foreign-item references
return `404 VAULT_PLUGIN_ITEM_NOT_FOUND` (NEVER 403) — see the
"Cross-user isolation" section.
## X-Vault-Unlock header semantics
The plugin's default `unlockExtractor` reads the per-request unlock
secret from the `X-Vault-Unlock` header. The secret is forwarded to
`@suaveplan/vault`'s 2SKD pipeline along with the principal's id (used
as the Bitwarden-style "secret key") and a HKDF-derived per-principal
salt. The plugin NEVER logs the secret and NEVER persists it.
Alternative extractors (operators wire one via the `unlockExtractor`
option):
- **JWT-bound** — extract a claim from a JWT in the `Authorization`
header. Useful when the client already holds a session-bound JWT
with vault-key claims.
- **Cookie-bound** — extract from a session cookie set during sign-in.
- **Derived from password** — use the principal's authentication
password (already submitted at sign-in) as the unlock secret; the
caller does not need to send a second header at all. This requires
the auth plugin to expose the post-auth password to the request
context.
## Cross-user isolation (security note)
Items owned by another principal return `404 VAULT_PLUGIN_ITEM_NOT_FOUND`
— never `403`. Surfacing `403` would let an attacker enumerate item
ids that exist for other principals (a known weakness in many CRUD
plugins). The plugin's behaviour is deliberate; never override this
in a wrapper without auditing the consequences.
## Schema application
`VAULT_OWNERSHIP_SCHEMA` defines two tables. The `vault_item` table is
the active surface; `vault_share` is forward-declared so the future
`api-plugin-vault-sharing` follow-up can register routes without
requiring a database migration.
Operators can:
- accept the default (`applySchemaOnInit: true`) — schema applies on
every cold start, idempotently;
- set `applySchemaOnInit: false` to manage migrations externally and
apply `VAULT_OWNERSHIP_SCHEMA` via the operator's own runner.
## Modules
- [`error-codes/error-codes`](./src/error-codes/error-codes.md) — 9
stable `VAULT_PLUGIN_*` codes.
- [`otel/instruments`](./src/otel/instruments.md) — 4 counters + 1
histogram.
- [`plugin/plugin`](./src/plugin/plugin.md) — `createVaultPlugin`
factory.
- [`plugin/unlock`](./src/plugin/unlock.md) — header-based extractor.
- [`routes/build-routes`](./src/routes/build-routes.md) — the 5
OpenAPI route entries.
- [`schemas/options`](./src/schemas/options.md) — boundary schema.
- [`schemas/vault-item`](./src/schemas/vault-item.md) — wire schemas.
- [`schema/schema`](./src/schema/schema.md) — SurrealQL DDL.
- [`services/vault-service`](./src/services/vault-service.md) —
`vaultService` impl wrapping `@suaveplan/vault`.
- [`services/registry-augmentation`](./src/services/registry-augmentation.md)
— typed `ServiceRegistry` slot.
- [`types/types`](./src/types/types.md) — public TS surface.