Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Vaults API

CRUD operations for encrypted password vaults.

Vault Structure

All vault data stored on the server is encrypted:

interface VaultResponse {
  encryptedData: string;  // AES-GCM encrypted entries (Base64)
  iv: string;             // Encryption IV (Base64)
  wrappedVaultKey: string; // KEK-wrapped vault key (Base64)
  vaultKeyIv: string;     // Vault key wrap IV (Base64)
  vaultKeySalt: string;   // PBKDF2 salt (Base64)
  version: number;        // Optimistic locking version
  updatedAt: string;      // ISO 8601 timestamp
}

Endpoints

List Vaults

GET /vault
Authorization: Bearer <token>
Response:
{
  "vaults": [
    {
      "name": "default",
      "version": 5,
      "updatedAt": "2024-01-15T10:30:00Z"
    },
    {
      "name": "work",
      "version": 12,
      "updatedAt": "2024-01-14T15:45:00Z"
    }
  ]
}

Create Vault

POST /vault
Authorization: Bearer <token>
Content-Type: application/json
 
{
  "name": "personal",
  "encryptedData": "base64-encrypted-entries",
  "iv": "base64-iv",
  "wrappedVaultKey": "base64-wrapped-key",
  "vaultKeyIv": "base64-key-iv",
  "vaultKeySalt": "base64-salt"
}
Response:
{
  "name": "personal",
  "version": 1,
  "createdAt": "2024-01-15T12:00:00Z"
}

Get Vault

GET /vault/:name
Authorization: Bearer <token>
Response:
{
  "encryptedData": "base64-encrypted-entries",
  "iv": "base64-iv",
  "wrappedVaultKey": "base64-wrapped-key",
  "vaultKeyIv": "base64-key-iv",
  "vaultKeySalt": "base64-salt",
  "version": 5,
  "updatedAt": "2024-01-15T10:30:00Z"
}

Update Vault

PUT /vault/:name
Authorization: Bearer <token>
Content-Type: application/json
 
{
  "encryptedData": "base64-new-encrypted-entries",
  "iv": "base64-new-iv",
  "version": 5
}

Important: Include the current version for optimistic locking.

Response:
{
  "version": 6,
  "updatedAt": "2024-01-15T12:30:00Z"
}

Delete Vault

DELETE /vault/:name
Authorization: Bearer <token>
Response:
{
  "success": true
}

Version Conflicts

Vault updates use optimistic locking to prevent data loss:

PUT /vault/:name
Content-Type: application/json
 
{
  "encryptedData": "...",
  "iv": "...",
  "version": 5  // Must match current server version
}
Conflict Response (409):
{
  "error": "Version conflict",
  "code": "VERSION_CONFLICT",
  "currentVersion": 6,
  "yourVersion": 5
}
Resolution:
  1. Fetch current vault data
  2. Merge changes locally
  3. Retry with correct version

Client-Side Encryption Flow

Creating a New Vault

import { generateVaultKey, wrapVaultKey, encryptWithVaultKey } from "@pwm/shared";
 
// 1. Generate random vault key
const vaultKey = await generateVaultKey();
 
// 2. Wrap vault key with master password
const { wrappedKey, iv: vaultKeyIv, salt } = await wrapVaultKey(
  vaultKey,
  masterPassword
);
 
// 3. Encrypt empty vault
const vault = { entries: [] };
const { encryptedData, iv } = await encryptWithVaultKey(vault, vaultKey);
 
// 4. Send to server
await api.vault.$post({
  json: {
    name: "default",
    encryptedData,
    iv,
    wrappedVaultKey: wrappedKey,
    vaultKeyIv,
    vaultKeySalt: salt
  }
});

Unlocking a Vault

import { unwrapVaultKey, decryptWithVaultKey } from "@pwm/shared";
 
// 1. Fetch encrypted vault
const response = await api.vault[":name"].$get({ param: { name: "default" } });
 
// 2. Unwrap vault key with master password
const vaultKey = await unwrapVaultKey(
  response.wrappedVaultKey,
  response.vaultKeyIv,
  response.vaultKeySalt,
  masterPassword
);
 
// 3. Decrypt entries
const vault = await decryptWithVaultKey(
  response.encryptedData,
  response.iv,
  vaultKey
);
 
// vault.entries is now decrypted

Saving Changes

// 1. Encrypt updated vault
const { encryptedData, iv } = await encryptWithVaultKey(vault, vaultKey);
 
// 2. Update on server (include version!)
await api.vault[":name"].$put({
  param: { name: "default" },
  json: {
    encryptedData,
    iv,
    version: currentVersion
  }
});

Error Responses

Vault Not Found

{
  "error": "Vault not found",
  "code": "NOT_FOUND"
}

Vault Already Exists

{
  "error": "Vault already exists",
  "code": "ALREADY_EXISTS"
}

Cannot Delete Default Vault

{
  "error": "Cannot delete default vault",
  "code": "FORBIDDEN"
}

Related