Files
HealthLog/src/components/admin/use-key-backup-status.ts
T
Marc-André Bombeck 308470f229 feat(encryption): back up the key, and refuse to serve when it cannot open the database
Every configured key id now has a canary in encryption_key_canaries,
checked at boot. A key that cannot open its canary, or existing data
written before the canary existed, puts the process in a refusal state:
every API route answers 503 encryption.key_mismatch, /api/health reports
reason encryption_key_mismatch, the pages render one explanation, and
neither the queue producer nor the worker starts. The process stays up
so the reason is visible instead of hiding in a crash loop.

The admin Encryption page gains a "Back up your encryption key" step:
key id and fingerprint (never the key), where the key lives per
platform, a check of a pasted copy that is compared and discarded, and
a confirmation bound to the active key id and fingerprint. The overview
gets a tile and admins get a dashboard banner while it is due.

HEALTHLOG_PLATFORM picks the platform the step opens on. Adds the
TrueNAS self-hosting page and key backup sections in the docs.
2026-10-03 18:06:28 +02:00

37 lines
1.0 KiB
TypeScript

"use client";
import { useQuery } from "@tanstack/react-query";
import { apiGet } from "@/lib/api/api-fetch";
import { queryKeys } from "@/lib/query-keys";
export type KeyBackupPlatform =
"compose" | "truenas" | "coolify" | "unraid" | "portainer";
/** `GET /api/admin/encryption/key-backup`; key id and fingerprint only. */
export interface KeyBackupStatus {
due: boolean;
activeKeyId: string;
fingerprint: string;
confirmedAt: string | null;
confirmedKeyId: string | null;
confirmedFingerprint: string | null;
confirmedBy: { id: string; email: string | null } | null;
platformHint: KeyBackupPlatform;
}
/**
* The key backup step's state. `enabled` lets the dashboard banner ask only
* for an admin; the route itself is admin-only and cookie-only.
*/
export function useKeyBackupStatus(enabled = true) {
return useQuery({
queryKey: queryKeys.adminEncryptionKeyBackup(),
queryFn: () =>
apiGet<KeyBackupStatus>("/api/admin/encryption/key-backup", {
credentials: "include",
}),
enabled,
staleTime: 60_000,
});
}