@indigoai-us/hq-cli 5.77.11 → 5.77.12

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.
@@ -0,0 +1,69 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("./cognito-session.js", () => ({
4
+ ensureCognitoToken: vi.fn(async () => "cognito-token"),
5
+ }));
6
+
7
+ import { ensureCognitoToken } from "./cognito-session.js";
8
+ import {
9
+ assertCognitoOnlyCommand,
10
+ HQ_API_KEY_PREFIX,
11
+ peekHqApiKey,
12
+ resolveVaultCredential,
13
+ } from "./resolve-vault-credential.js";
14
+
15
+ afterEach(() => {
16
+ delete process.env.HQ_API_KEY;
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ describe("peekHqApiKey", () => {
21
+ it("returns undefined when unset or blank", () => {
22
+ expect(peekHqApiKey()).toBeUndefined();
23
+ process.env.HQ_API_KEY = " ";
24
+ expect(peekHqApiKey()).toBeUndefined();
25
+ });
26
+
27
+ it("returns trimmed value when set", () => {
28
+ process.env.HQ_API_KEY = " hqk_abc ";
29
+ expect(peekHqApiKey()).toBe("hqk_abc");
30
+ });
31
+ });
32
+
33
+ describe("resolveVaultCredential", () => {
34
+ it("uses Cognito when HQ_API_KEY is unset", async () => {
35
+ const cred = await resolveVaultCredential();
36
+ expect(cred).toEqual({ kind: "cognito", token: "cognito-token" });
37
+ expect(ensureCognitoToken).toHaveBeenCalledOnce();
38
+ });
39
+
40
+ it("returns api-key credential for hqk_ without calling Cognito", async () => {
41
+ process.env.HQ_API_KEY = `${HQ_API_KEY_PREFIX}valid`;
42
+ const cred = await resolveVaultCredential();
43
+ expect(cred).toEqual({
44
+ kind: "api-key",
45
+ token: `${HQ_API_KEY_PREFIX}valid`,
46
+ });
47
+ expect(ensureCognitoToken).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it("fails closed on invalid prefix (no Cognito fallback)", async () => {
51
+ process.env.HQ_API_KEY = "hqk_totally_invalid".replace("hqk_", "bad_");
52
+ process.env.HQ_API_KEY = "not_a_vault_key";
53
+ await expect(resolveVaultCredential()).rejects.toThrow(/must start with/);
54
+ expect(ensureCognitoToken).not.toHaveBeenCalled();
55
+ });
56
+ });
57
+
58
+ describe("assertCognitoOnlyCommand", () => {
59
+ it("no-ops when HQ_API_KEY unset", () => {
60
+ expect(() => assertCognitoOnlyCommand("secrets list")).not.toThrow();
61
+ });
62
+
63
+ it("throws when HQ_API_KEY is set", () => {
64
+ process.env.HQ_API_KEY = `${HQ_API_KEY_PREFIX}x`;
65
+ expect(() => assertCognitoOnlyCommand("secrets list")).toThrow(
66
+ /not supported for API keys/,
67
+ );
68
+ });
69
+ });
@@ -0,0 +1,60 @@
1
+ import { ensureCognitoToken } from "./cognito-session.js";
2
+
3
+ /** Vault API keys issued by `hq api-keys create` (hq-pro). */
4
+ export const HQ_API_KEY_PREFIX = "hqk_";
5
+
6
+ export type VaultCredential =
7
+ | { kind: "api-key"; token: string }
8
+ | { kind: "cognito"; token: string };
9
+
10
+ /**
11
+ * Raw HQ_API_KEY from the environment, trimmed. Undefined when unset/empty.
12
+ * Does not validate prefix — use {@link resolveVaultCredential} for that.
13
+ */
14
+ export function peekHqApiKey(): string | undefined {
15
+ const raw = process.env.HQ_API_KEY;
16
+ if (raw === undefined) return undefined;
17
+ const trimmed = raw.trim();
18
+ return trimmed.length > 0 ? trimmed : undefined;
19
+ }
20
+
21
+ /**
22
+ * Resolve vault auth for CLI commands.
23
+ *
24
+ * When `HQ_API_KEY` is set it is authoritative: must be a vault key (`hqk_…`)
25
+ * and Cognito is never used as a fallback (fail-closed). When unset, uses the
26
+ * cached Cognito session (interactive login if needed).
27
+ */
28
+ export async function resolveVaultCredential(options?: {
29
+ interactive?: boolean;
30
+ }): Promise<VaultCredential> {
31
+ const apiKey = peekHqApiKey();
32
+ if (apiKey !== undefined) {
33
+ if (!apiKey.startsWith(HQ_API_KEY_PREFIX)) {
34
+ throw new Error(
35
+ `HQ_API_KEY must start with '${HQ_API_KEY_PREFIX}' (vault API key). ` +
36
+ `Got a value that is not a vault key — refusing to fall back to Cognito. ` +
37
+ `Unset HQ_API_KEY to use your session, or create a key with \`hq api-keys create\`.`,
38
+ );
39
+ }
40
+ return { kind: "api-key", token: apiKey };
41
+ }
42
+
43
+ const token = await ensureCognitoToken({
44
+ interactive: options?.interactive,
45
+ });
46
+ return { kind: "cognito", token };
47
+ }
48
+
49
+ /**
50
+ * Throw when HQ_API_KEY is set but the command only supports Cognito sessions
51
+ * (list, set, ACL, api-keys admin, etc.).
52
+ */
53
+ export function assertCognitoOnlyCommand(commandLabel: string): void {
54
+ if (peekHqApiKey() === undefined) return;
55
+ throw new Error(
56
+ `HQ_API_KEY is set; '${commandLabel}' is not supported for API keys. ` +
57
+ `API keys support scoped secret reads via \`hq secrets get\`, \`hq secrets exec\`, ` +
58
+ `and \`hq secrets env\`. Unset HQ_API_KEY to use your Cognito session.`,
59
+ );
60
+ }