useRelationshipEncryption
Location: src/hooks/useRelationshipEncryption.js
Overview
Manages client-side encryption for relationship data (labels and notes on relationship edges). Uses the Web Crypto API for key derivation and encryption. The derived key is kept in memory only (never persisted to disk), while the user's secret is stored in sessionStorage for session continuity. Supports initialization, unlock/lock, encrypt/decrypt operations, and migration of existing plaintext data to encrypted storage.
Signature
function useRelationshipEncryption(): {
// State
isSupported: boolean,
isInitialized: boolean,
isUnlocked: boolean,
loading: boolean,
error: string | null,
salt: string | null,
// Actions
initializeEncryption: (secret: string) => Promise<{ success?: boolean, error?: string }>,
unlockEncryption: (secret: string) => Promise<{ success?: boolean, error?: string }>,
lockEncryption: () => void,
restoreFromSession: () => Promise<boolean>,
// Encryption helpers
encrypt: (data: { label?: string, notes?: string }) => Promise<{ label_encrypted?, notes_encrypted?, label?, notes? }>,
decrypt: (data: object) => Promise<object>,
decryptMany: (edges: object[]) => Promise<object[]>,
// Migration
getUnencryptedEdges: () => Promise<{ data: object[], error: string | null }>,
migrateEdge: (edge: object) => Promise<{ success?: boolean, error?: string }>,
migrateAllEdges: () => Promise<{ migrated: number, total?: number, errors?: object[] | null, error?: string }>
}
Parameters
This hook takes no parameters. It determines the current user internally.
Return Value
| Property | Type | Description |
|---|---|---|
isSupported | boolean | Whether the browser supports the Web Crypto API |
isInitialized | boolean | Whether encryption has been initialized (salt exists in DB) |
isUnlocked | boolean | Whether the encryption key is currently in memory |
loading | boolean | Whether the salt is being fetched |
error | string | null | Error message if initialization or fetch failed |
salt | string | null | The user's encryption salt (stored in DB) |
initializeEncryption | (secret) => Promise<Result> | First-time setup: generates a salt, saves it to DB, derives the key |
unlockEncryption | (secret) => Promise<Result> | Derive the encryption key from the user's secret and salt |
lockEncryption | () => void | Clear the key from memory and session storage |
restoreFromSession | () => Promise<boolean> | Attempt to restore the key from session storage |
encrypt | (data) => Promise<object> | Encrypt label and notes fields. Returns plaintext if not unlocked. |
decrypt | (data) => Promise<object> | Decrypt encrypted fields on a relationship object. Returns as-is if not unlocked. |
decryptMany | (edges) => Promise<object[]> | Decrypt an array of relationship edge objects |
getUnencryptedEdges | () => Promise<Result> | Fetch relationship edges that have not been encrypted yet |
migrateEdge | (edge) => Promise<Result> | Encrypt and update a single relationship edge |
migrateAllEdges | () => Promise<Result> | Migrate all unencrypted edges. Returns count of migrated, total, and any errors. |
Usage
import { useRelationshipEncryption } from '../hooks/useRelationshipEncryption';
function EncryptionSettings() {
const {
isSupported,
isInitialized,
isUnlocked,
initializeEncryption,
unlockEncryption,
lockEncryption,
migrateAllEdges
} = useRelationshipEncryption();
if (!isSupported) return <p>Encryption is not supported in this browser.</p>;
if (!isInitialized) {
return (
<button onClick={() => initializeEncryption('user-secret')}>
Enable Encryption
</button>
);
}
if (!isUnlocked) {
return (
<button onClick={() => unlockEncryption('user-secret')}>
Unlock Encryption
</button>
);
}
return (
<div>
<p>Encryption is active</p>
<button onClick={lockEncryption}>Lock</button>
<button onClick={migrateAllEdges}>Migrate existing data</button>
</div>
);
}
Notes
- The encryption key is derived using the user's ID, a secret, and a salt via the
getRelationshipKeyfunction fromsrc/lib/encryption.js. - The key is stored in a
useRef(memory only) and never written to localStorage or the database. - The user's secret is stored in
sessionStorage(cleared when the browser is closed) for session continuity. - If encryption is not unlocked,
encryptreturns plaintext data anddecryptreturns data as-is (graceful fallback). - Migration converts plaintext
labelandnotesfields to encrypted versions using themigrate_relationship_to_encryptedRPC.
Last updated: 2026-02-07