Vault server
Configure and call Ownfold's backend coordination service directly, without a browser or frontend framework.
@ownfold/server is the framework-independent backend SDK. It depends only on Ownfold’s portable
contracts and better-result; it does not import React, browser storage, Web Workers, or a database
driver.
Install
pnpm add @ownfold/server@beta
Add one database adapter, or implement VaultAdapter & VaultRotationAdapter
for your own storage layer.
Create the server
import { createVaultServer } from "@ownfold/server"
import { SqliteVaultAdapter } from "@ownfold/sqlite"
const server = createVaultServer({
adapter: new SqliteVaultAdapter(database),
getUserId: async ({ request }) => sessions.userIdFromRequest(request),
})
getUserId is the ownership boundary. It must return an immutable application user ID from a
verified session or null for an unauthenticated request. Ownfold never accepts an owner ID from a
request body.
Use it directly
Framework adapters call the same public methods that your backend code can call:
const result = await server.getVault({ request })
if (result.status === "error") {
logger.warn({ code: result.error.code }, "Vault lookup rejected")
return
}
const vault = result.value
Expected failures are typed result values. Do not replace an error with a success-shaped fallback; handle authentication, conflict, rate-limit, and storage errors according to their tags and codes.
Operation groups
| Group | Methods |
|---|---|
| Vault | getVault, createVault |
| Recovery | markRecoveryVerified, replaceRecoveryKit |
| Devices | listDevices, registerDevice, touchDevice, revokeDevice |
| Pairing | createPairing, getPairing, approvePairing, cancelPairing |
| Rotation | getRotation, beginRotation, updateRotationProgress, completeRotation |
| Application records | validateEncryptedRecordWrite |
The server API reference documents every input, result, state transition, and error condition.
Validate ciphertext writes
Application record tables belong to your application. Validate the encrypted payload before an insert or update:
const validated = await server.validateEncryptedRecordWrite({
request,
body: requestBody,
namespace: "documents",
recordId: documentId,
})
if (validated.status === "error") return reject(validated.error)
await documents.insert({
id: documentId,
ownerId: authenticatedUserId,
encryptedPayload: validated.value.encryptedPayload,
})
Pass only the encrypted-write object as body. Parse routing fields such as documentId at your
application boundary first. Plaintext siblings and mismatched authenticated context are rejected.
Rate limiting
Use checkRateLimit to connect Redis, an in-process limiter, or an infrastructure service:
const server = createVaultServer({
adapter,
getUserId,
checkRateLimit: ({ operation, request, userId }) => limiter.check({
operation,
request,
userId,
}),
})
The hook returns Result<void, RateLimitExceededError>. Apply stricter limits to mutation,
pairing, recovery, and rotation operations than to ordinary reads.
Audit hooks
onAuditEvent receives allowed and rejected decisions with the operation, user ID when known,
timestamp, and error code for rejections. It never receives plaintext or root keys.
const server = createVaultServer({
adapter,
getUserId,
onAuditEvent: (event) => auditQueue.publish(event),
onAuditDeliveryError: ({ event }) => {
logger.error({ operation: event.operation }, "Ownfold audit delivery failed")
},
})
Audit delivery failure does not silently turn a rejected vault operation into success. Decide whether your host application should fail closed at a higher boundary.
Expose HTTP routes
Use the portable Fetch handler when your runtime accepts Web Request objects:
import { createVaultFetchHandler } from "@ownfold/fetch"
export const handleOwnfold = createVaultFetchHandler({
server,
isOriginAllowed: ({ request }) => allowedOrigins.has(request.headers.get("origin") ?? ""),
})
Node, Next.js, Fastify, Hono, Elysia, and tRPC adapters only translate their framework boundary to this server. Choose one from framework adapters.
Production checklist
- Resolve ownership from a verified server-side session.
- Apply adapter migrations before accepting traffic.
- Keep encrypted application records in application-owned tables.
- Enforce same-origin mutation checks for cookie-authenticated HTTP endpoints.
- Configure request-size limits before buffering JSON.
- Add rate limiting and audit delivery.
- Return
Cache-Control: no-storefrom vault endpoints. - Test authorization, stale revisions, malformed stored data, and transaction rollback.