Files
NetMesh/application/vaultCsvExportCredentials.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

97 lines
3.5 KiB
TypeScript

import type { Host, SSHKey } from "../domain/models";
import { isEncryptedCredentialPlaceholder, sanitizeCredentialValue } from "../domain/credentials";
import { resolveVaultCsvHostKeyPath } from "../domain/vaultImport";
import { isPluginHostProtocol } from "../domain/pluginConnection";
import {
readExportableRememberedKeyPassphrases,
type DefaultKeyPassphraseVerificationRead,
} from "./defaultKeyPassphrases";
export interface VaultCsvCredentialOptions {
keyPathsById: Map<string, string>;
keyPassphrasesById: Map<string, string>;
keyPassphrases: Map<string, string>;
unreadablePassphraseCount: number;
}
export async function buildVaultCsvCredentialOptions(
hosts: Host[],
keys: SSHKey[],
readPassphrases?: (
keyPath: string,
) => Promise<DefaultKeyPassphraseVerificationRead>,
): Promise<VaultCsvCredentialOptions> {
const exportableHosts = hosts.filter((host) => (
host.protocol !== "serial" && !isPluginHostProtocol(host.protocol)
));
const referenceKeysById = new Map(keys.map((key) => [key.id, key] as const));
const keyPathsById = new Map(keys.flatMap((key) => (
key.source === "reference" && key.filePath?.trim()
? [[key.id, key.filePath.trim()] as const]
: []
)));
const keyPaths = Array.from(new Set(exportableHosts
.map((host) => resolveVaultCsvHostKeyPath(host, { keyPathsById }))
.filter(Boolean)));
const reads = new Map<string, DefaultKeyPassphraseVerificationRead>();
const readForExport = readPassphrases ?? (async (keyPath: string) => {
const remembered = await readExportableRememberedKeyPassphrases(keyPath, keys);
return {
...remembered,
present: remembered.unreadable || remembered.values.length > 0,
};
});
await Promise.all(keyPaths.map(async (keyPath) => {
try {
reads.set(keyPath, await readForExport(keyPath));
} catch {
reads.set(keyPath, { values: [], unreadable: true, present: true });
}
}));
const keyPassphrasesById = new Map<string, string>();
const keyPassphrases = new Map<string, string>();
const unreadableKeyPaths = new Set<string>();
for (const host of exportableHosts) {
const keyPath = resolveVaultCsvHostKeyPath(host, { keyPathsById });
if (!keyPath) continue;
const read = reads.get(keyPath);
const verifiedSideStoreValue = read?.values.length === 1 && !read.unreadable
? read.values[0]
: undefined;
const sideStoreIsAmbiguous = Boolean(
read && (read.unreadable || read.values.length > 1),
);
if (host.identityFileId) {
const key = referenceKeysById.get(host.identityFileId);
if (key?.source !== "reference" || key.savePassphrase === false) continue;
const keyValue = sanitizeCredentialValue(key.passphrase);
const hasSavedCredentialState = key.savePassphrase === true || Boolean(key.passphrase);
const selected = keyValue ?? (hasSavedCredentialState ? verifiedSideStoreValue : undefined);
if (selected) {
keyPassphrasesById.set(host.identityFileId, selected);
} else if (
isEncryptedCredentialPlaceholder(key.passphrase)
|| (hasSavedCredentialState && sideStoreIsAmbiguous)
) {
unreadableKeyPaths.add(keyPath);
}
continue;
}
const selected = verifiedSideStoreValue;
if (selected) {
keyPassphrases.set(keyPath, selected);
} else if (sideStoreIsAmbiguous || !reads.has(keyPath)) {
unreadableKeyPaths.add(keyPath);
}
}
return {
keyPathsById,
keyPassphrasesById,
keyPassphrases,
unreadablePassphraseCount: unreadableKeyPaths.size,
};
}