Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

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-store from vault endpoints.
  • Test authorization, stale revisions, malformed stored data, and transaction rollback.

Last updated on August 4, 2026

Was this page helpful?