passwd-sso-cli 0.4.46 → 0.4.47
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/env.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Requires the vault to be unlocked (encryption key in memory) OR
|
|
8
8
|
* an apiKey + PSSO_PASSPHRASE env var for non-interactive mode.
|
|
9
9
|
*/
|
|
10
|
-
import { loadSecretsConfig, getPasswordPath } from "../lib/secrets-config.js";
|
|
10
|
+
import { loadSecretsConfig, getPasswordPath, getSecretsServerUrl } from "../lib/secrets-config.js";
|
|
11
11
|
import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
|
|
12
12
|
import { autoUnlockIfNeeded } from "./unlock.js";
|
|
13
13
|
import { getToken } from "../lib/api-client.js";
|
|
@@ -22,10 +22,17 @@ export async function envCommand(opts) {
|
|
|
22
22
|
}
|
|
23
23
|
catch (err) {
|
|
24
24
|
output.error(err instanceof Error ? err.message : "Failed to load config.");
|
|
25
|
-
|
|
25
|
+
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
const useV1 = !!config.apiKey;
|
|
28
|
-
|
|
28
|
+
let baseUrl;
|
|
29
|
+
try {
|
|
30
|
+
baseUrl = getSecretsServerUrl();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
output.error(err instanceof Error ? err.message : "Failed to resolve server URL.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
29
36
|
// Determine auth header
|
|
30
37
|
let authHeader;
|
|
31
38
|
if (config.apiKey) {
|
|
@@ -35,14 +42,14 @@ export async function envCommand(opts) {
|
|
|
35
42
|
const token = await getToken();
|
|
36
43
|
if (!token) {
|
|
37
44
|
output.error("Not logged in. Run `passwd-sso login` first, or set apiKey in config.");
|
|
38
|
-
|
|
45
|
+
process.exit(1);
|
|
39
46
|
}
|
|
40
47
|
authHeader = `Bearer ${token}`;
|
|
41
48
|
}
|
|
42
49
|
// Auto-unlock with PSSO_PASSPHRASE if needed
|
|
43
50
|
if (!await autoUnlockIfNeeded()) {
|
|
44
51
|
output.error("Vault is not unlocked. Run `passwd-sso unlock` first, or set PSSO_PASSPHRASE.");
|
|
45
|
-
|
|
52
|
+
process.exit(1);
|
|
46
53
|
}
|
|
47
54
|
const encryptionKey = getEncryptionKey();
|
|
48
55
|
const userId = getUserId();
|
package/dist/commands/run.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Blocks certain dangerous env var names from being overwritten.
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
|
-
import { loadSecretsConfig, getPasswordPath } from "../lib/secrets-config.js";
|
|
8
|
+
import { loadSecretsConfig, getPasswordPath, getSecretsServerUrl } from "../lib/secrets-config.js";
|
|
9
9
|
import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
|
|
10
10
|
import { autoUnlockIfNeeded } from "./unlock.js";
|
|
11
11
|
import { getToken } from "../lib/api-client.js";
|
|
@@ -27,7 +27,14 @@ export async function runCommand(opts) {
|
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
const useV1 = !!config.apiKey;
|
|
30
|
-
|
|
30
|
+
let baseUrl;
|
|
31
|
+
try {
|
|
32
|
+
baseUrl = getSecretsServerUrl();
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
output.error(err instanceof Error ? err.message : "Failed to resolve server URL.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
31
38
|
let authHeader;
|
|
32
39
|
if (config.apiKey) {
|
|
33
40
|
authHeader = `Bearer ${config.apiKey}`;
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Schema:
|
|
5
5
|
* {
|
|
6
|
-
* "server": "https://...",
|
|
7
6
|
* "apiKey?": "api_...", // optional — uses /api/v1/ path
|
|
8
7
|
* "secrets": {
|
|
9
8
|
* "ENV_VAR_NAME": { "entry": "<entryId>", "field": "password" }
|
|
@@ -19,11 +18,11 @@ export interface SecretMapping {
|
|
|
19
18
|
field: string;
|
|
20
19
|
}
|
|
21
20
|
export interface SecretsConfig {
|
|
22
|
-
server: string;
|
|
23
21
|
apiKey?: string;
|
|
24
22
|
secrets: Record<string, SecretMapping>;
|
|
25
23
|
}
|
|
26
24
|
export declare function loadSecretsConfig(configPath?: string): SecretsConfig;
|
|
25
|
+
export declare function getSecretsServerUrl(): string;
|
|
27
26
|
/**
|
|
28
27
|
* Returns the API path for fetching a single password entry.
|
|
29
28
|
* If apiKey is configured, use the public /api/v1/ path.
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Schema:
|
|
5
5
|
* {
|
|
6
|
-
* "server": "https://...",
|
|
7
6
|
* "apiKey?": "api_...", // optional — uses /api/v1/ path
|
|
8
7
|
* "secrets": {
|
|
9
8
|
* "ENV_VAR_NAME": { "entry": "<entryId>", "field": "password" }
|
|
@@ -16,6 +15,11 @@
|
|
|
16
15
|
*/
|
|
17
16
|
import { readFileSync, existsSync } from "node:fs";
|
|
18
17
|
import { resolve } from "node:path";
|
|
18
|
+
import { loadConfig } from "./config.js";
|
|
19
|
+
import { validateServerUrl } from "./oauth.js";
|
|
20
|
+
function isPlaceholderEntryId(entryId) {
|
|
21
|
+
return entryId === "dummy-entry-id" || /^<[^>]+>$/.test(entryId);
|
|
22
|
+
}
|
|
19
23
|
export function loadSecretsConfig(configPath) {
|
|
20
24
|
const filePath = configPath
|
|
21
25
|
? resolve(configPath)
|
|
@@ -25,14 +29,41 @@ export function loadSecretsConfig(configPath) {
|
|
|
25
29
|
}
|
|
26
30
|
const raw = readFileSync(filePath, "utf-8");
|
|
27
31
|
const parsed = JSON.parse(raw);
|
|
28
|
-
if (!parsed.server || typeof parsed.server !== "string") {
|
|
29
|
-
throw new Error("Config file must have a 'server' field.");
|
|
30
|
-
}
|
|
31
32
|
if (!parsed.secrets || typeof parsed.secrets !== "object") {
|
|
32
33
|
throw new Error("Config file must have a 'secrets' field.");
|
|
33
34
|
}
|
|
35
|
+
for (const [envName, mapping] of Object.entries(parsed.secrets)) {
|
|
36
|
+
if (!mapping || typeof mapping !== "object") {
|
|
37
|
+
throw new Error(`Secret mapping for '${envName}' must be an object.`);
|
|
38
|
+
}
|
|
39
|
+
const rawMapping = mapping;
|
|
40
|
+
if (typeof rawMapping.entry !== "string" || rawMapping.entry.trim().length === 0) {
|
|
41
|
+
throw new Error(`Secret mapping for '${envName}' must have a non-empty 'entry' string.`);
|
|
42
|
+
}
|
|
43
|
+
if (typeof rawMapping.field !== "string" || rawMapping.field.trim().length === 0) {
|
|
44
|
+
throw new Error(`Secret mapping for '${envName}' must have a non-empty 'field' string.`);
|
|
45
|
+
}
|
|
46
|
+
const entry = rawMapping.entry.trim();
|
|
47
|
+
const field = rawMapping.field.trim();
|
|
48
|
+
if (isPlaceholderEntryId(entry)) {
|
|
49
|
+
throw new Error(`Secret mapping for '${envName}' uses placeholder entry ID "${entry}". Replace it with a real vault entry ID.`);
|
|
50
|
+
}
|
|
51
|
+
// Preserve unknown keys (e.g. user-added comment fields) while normalising entry/field.
|
|
52
|
+
parsed.secrets[envName] = { ...rawMapping, entry, field };
|
|
53
|
+
}
|
|
34
54
|
return parsed;
|
|
35
55
|
}
|
|
56
|
+
export function getSecretsServerUrl() {
|
|
57
|
+
const { serverUrl } = loadConfig();
|
|
58
|
+
if (!serverUrl) {
|
|
59
|
+
throw new Error("Server URL not configured. Run `passwd-sso login -s <server-url>` once to configure it.");
|
|
60
|
+
}
|
|
61
|
+
// Defense-in-depth: re-validate the persisted URL before issuing a fetch
|
|
62
|
+
// with a Bearer token. Login validates at write time, but a hand-edited
|
|
63
|
+
// config file would otherwise reach fetch() unchecked.
|
|
64
|
+
validateServerUrl(serverUrl);
|
|
65
|
+
return serverUrl.replace(/\/$/, "");
|
|
66
|
+
}
|
|
36
67
|
/**
|
|
37
68
|
* Returns the API path for fetching a single password entry.
|
|
38
69
|
* If apiKey is configured, use the public /api/v1/ path.
|