passwd-sso-cli 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent-decrypt.d.ts +11 -0
- package/dist/commands/agent-decrypt.js +317 -0
- package/dist/commands/agent.d.ts +13 -0
- package/dist/commands/agent.js +116 -0
- package/dist/commands/api-key.d.ts +22 -0
- package/dist/commands/api-key.js +118 -0
- package/dist/commands/decrypt.d.ts +17 -0
- package/dist/commands/decrypt.js +108 -0
- package/dist/commands/env.d.ts +15 -0
- package/dist/commands/env.js +102 -0
- package/dist/commands/export.d.ts +7 -0
- package/dist/commands/export.js +99 -0
- package/dist/commands/generate.d.ts +11 -0
- package/dist/commands/generate.js +45 -0
- package/dist/commands/get.d.ts +8 -0
- package/dist/commands/get.js +73 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +66 -0
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.js +45 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +97 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +62 -0
- package/dist/commands/totp.d.ts +7 -0
- package/dist/commands/totp.js +57 -0
- package/dist/commands/unlock.d.ts +19 -0
- package/dist/commands/unlock.js +125 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +298 -0
- package/dist/lib/api-client.d.ts +22 -0
- package/dist/lib/api-client.js +145 -0
- package/dist/lib/blocked-keys.d.ts +2 -0
- package/dist/lib/blocked-keys.js +23 -0
- package/dist/lib/clipboard.d.ts +8 -0
- package/dist/lib/clipboard.js +79 -0
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/config.js +110 -0
- package/dist/lib/crypto-aad.d.ts +5 -0
- package/dist/lib/crypto-aad.js +44 -0
- package/dist/lib/crypto.d.ts +23 -0
- package/dist/lib/crypto.js +148 -0
- package/dist/lib/migrate.d.ts +8 -0
- package/dist/lib/migrate.js +87 -0
- package/dist/lib/openssh-key-parser.d.ts +17 -0
- package/dist/lib/openssh-key-parser.js +273 -0
- package/dist/lib/output.d.ts +10 -0
- package/dist/lib/output.js +36 -0
- package/dist/lib/paths.d.ts +17 -0
- package/dist/lib/paths.js +39 -0
- package/dist/lib/secrets-config.d.ts +31 -0
- package/dist/lib/secrets-config.js +48 -0
- package/dist/lib/ssh-agent-protocol.d.ts +56 -0
- package/dist/lib/ssh-agent-protocol.js +108 -0
- package/dist/lib/ssh-agent-socket.d.ts +20 -0
- package/dist/lib/ssh-agent-socket.js +187 -0
- package/dist/lib/ssh-key-agent.d.ts +54 -0
- package/dist/lib/ssh-key-agent.js +197 -0
- package/dist/lib/totp.d.ts +10 -0
- package/dist/lib/totp.js +31 -0
- package/dist/lib/vault-state.d.ts +15 -0
- package/dist/lib/vault-state.js +37 -0
- package/package.json +56 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso decrypt <id>` — Thin client that connects to the decrypt agent socket.
|
|
3
|
+
* Sends a decrypt request and prints the result to stdout.
|
|
4
|
+
* Used by Claude Code hooks to decrypt credentials without exposing them to the LLM.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* passwd-sso decrypt <entryId> --field password --mcp-client <mcpc_xxx>
|
|
8
|
+
* passwd-sso decrypt <entryId> --json --mcp-client <mcpc_xxx>
|
|
9
|
+
* passwd-sso decrypt abc123 --field password --mcp-client mcpc_xxx | curl --config - ...
|
|
10
|
+
*
|
|
11
|
+
* --json outputs all decrypted fields as JSON (ignores --field).
|
|
12
|
+
*/
|
|
13
|
+
import { createConnection } from "node:net";
|
|
14
|
+
export async function decryptCommand(id, options) {
|
|
15
|
+
const socketPath = process.env.PSSO_AGENT_SOCK;
|
|
16
|
+
if (!socketPath) {
|
|
17
|
+
process.stderr.write("Error: Agent not running. Start with:\n" +
|
|
18
|
+
" eval $(passwd-sso agent --decrypt --eval)\n");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (options.json && options.field) {
|
|
22
|
+
process.stderr.write("Error: --json and --field are mutually exclusive\n");
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const field = options.json ? "_json" : (options.field ?? "password");
|
|
27
|
+
const request = JSON.stringify({
|
|
28
|
+
entryId: id,
|
|
29
|
+
clientId: options.mcpClient,
|
|
30
|
+
field,
|
|
31
|
+
});
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
const socket = createConnection(socketPath);
|
|
34
|
+
let responseBuffer = "";
|
|
35
|
+
let settled = false;
|
|
36
|
+
const finish = (err) => {
|
|
37
|
+
if (settled)
|
|
38
|
+
return;
|
|
39
|
+
settled = true;
|
|
40
|
+
socket.destroy();
|
|
41
|
+
if (err)
|
|
42
|
+
reject(err);
|
|
43
|
+
else
|
|
44
|
+
resolve();
|
|
45
|
+
};
|
|
46
|
+
socket.on("connect", () => {
|
|
47
|
+
socket.write(request + "\n");
|
|
48
|
+
});
|
|
49
|
+
socket.on("data", (chunk) => {
|
|
50
|
+
responseBuffer += chunk.toString("utf-8");
|
|
51
|
+
const lines = responseBuffer.split("\n");
|
|
52
|
+
responseBuffer = lines.pop() ?? "";
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
if (!trimmed)
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
const res = JSON.parse(trimmed);
|
|
59
|
+
if (res.ok && res.value !== undefined) {
|
|
60
|
+
// Write value + newline to stdout.
|
|
61
|
+
// When piped (e.g., to curl), the trailing newline is typically
|
|
62
|
+
// consumed by the receiving process. For interactive use, it
|
|
63
|
+
// ensures the value is visible before the next shell prompt.
|
|
64
|
+
process.stdout.write(res.value + "\n");
|
|
65
|
+
finish();
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
process.stderr.write(`Error: ${res.error ?? "Decrypt failed"}\n`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
finish();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
process.stderr.write(`Error: Invalid response from agent: ${err instanceof Error ? err.message : "unknown"}\n`);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
finish();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
socket.on("end", () => {
|
|
81
|
+
finish();
|
|
82
|
+
});
|
|
83
|
+
socket.on("error", (err) => {
|
|
84
|
+
if (err.code === "ENOENT") {
|
|
85
|
+
process.stderr.write("Error: Agent socket not found. Start the agent with:\n" +
|
|
86
|
+
" eval $(passwd-sso agent --decrypt --eval)\n");
|
|
87
|
+
}
|
|
88
|
+
else if (err.code === "ECONNREFUSED") {
|
|
89
|
+
process.stderr.write("Error: Agent is not running. Start with:\n" +
|
|
90
|
+
" eval $(passwd-sso agent --decrypt --eval)\n");
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
process.stderr.write(`Error: Socket error: ${err.message}\n`);
|
|
94
|
+
}
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
finish(err);
|
|
97
|
+
});
|
|
98
|
+
socket.setTimeout(10_000);
|
|
99
|
+
socket.on("timeout", () => {
|
|
100
|
+
process.stderr.write("Error: Agent request timed out\n");
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
finish(new Error("timeout"));
|
|
103
|
+
});
|
|
104
|
+
}).catch(() => {
|
|
105
|
+
// exitCode already set above
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=decrypt.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso env` — Output vault secrets as environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Reads .passwd-sso-env.json, fetches & decrypts entries, then outputs
|
|
5
|
+
* env vars in the requested format (shell, dotenv, json).
|
|
6
|
+
*
|
|
7
|
+
* Requires the vault to be unlocked (encryption key in memory) OR
|
|
8
|
+
* an apiKey + PSSO_PASSPHRASE env var for non-interactive mode.
|
|
9
|
+
*/
|
|
10
|
+
interface EnvOptions {
|
|
11
|
+
config?: string;
|
|
12
|
+
format: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function envCommand(opts: EnvOptions): Promise<void>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso env` — Output vault secrets as environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Reads .passwd-sso-env.json, fetches & decrypts entries, then outputs
|
|
5
|
+
* env vars in the requested format (shell, dotenv, json).
|
|
6
|
+
*
|
|
7
|
+
* Requires the vault to be unlocked (encryption key in memory) OR
|
|
8
|
+
* an apiKey + PSSO_PASSPHRASE env var for non-interactive mode.
|
|
9
|
+
*/
|
|
10
|
+
import { loadSecretsConfig, getPasswordPath } from "../lib/secrets-config.js";
|
|
11
|
+
import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
|
|
12
|
+
import { autoUnlockIfNeeded } from "./unlock.js";
|
|
13
|
+
import { getToken } from "../lib/api-client.js";
|
|
14
|
+
import { decryptData } from "../lib/crypto.js";
|
|
15
|
+
import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
|
|
16
|
+
import { BLOCKED_KEYS } from "../lib/blocked-keys.js";
|
|
17
|
+
import * as output from "../lib/output.js";
|
|
18
|
+
export async function envCommand(opts) {
|
|
19
|
+
let config;
|
|
20
|
+
try {
|
|
21
|
+
config = loadSecretsConfig(opts.config);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
output.error(err instanceof Error ? err.message : "Failed to load config.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const useV1 = !!config.apiKey;
|
|
28
|
+
const baseUrl = config.server.replace(/\/$/, "");
|
|
29
|
+
// Determine auth header
|
|
30
|
+
let authHeader;
|
|
31
|
+
if (config.apiKey) {
|
|
32
|
+
authHeader = `Bearer ${config.apiKey}`;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const token = await getToken();
|
|
36
|
+
if (!token) {
|
|
37
|
+
output.error("Not logged in. Run `passwd-sso login` first, or set apiKey in config.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
authHeader = `Bearer ${token}`;
|
|
41
|
+
}
|
|
42
|
+
// Auto-unlock with PSSO_PASSPHRASE if needed
|
|
43
|
+
if (!await autoUnlockIfNeeded()) {
|
|
44
|
+
output.error("Vault is not unlocked. Run `passwd-sso unlock` first, or set PSSO_PASSPHRASE.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const encryptionKey = getEncryptionKey();
|
|
48
|
+
const userId = getUserId();
|
|
49
|
+
const result = {};
|
|
50
|
+
const entries = Object.entries(config.secrets);
|
|
51
|
+
for (const [envName, mapping] of entries) {
|
|
52
|
+
if (BLOCKED_KEYS.has(envName.toUpperCase())) {
|
|
53
|
+
output.error(`Blocked: cannot output '${envName}' (security restriction)`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const path = getPasswordPath(mapping.entry, useV1);
|
|
57
|
+
const url = `${baseUrl}${path}`;
|
|
58
|
+
const res = await fetch(url, {
|
|
59
|
+
headers: { Authorization: authHeader },
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
output.error(`Failed to fetch entry ${mapping.entry}: HTTP ${res.status}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const data = (await res.json());
|
|
66
|
+
let additionalData;
|
|
67
|
+
if (data.aadVersion && data.aadVersion >= 1 && userId) {
|
|
68
|
+
additionalData = buildPersonalEntryAAD(userId, data.id);
|
|
69
|
+
}
|
|
70
|
+
const decrypted = await decryptData(data.encryptedBlob, encryptionKey, additionalData);
|
|
71
|
+
const blob = JSON.parse(decrypted);
|
|
72
|
+
const value = blob[mapping.field];
|
|
73
|
+
if (value === undefined || value === null) {
|
|
74
|
+
output.warn(`Field '${mapping.field}' not found in entry ${mapping.entry}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
result[envName] = String(value);
|
|
78
|
+
}
|
|
79
|
+
// Output
|
|
80
|
+
switch (opts.format) {
|
|
81
|
+
case "json":
|
|
82
|
+
console.log(JSON.stringify(result, null, 2));
|
|
83
|
+
break;
|
|
84
|
+
case "dotenv":
|
|
85
|
+
for (const [k, v] of Object.entries(result)) {
|
|
86
|
+
console.log(`${k}=${shellEscape(v)}`);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case "shell":
|
|
90
|
+
default:
|
|
91
|
+
for (const [k, v] of Object.entries(result)) {
|
|
92
|
+
console.log(`export ${k}=${shellEscape(v)}`);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function shellEscape(s) {
|
|
98
|
+
if (/^[a-zA-Z0-9._\-/:@]+$/.test(s))
|
|
99
|
+
return s;
|
|
100
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso export` — Export vault entries to JSON or CSV.
|
|
3
|
+
*/
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
6
|
+
import { apiRequest } from "../lib/api-client.js";
|
|
7
|
+
import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
|
|
8
|
+
import { decryptData } from "../lib/crypto.js";
|
|
9
|
+
import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
|
|
10
|
+
import * as output from "../lib/output.js";
|
|
11
|
+
function escapeCSV(value) {
|
|
12
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
13
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
export async function exportCommand(options) {
|
|
18
|
+
const key = getEncryptionKey();
|
|
19
|
+
if (!key) {
|
|
20
|
+
output.error("Vault is locked. Run `unlock` first.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const format = options.format ?? "json";
|
|
24
|
+
if (format !== "json" && format !== "csv") {
|
|
25
|
+
output.error("Format must be 'json' or 'csv'.");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const res = await apiRequest("/api/passwords?include=blob");
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
output.error(`Failed to fetch entries: ${res.status}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const entries = res.data;
|
|
34
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
35
|
+
output.info("No entries to export.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Validate and resolve output path before processing
|
|
39
|
+
const outputPath = options.output ? resolve(options.output) : undefined;
|
|
40
|
+
if (!outputPath) {
|
|
41
|
+
output.warn("Decrypted passwords will be printed to stdout.");
|
|
42
|
+
}
|
|
43
|
+
if (outputPath && existsSync(outputPath) && lstatSync(outputPath).isSymbolicLink()) {
|
|
44
|
+
output.error("Output path is a symlink — refusing to write.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const userId = getUserId();
|
|
48
|
+
const decrypted = [];
|
|
49
|
+
let failedCount = 0;
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
try {
|
|
52
|
+
const aad = entry.aadVersion >= 1 && userId
|
|
53
|
+
? buildPersonalEntryAAD(userId, entry.id)
|
|
54
|
+
: undefined;
|
|
55
|
+
const plaintext = await decryptData(entry.encryptedBlob, key, aad);
|
|
56
|
+
decrypted.push(JSON.parse(plaintext));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
failedCount++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (failedCount > 0) {
|
|
63
|
+
output.warn(`${failedCount} entries failed to decrypt and were skipped.`);
|
|
64
|
+
}
|
|
65
|
+
if (format === "json") {
|
|
66
|
+
const out = JSON.stringify(decrypted, null, 2);
|
|
67
|
+
if (outputPath) {
|
|
68
|
+
const { writeFileSync } = await import("node:fs");
|
|
69
|
+
writeFileSync(outputPath, out, { encoding: "utf-8", mode: 0o600 });
|
|
70
|
+
output.success(`Exported ${decrypted.length} entries to ${outputPath}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(out);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const headers = ["title", "username", "password", "url", "notes", "totp"];
|
|
78
|
+
const csvRows = [headers.join(",")];
|
|
79
|
+
for (const entry of decrypted) {
|
|
80
|
+
csvRows.push(headers.map((h) => {
|
|
81
|
+
const val = entry[h];
|
|
82
|
+
if (h === "totp" && val && typeof val === "object") {
|
|
83
|
+
return escapeCSV(val.secret ?? "");
|
|
84
|
+
}
|
|
85
|
+
return escapeCSV(String(val ?? ""));
|
|
86
|
+
}).join(","));
|
|
87
|
+
}
|
|
88
|
+
const csvOut = csvRows.join("\n");
|
|
89
|
+
if (outputPath) {
|
|
90
|
+
const { writeFileSync } = await import("node:fs");
|
|
91
|
+
writeFileSync(outputPath, csvOut, { encoding: "utf-8", mode: 0o600 });
|
|
92
|
+
output.success(`Exported ${decrypted.length} entries to ${outputPath}`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(csvOut);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso generate` — Generate a secure password (offline).
|
|
3
|
+
*/
|
|
4
|
+
export declare function generateCommand(options: {
|
|
5
|
+
length?: number;
|
|
6
|
+
noUppercase?: boolean;
|
|
7
|
+
noDigits?: boolean;
|
|
8
|
+
noSymbols?: boolean;
|
|
9
|
+
copy?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso generate` — Generate a secure password (offline).
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { copyToClipboard } from "../lib/clipboard.js";
|
|
6
|
+
import * as output from "../lib/output.js";
|
|
7
|
+
const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
8
|
+
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
9
|
+
const DIGITS = "0123456789";
|
|
10
|
+
const SYMBOLS = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~";
|
|
11
|
+
export async function generateCommand(options) {
|
|
12
|
+
const length = options.length ?? 20;
|
|
13
|
+
if (length < 4 || length > 128) {
|
|
14
|
+
output.error("Length must be between 4 and 128.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let charset = LOWERCASE;
|
|
18
|
+
if (!options.noUppercase)
|
|
19
|
+
charset += UPPERCASE;
|
|
20
|
+
if (!options.noDigits)
|
|
21
|
+
charset += DIGITS;
|
|
22
|
+
if (!options.noSymbols)
|
|
23
|
+
charset += SYMBOLS;
|
|
24
|
+
// Rejection sampling to avoid modulo bias
|
|
25
|
+
const limit = Math.floor(0x100000000 / charset.length) * charset.length;
|
|
26
|
+
const password = Array.from({ length }, () => {
|
|
27
|
+
let val;
|
|
28
|
+
do {
|
|
29
|
+
val = randomBytes(4).readUInt32BE(0);
|
|
30
|
+
} while (val >= limit);
|
|
31
|
+
return charset[val % charset.length];
|
|
32
|
+
}).join("");
|
|
33
|
+
if (options.json) {
|
|
34
|
+
output.json({ password });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (options.copy) {
|
|
38
|
+
await copyToClipboard(password);
|
|
39
|
+
output.success("Password generated and copied to clipboard (auto-clears in 30s).");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(password);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso get <id>` — Show a single entry (decrypted blob).
|
|
3
|
+
*/
|
|
4
|
+
import { apiRequest } from "../lib/api-client.js";
|
|
5
|
+
import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
|
|
6
|
+
import { decryptData } from "../lib/crypto.js";
|
|
7
|
+
import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
|
|
8
|
+
import { copyToClipboard } from "../lib/clipboard.js";
|
|
9
|
+
import * as output from "../lib/output.js";
|
|
10
|
+
export async function getCommand(id, options) {
|
|
11
|
+
const key = getEncryptionKey();
|
|
12
|
+
if (!key) {
|
|
13
|
+
output.error("Vault is locked. Run `unlock` first.");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const userId = getUserId();
|
|
17
|
+
const res = await apiRequest(`/api/passwords/${id}`);
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
output.error(`Entry not found: ${res.status}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const entry = res.data;
|
|
23
|
+
try {
|
|
24
|
+
const aad = entry.aadVersion >= 1 && userId
|
|
25
|
+
? buildPersonalEntryAAD(userId, entry.id)
|
|
26
|
+
: undefined;
|
|
27
|
+
const plaintext = await decryptData(entry.encryptedBlob, key, aad);
|
|
28
|
+
const blob = JSON.parse(plaintext);
|
|
29
|
+
if (options.field) {
|
|
30
|
+
const value = blob[options.field];
|
|
31
|
+
if (value === undefined) {
|
|
32
|
+
output.error(`Field "${options.field}" not found.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const str = typeof value === "object" && value !== null
|
|
36
|
+
? JSON.stringify(value)
|
|
37
|
+
: String(value);
|
|
38
|
+
if (options.copy) {
|
|
39
|
+
await copyToClipboard(str);
|
|
40
|
+
output.success(`Copied ${options.field} to clipboard (auto-clears in 30s).`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log(str);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (options.json) {
|
|
48
|
+
output.json(blob);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Formatted display
|
|
52
|
+
if (blob.title)
|
|
53
|
+
console.log(`Title: ${blob.title}`);
|
|
54
|
+
if (blob.username)
|
|
55
|
+
console.log(`Username: ${blob.username}`);
|
|
56
|
+
if (blob.password)
|
|
57
|
+
console.log(`Password: ${output.masked(blob.password)}`);
|
|
58
|
+
if (blob.url)
|
|
59
|
+
console.log(`URL: ${blob.url}`);
|
|
60
|
+
if (blob.notes)
|
|
61
|
+
console.log(`Notes: ${blob.notes}`);
|
|
62
|
+
if (blob.totp)
|
|
63
|
+
console.log(`TOTP: (configured)`);
|
|
64
|
+
if (options.copy && blob.password) {
|
|
65
|
+
await copyToClipboard(blob.password);
|
|
66
|
+
output.success("Password copied to clipboard (auto-clears in 30s).");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
output.error("Failed to decrypt entry.");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=get.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso list` — List vault entries (decrypted overview).
|
|
3
|
+
*/
|
|
4
|
+
import { apiRequest } from "../lib/api-client.js";
|
|
5
|
+
import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
|
|
6
|
+
import { decryptData } from "../lib/crypto.js";
|
|
7
|
+
import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
|
|
8
|
+
import * as output from "../lib/output.js";
|
|
9
|
+
export async function listCommand(options) {
|
|
10
|
+
const key = getEncryptionKey();
|
|
11
|
+
if (!key) {
|
|
12
|
+
output.error("Vault is locked. Run `unlock` first.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const userId = getUserId();
|
|
16
|
+
const res = await apiRequest("/api/passwords");
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
output.error(`Failed to fetch entries: ${res.status}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const entries = res.data;
|
|
22
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
23
|
+
output.info("No entries found.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const decrypted = [];
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
try {
|
|
29
|
+
const aad = entry.aadVersion >= 1 && userId
|
|
30
|
+
? buildPersonalEntryAAD(userId, entry.id)
|
|
31
|
+
: undefined;
|
|
32
|
+
const plaintext = await decryptData(entry.encryptedOverview, key, aad);
|
|
33
|
+
const overview = JSON.parse(plaintext);
|
|
34
|
+
decrypted.push({
|
|
35
|
+
id: entry.id,
|
|
36
|
+
title: overview.title ?? "(untitled)",
|
|
37
|
+
username: overview.username ?? "",
|
|
38
|
+
url: overview.urlHost ?? "",
|
|
39
|
+
type: entry.entryType ?? "LOGIN",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
decrypted.push({
|
|
44
|
+
id: entry.id,
|
|
45
|
+
title: "(decryption failed)",
|
|
46
|
+
username: "",
|
|
47
|
+
url: "",
|
|
48
|
+
type: entry.entryType ?? "?",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (options.json) {
|
|
53
|
+
output.json(decrypted);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
output.table(["ID", "Type", "Title", "Username", "URL"], decrypted.map((d) => [
|
|
57
|
+
d.id,
|
|
58
|
+
d.type,
|
|
59
|
+
d.title,
|
|
60
|
+
d.username,
|
|
61
|
+
d.url,
|
|
62
|
+
]));
|
|
63
|
+
output.info(`${decrypted.length} entries`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso login` — Configure server URL and Bearer token.
|
|
3
|
+
*/
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { loadConfig, saveConfig, saveToken } from "../lib/config.js";
|
|
6
|
+
import { setTokenCache } from "../lib/api-client.js";
|
|
7
|
+
import * as output from "../lib/output.js";
|
|
8
|
+
async function prompt(question) {
|
|
9
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
rl.question(question, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
resolve(answer.trim());
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function loginCommand() {
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
const serverUrl = await prompt(`Server URL${config.serverUrl ? ` [${config.serverUrl}]` : ""}: `);
|
|
20
|
+
if (serverUrl) {
|
|
21
|
+
config.serverUrl = serverUrl.replace(/\/$/, "");
|
|
22
|
+
}
|
|
23
|
+
if (!config.serverUrl) {
|
|
24
|
+
output.error("Server URL is required.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
output.info("Open your browser and go to the token page to generate a CLI token.");
|
|
28
|
+
output.info(` ${config.serverUrl}/dashboard/settings`);
|
|
29
|
+
console.log();
|
|
30
|
+
const token = await prompt("Paste your token: ");
|
|
31
|
+
if (!token) {
|
|
32
|
+
output.error("Token is required.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Approximate token expiry (15 min from now)
|
|
36
|
+
config.tokenExpiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString();
|
|
37
|
+
saveConfig(config);
|
|
38
|
+
const storage = await saveToken(token);
|
|
39
|
+
setTokenCache(token, config.tokenExpiresAt);
|
|
40
|
+
if (storage === "file") {
|
|
41
|
+
output.warn("Token saved to file (plaintext). Consider installing keytar for OS keychain storage.");
|
|
42
|
+
}
|
|
43
|
+
output.success(`Logged in to ${config.serverUrl}`);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `passwd-sso run` — Inject vault secrets into a command's environment.
|
|
3
|
+
*
|
|
4
|
+
* Uses child_process.execFile (NOT shell) for security.
|
|
5
|
+
* Blocks certain dangerous env var names from being overwritten.
|
|
6
|
+
*/
|
|
7
|
+
interface RunOptions {
|
|
8
|
+
config?: string;
|
|
9
|
+
command: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function runCommand(opts: RunOptions): Promise<void>;
|
|
12
|
+
export {};
|