/** * Credential transfer between backends. * * Supports two modes: * - Cross-backend: copy credentials from active backend to a different backend * - Same-backend rewrite: re-read and re-write every credential in the active * backend, which forces age re-encryption with current recipients */ import type { CredentialBackend, BackendType, VaultConfig } from "../types.js"; import { createBackend, clearBackendCache } from "../backends/registry.js"; export interface TransferResult { readonly copied: number; readonly skipped: number; readonly failed: number; readonly failures: readonly TransferFailure[]; } export interface TransferFailure { readonly provider: string; readonly error: string; } /** * Copy all credentials from the source backend to the target backend. * * When source and target are the same backend type, every credential * is re-read and re-written (useful for age re-encryption after * recipient changes). * * Does not delete source data. */ export async function transferCredentials( source: CredentialBackend, target: CredentialBackend, ): Promise { const providers = await source.list(); let copied = 0; let skipped = 0; let failed = 0; const failures: TransferFailure[] = []; for (const providerId of providers) { try { const entry = await source.get(providerId); if (!entry) { skipped++; continue; } await target.set(providerId, entry); copied++; } catch (err) { failed++; const message = err instanceof Error ? err.message : String(err); failures.push({ provider: providerId, error: message }); } } return { copied, skipped, failed, failures }; } /** * Create a fresh (uncached) backend instance for the given type. * * Returns undefined if the backend cannot be created. */ export function createTargetBackend( targetType: BackendType, config: VaultConfig, ): CredentialBackend | undefined { try { // Clear cache so we get a fresh instance for the target // (important when target == source for same-backend rewrite) clearBackendCache(); return createBackend(targetType, config); } catch { return undefined; } } export const VALID_EXPORT_TARGETS: readonly BackendType[] = [ "age", "keychain", "passthrough", ]; export function isValidExportTarget(value: string): value is BackendType { return (VALID_EXPORT_TARGETS as readonly string[]).includes(value); }