@indigoai-us/hq-cloud 5.18.1 → 5.19.1
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/cli/invite.js +4 -1
- package/dist/cli/invite.js.map +1 -1
- package/dist/cli/invite.test.js +3 -0
- package/dist/cli/invite.test.js.map +1 -1
- package/dist/cli/promote.js +3 -0
- package/dist/cli/promote.js.map +1 -1
- package/dist/cli/share.test.js +12 -1
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.test.js +12 -0
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/client-info.d.ts +44 -0
- package/dist/client-info.d.ts.map +1 -0
- package/dist/client-info.js +112 -0
- package/dist/client-info.js.map +1 -0
- package/dist/client-info.test.d.ts +11 -0
- package/dist/client-info.test.d.ts.map +1 -0
- package/dist/client-info.test.js +168 -0
- package/dist/client-info.test.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +117 -20
- package/dist/context.js.map +1 -1
- package/dist/context.test.js +63 -14
- package/dist/context.test.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vault-client.d.ts +25 -0
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +33 -0
- package/dist/vault-client.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/invite.test.ts +3 -0
- package/src/cli/invite.ts +4 -1
- package/src/cli/promote.ts +3 -0
- package/src/cli/share.test.ts +12 -1
- package/src/cli/sync.test.ts +12 -0
- package/src/client-info.test.ts +214 -0
- package/src/client-info.ts +121 -0
- package/src/context.test.ts +73 -14
- package/src/context.ts +126 -22
- package/src/index.ts +12 -0
- package/src/types.ts +23 -0
- package/src/vault-client.ts +42 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client identification — every HQ client that talks to hq-cloud-api should
|
|
3
|
+
* stamp its name and version on outbound requests so the server can attribute
|
|
4
|
+
* traffic, gate on minimum versions, and surface deprecation warnings.
|
|
5
|
+
*
|
|
6
|
+
* The CLI in particular sends a third header (`x-hq-core-version`) when it's
|
|
7
|
+
* invoked from inside an hq-core checkout, so the server sees which scaffold
|
|
8
|
+
* generation the user is running against.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
export const HEADER_CLIENT_NAME = "x-hq-client-name";
|
|
13
|
+
export const HEADER_CLIENT_VERSION = "x-hq-client-version";
|
|
14
|
+
export const HEADER_HQ_CORE_VERSION = "x-hq-core-version";
|
|
15
|
+
/**
|
|
16
|
+
* Build the set of `x-hq-*` headers (plus a derived `User-Agent`) for a
|
|
17
|
+
* ClientInfo. Returns an empty object when info is undefined so callers can
|
|
18
|
+
* spread the result unconditionally.
|
|
19
|
+
*/
|
|
20
|
+
export function buildClientHeaders(info) {
|
|
21
|
+
if (!info)
|
|
22
|
+
return {};
|
|
23
|
+
const headers = {
|
|
24
|
+
"User-Agent": `${info.name}/${info.version}`,
|
|
25
|
+
[HEADER_CLIENT_NAME]: info.name,
|
|
26
|
+
[HEADER_CLIENT_VERSION]: info.version,
|
|
27
|
+
};
|
|
28
|
+
if (info.hqCoreVersion) {
|
|
29
|
+
headers[HEADER_HQ_CORE_VERSION] = info.hqCoreVersion;
|
|
30
|
+
}
|
|
31
|
+
if (info.extra) {
|
|
32
|
+
for (const [k, v] of Object.entries(info.extra)) {
|
|
33
|
+
headers[`x-hq-client-${k}`] = v;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return headers;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build a ClientInfo from a parsed package.json. Most consumers will call this
|
|
40
|
+
* once at startup and pass the result into every VaultServiceConfig.
|
|
41
|
+
*/
|
|
42
|
+
export function clientInfoFromPackage(pkg) {
|
|
43
|
+
if (typeof pkg.name !== "string" || pkg.name.length === 0) {
|
|
44
|
+
throw new Error("clientInfoFromPackage: package.json is missing a name");
|
|
45
|
+
}
|
|
46
|
+
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
|
|
47
|
+
throw new Error("clientInfoFromPackage: package.json is missing a version");
|
|
48
|
+
}
|
|
49
|
+
return { name: pkg.name, version: pkg.version };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Walk upward from `startDir` looking for `core/core.yaml`. When found, parse
|
|
53
|
+
* out the `hqVersion` field and return it. Returns undefined if we never find
|
|
54
|
+
* an hq-core checkout — i.e. the caller is running from a plain user dir.
|
|
55
|
+
*
|
|
56
|
+
* Honors `HQ_HOME` env var as an explicit override so multi-checkout setups
|
|
57
|
+
* (e.g. CI bots, the menubar app pointing at a non-cwd HQ root) can pin the
|
|
58
|
+
* detection without relying on cwd.
|
|
59
|
+
*
|
|
60
|
+
* Why a regex instead of a YAML parser: `core.yaml` lives at the very top of
|
|
61
|
+
* the file and the field has a stable shape — adding a YAML dep just to read
|
|
62
|
+
* one string would balloon every consumer's bundle. The current shape is
|
|
63
|
+
* `hqVersion: "X.Y.Z"` (quoted) per the canonical seed; we tolerate unquoted
|
|
64
|
+
* too.
|
|
65
|
+
*/
|
|
66
|
+
export function detectHqCoreVersion(startDir) {
|
|
67
|
+
const fromEnv = process.env.HQ_HOME ?? process.env.HQ_ROOT;
|
|
68
|
+
if (fromEnv) {
|
|
69
|
+
const v = readCoreVersionAt(fromEnv);
|
|
70
|
+
if (v)
|
|
71
|
+
return v;
|
|
72
|
+
}
|
|
73
|
+
let dir = resolve(startDir ?? process.cwd());
|
|
74
|
+
while (true) {
|
|
75
|
+
const v = readCoreVersionAt(dir);
|
|
76
|
+
if (v)
|
|
77
|
+
return v;
|
|
78
|
+
const parent = dirname(dir);
|
|
79
|
+
if (parent === dir)
|
|
80
|
+
return undefined;
|
|
81
|
+
dir = parent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function readCoreVersionAt(hqRoot) {
|
|
85
|
+
// hq-core identity requires BOTH core/core.yaml AND companies/ — matches the
|
|
86
|
+
// CLI's own detection (commit bc827d0). Without this guard, any directory
|
|
87
|
+
// containing a stray `core/core.yaml` (e.g. a test fixture) would be
|
|
88
|
+
// misidentified as an hq-core root.
|
|
89
|
+
const yamlPath = join(hqRoot, "core", "core.yaml");
|
|
90
|
+
const companiesPath = join(hqRoot, "companies");
|
|
91
|
+
let content;
|
|
92
|
+
try {
|
|
93
|
+
content = readFileSync(yamlPath, "utf8");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
// statSync would be marginally cleaner, but readdirSync of a nonexistent
|
|
100
|
+
// path throws synchronously which is what we want.
|
|
101
|
+
readFileSync(companiesPath, { flag: "r" });
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const e = err;
|
|
105
|
+
// EISDIR is the success case — companies/ exists and is a directory.
|
|
106
|
+
if (e.code !== "EISDIR")
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const match = /^hqVersion:\s*["']?([^"'\s]+)/m.exec(content);
|
|
110
|
+
return match ? match[1] : undefined;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=client-info.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-info.js","sourceRoot":"","sources":["../src/client-info.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the client-info helpers and header injection.
|
|
3
|
+
*
|
|
4
|
+
* Two layers are exercised here:
|
|
5
|
+
* 1. The pure functions in `client-info.ts` — buildClientHeaders,
|
|
6
|
+
* clientInfoFromPackage, detectHqCoreVersion.
|
|
7
|
+
* 2. End-to-end injection into VaultClient.request, since "the headers
|
|
8
|
+
* actually land on outbound fetch" is the property consumers care about.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=client-info.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-info.test.d.ts","sourceRoot":"","sources":["../src/client-info.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the client-info helpers and header injection.
|
|
3
|
+
*
|
|
4
|
+
* Two layers are exercised here:
|
|
5
|
+
* 1. The pure functions in `client-info.ts` — buildClientHeaders,
|
|
6
|
+
* clientInfoFromPackage, detectHqCoreVersion.
|
|
7
|
+
* 2. End-to-end injection into VaultClient.request, since "the headers
|
|
8
|
+
* actually land on outbound fetch" is the property consumers care about.
|
|
9
|
+
*/
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { buildClientHeaders, clientInfoFromPackage, detectHqCoreVersion, HEADER_CLIENT_NAME, HEADER_CLIENT_VERSION, HEADER_HQ_CORE_VERSION, } from "./client-info.js";
|
|
15
|
+
import { VaultClient } from "./vault-client.js";
|
|
16
|
+
describe("buildClientHeaders", () => {
|
|
17
|
+
it("returns empty object when info is undefined", () => {
|
|
18
|
+
expect(buildClientHeaders(undefined)).toEqual({});
|
|
19
|
+
});
|
|
20
|
+
it("emits User-Agent + x-hq-client-{name,version} from name/version", () => {
|
|
21
|
+
const headers = buildClientHeaders({
|
|
22
|
+
name: "@indigoai-us/hq-cli",
|
|
23
|
+
version: "5.15.0",
|
|
24
|
+
});
|
|
25
|
+
expect(headers["User-Agent"]).toBe("@indigoai-us/hq-cli/5.15.0");
|
|
26
|
+
expect(headers[HEADER_CLIENT_NAME]).toBe("@indigoai-us/hq-cli");
|
|
27
|
+
expect(headers[HEADER_CLIENT_VERSION]).toBe("5.15.0");
|
|
28
|
+
expect(headers[HEADER_HQ_CORE_VERSION]).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
it("includes x-hq-core-version only when hqCoreVersion is set", () => {
|
|
31
|
+
const headers = buildClientHeaders({
|
|
32
|
+
name: "@indigoai-us/hq-cli",
|
|
33
|
+
version: "5.15.0",
|
|
34
|
+
hqCoreVersion: "14.1.0",
|
|
35
|
+
});
|
|
36
|
+
expect(headers[HEADER_HQ_CORE_VERSION]).toBe("14.1.0");
|
|
37
|
+
});
|
|
38
|
+
it("emits arbitrary extra fields as x-hq-client-<key> headers", () => {
|
|
39
|
+
const headers = buildClientHeaders({
|
|
40
|
+
name: "x",
|
|
41
|
+
version: "1.0.0",
|
|
42
|
+
extra: { machine: "ec2-bot-7", channel: "stable" },
|
|
43
|
+
});
|
|
44
|
+
expect(headers["x-hq-client-machine"]).toBe("ec2-bot-7");
|
|
45
|
+
expect(headers["x-hq-client-channel"]).toBe("stable");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("clientInfoFromPackage", () => {
|
|
49
|
+
it("extracts name and version from a parsed package.json", () => {
|
|
50
|
+
expect(clientInfoFromPackage({ name: "foo", version: "1.2.3" })).toEqual({
|
|
51
|
+
name: "foo",
|
|
52
|
+
version: "1.2.3",
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it("throws when name is missing", () => {
|
|
56
|
+
expect(() => clientInfoFromPackage({ version: "1.0.0" })).toThrow(/name/);
|
|
57
|
+
});
|
|
58
|
+
it("throws when version is missing", () => {
|
|
59
|
+
expect(() => clientInfoFromPackage({ name: "foo" })).toThrow(/version/);
|
|
60
|
+
});
|
|
61
|
+
it("throws on non-string fields", () => {
|
|
62
|
+
expect(() => clientInfoFromPackage({ name: 42, version: "1.0.0" })).toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("detectHqCoreVersion", () => {
|
|
66
|
+
let tmpRoot;
|
|
67
|
+
const origHqHome = process.env.HQ_HOME;
|
|
68
|
+
const origHqRoot = process.env.HQ_ROOT;
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
tmpRoot = mkdtempSync(join(tmpdir(), "hq-core-detect-"));
|
|
71
|
+
delete process.env.HQ_HOME;
|
|
72
|
+
delete process.env.HQ_ROOT;
|
|
73
|
+
});
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
76
|
+
if (origHqHome !== undefined)
|
|
77
|
+
process.env.HQ_HOME = origHqHome;
|
|
78
|
+
if (origHqRoot !== undefined)
|
|
79
|
+
process.env.HQ_ROOT = origHqRoot;
|
|
80
|
+
});
|
|
81
|
+
function seedHqCore(root, version) {
|
|
82
|
+
mkdirSync(join(root, "core"), { recursive: true });
|
|
83
|
+
mkdirSync(join(root, "companies"), { recursive: true });
|
|
84
|
+
writeFileSync(join(root, "core", "core.yaml"), `version: 1\nhqVersion: "${version}"\nupdatedAt: "2026-05-13T00:00:00Z"\n`);
|
|
85
|
+
}
|
|
86
|
+
it("returns undefined when nothing on the walk-up has core.yaml", () => {
|
|
87
|
+
expect(detectHqCoreVersion(tmpRoot)).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
it("returns hqVersion when startDir is the hq-core root", () => {
|
|
90
|
+
seedHqCore(tmpRoot, "14.1.0");
|
|
91
|
+
expect(detectHqCoreVersion(tmpRoot)).toBe("14.1.0");
|
|
92
|
+
});
|
|
93
|
+
it("walks upward to find core.yaml from a nested cwd", () => {
|
|
94
|
+
seedHqCore(tmpRoot, "14.2.0");
|
|
95
|
+
const nested = join(tmpRoot, "companies", "acme", "projects", "p1");
|
|
96
|
+
mkdirSync(nested, { recursive: true });
|
|
97
|
+
expect(detectHqCoreVersion(nested)).toBe("14.2.0");
|
|
98
|
+
});
|
|
99
|
+
it("ignores directories that have core.yaml but no companies/ — disambiguates fixtures", () => {
|
|
100
|
+
mkdirSync(join(tmpRoot, "core"), { recursive: true });
|
|
101
|
+
writeFileSync(join(tmpRoot, "core", "core.yaml"), `version: 1\nhqVersion: "99.0.0"\n`);
|
|
102
|
+
// No companies/ at this level → not an hq-core root.
|
|
103
|
+
expect(detectHqCoreVersion(tmpRoot)).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
it("honors HQ_HOME env override before walking cwd", () => {
|
|
106
|
+
seedHqCore(tmpRoot, "14.3.0");
|
|
107
|
+
process.env.HQ_HOME = tmpRoot;
|
|
108
|
+
// startDir intentionally points elsewhere — env should win.
|
|
109
|
+
const elsewhere = mkdtempSync(join(tmpdir(), "elsewhere-"));
|
|
110
|
+
try {
|
|
111
|
+
expect(detectHqCoreVersion(elsewhere)).toBe("14.3.0");
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
rmSync(elsewhere, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
it("parses unquoted hqVersion values too", () => {
|
|
118
|
+
mkdirSync(join(tmpRoot, "core"), { recursive: true });
|
|
119
|
+
mkdirSync(join(tmpRoot, "companies"), { recursive: true });
|
|
120
|
+
writeFileSync(join(tmpRoot, "core", "core.yaml"), `version: 1\nhqVersion: 14.4.0\n`);
|
|
121
|
+
expect(detectHqCoreVersion(tmpRoot)).toBe("14.4.0");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe("VaultClient stamps client headers on outbound requests", () => {
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
vi.restoreAllMocks();
|
|
127
|
+
});
|
|
128
|
+
it("includes x-hq-client-name + x-hq-client-version when clientInfo is set", async () => {
|
|
129
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({ memberships: [] }), {
|
|
130
|
+
status: 200,
|
|
131
|
+
headers: { "Content-Type": "application/json" },
|
|
132
|
+
}));
|
|
133
|
+
const client = new VaultClient({
|
|
134
|
+
apiUrl: "https://vault.test.example.com",
|
|
135
|
+
authToken: "tok",
|
|
136
|
+
clientInfo: {
|
|
137
|
+
name: "@indigoai-us/hq-cli",
|
|
138
|
+
version: "5.15.0",
|
|
139
|
+
hqCoreVersion: "14.1.0",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
await client.listMyMemberships();
|
|
143
|
+
expect(fetchSpy).toHaveBeenCalledOnce();
|
|
144
|
+
const init = fetchSpy.mock.calls[0]?.[1];
|
|
145
|
+
const headers = init.headers;
|
|
146
|
+
expect(headers[HEADER_CLIENT_NAME]).toBe("@indigoai-us/hq-cli");
|
|
147
|
+
expect(headers[HEADER_CLIENT_VERSION]).toBe("5.15.0");
|
|
148
|
+
expect(headers[HEADER_HQ_CORE_VERSION]).toBe("14.1.0");
|
|
149
|
+
expect(headers["User-Agent"]).toBe("@indigoai-us/hq-cli/5.15.0");
|
|
150
|
+
});
|
|
151
|
+
it("omits client headers when clientInfo is not set — back-compat", async () => {
|
|
152
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({ memberships: [] }), {
|
|
153
|
+
status: 200,
|
|
154
|
+
headers: { "Content-Type": "application/json" },
|
|
155
|
+
}));
|
|
156
|
+
const client = new VaultClient({
|
|
157
|
+
apiUrl: "https://vault.test.example.com",
|
|
158
|
+
authToken: "tok",
|
|
159
|
+
});
|
|
160
|
+
await client.listMyMemberships();
|
|
161
|
+
const headers = (fetchSpy.mock.calls[0]?.[1])
|
|
162
|
+
.headers;
|
|
163
|
+
expect(headers[HEADER_CLIENT_NAME]).toBeUndefined();
|
|
164
|
+
expect(headers[HEADER_CLIENT_VERSION]).toBeUndefined();
|
|
165
|
+
expect(headers["User-Agent"]).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=client-info.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-info.test.js","sourceRoot":"","sources":["../src/client-info.test.ts"],"names":[],"mappings":"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"}
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA2DpE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,2BAA4B,CAAC;AAE5D;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,aAAa,CAAC,CAsExB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,aAAa,CAAC,CAcxB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
package/dist/context.js
CHANGED
|
@@ -5,12 +5,59 @@
|
|
|
5
5
|
* and returns an EntityContext for S3 operations. Handles auto-refresh when
|
|
6
6
|
* credentials are within 2 minutes of expiry.
|
|
7
7
|
*/
|
|
8
|
+
import { buildClientHeaders } from "./client-info.js";
|
|
8
9
|
/** Minimum remaining TTL before auto-refresh triggers (2 minutes). */
|
|
9
10
|
const REFRESH_THRESHOLD_MS = 2 * 60 * 1000;
|
|
10
11
|
/** STS session duration requested from vault-service (15 minutes). */
|
|
11
12
|
const DEFAULT_SESSION_DURATION_SECONDS = 900;
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Cached contexts.
|
|
15
|
+
*
|
|
16
|
+
* Two keying schemes share the map:
|
|
17
|
+
* - UID keys: bare `cmp_xxx` / `prs_xxx`. Globally unique by ULID, so
|
|
18
|
+
* the cached context is safe to return to any caller asking by uid.
|
|
19
|
+
* - Slug keys: `slug:${callerSub}#${slug}`. Under the per-user-namespace
|
|
20
|
+
* model on hq-pro (PR 67, live 2026-05-15), the same slug can
|
|
21
|
+
* legitimately resolve to different company UIDs for different
|
|
22
|
+
* callers. Keying slug entries by `(callerSub, slug)` prevents one
|
|
23
|
+
* caller's resolution from being served to another caller against
|
|
24
|
+
* the wrong tenant. Codex flagged this as a P1 on PR 67's hq-cloud
|
|
25
|
+
* follow-up.
|
|
26
|
+
*/
|
|
13
27
|
const contextCache = new Map();
|
|
28
|
+
/**
|
|
29
|
+
* Extract the Cognito sub claim from a JWT without verifying the
|
|
30
|
+
* signature. We don't need verification here — the token already
|
|
31
|
+
* authorized the upstream API call; we're only using `sub` as a
|
|
32
|
+
* per-caller cache discriminator. If the token is malformed or
|
|
33
|
+
* missing a sub, fall back to a hash of the whole token (slightly
|
|
34
|
+
* larger key space, same correctness — different tokens still
|
|
35
|
+
* get distinct cache entries).
|
|
36
|
+
*/
|
|
37
|
+
function callerKeyFromAccessToken(accessToken) {
|
|
38
|
+
const parts = accessToken.split(".");
|
|
39
|
+
if (parts.length === 3) {
|
|
40
|
+
try {
|
|
41
|
+
// base64url decode (Node-compatible, no padding required)
|
|
42
|
+
const padded = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
43
|
+
const payload = Buffer.from(padded, "base64").toString("utf8");
|
|
44
|
+
const claims = JSON.parse(payload);
|
|
45
|
+
if (typeof claims.sub === "string" && claims.sub.length > 0) {
|
|
46
|
+
return claims.sub;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// fall through to token-hash fallback
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Stable fallback — same token → same key, different tokens → different keys.
|
|
54
|
+
// (Avoids importing crypto for a sha; the raw token suffix is sufficient as
|
|
55
|
+
// a cache discriminator and isn't exposed outside this process.)
|
|
56
|
+
return `tok:${accessToken.slice(-32)}`;
|
|
57
|
+
}
|
|
58
|
+
function slugCacheKey(callerKey, slug) {
|
|
59
|
+
return `slug:${callerKey}#${slug}`;
|
|
60
|
+
}
|
|
14
61
|
/**
|
|
15
62
|
* Closed-set of recognised entity-UID prefixes. Adding a new entity type
|
|
16
63
|
* means appending one entry here AND extending the dispatch in
|
|
@@ -25,16 +72,30 @@ export const KNOWN_UID_PREFIXES = ["cmp_", "prs_"];
|
|
|
25
72
|
* 2 minutes of expiry.
|
|
26
73
|
*/
|
|
27
74
|
export async function resolveEntityContext(companyUidOrSlug, config) {
|
|
28
|
-
//
|
|
29
|
-
|
|
75
|
+
// Step 0: Determine whether the input is a UID (globally unique) or a
|
|
76
|
+
// slug (per-caller). The cache key differs accordingly — see the
|
|
77
|
+
// `contextCache` jsdoc.
|
|
78
|
+
const looksLikeUid = KNOWN_UID_PREFIXES.some((p) => companyUidOrSlug.startsWith(p));
|
|
79
|
+
const looksLikePerson = companyUidOrSlug.startsWith("prs_");
|
|
80
|
+
// For slug lookups, scope the cache key by the caller. Resolving the
|
|
81
|
+
// access token here (once) doubles as a way to extract the sub for
|
|
82
|
+
// the key — same hop the downstream fetch makes anyway.
|
|
83
|
+
let cacheKey;
|
|
84
|
+
let callerKey = null;
|
|
85
|
+
if (looksLikeUid) {
|
|
86
|
+
cacheKey = companyUidOrSlug;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const token = await resolveAuthToken(config);
|
|
90
|
+
callerKey = callerKeyFromAccessToken(token);
|
|
91
|
+
cacheKey = slugCacheKey(callerKey, companyUidOrSlug);
|
|
92
|
+
}
|
|
93
|
+
// Check cache — return if credentials still fresh.
|
|
94
|
+
const cached = contextCache.get(cacheKey);
|
|
30
95
|
if (cached && !isExpiringSoon(cached.expiresAt)) {
|
|
31
96
|
return cached;
|
|
32
97
|
}
|
|
33
|
-
// Step 1: Resolve entity —
|
|
34
|
-
// otherwise look up by slug. Explicit enumeration avoids over-matching slugs
|
|
35
|
-
// like foo_bar or team_alpha that happen to look like UIDs.
|
|
36
|
-
const looksLikeUid = KNOWN_UID_PREFIXES.some((p) => companyUidOrSlug.startsWith(p));
|
|
37
|
-
const looksLikePerson = companyUidOrSlug.startsWith("prs_");
|
|
98
|
+
// Step 1: Resolve entity — direct fetch by UID or namespace lookup by slug.
|
|
38
99
|
const entity = looksLikeUid
|
|
39
100
|
? await fetchEntity(companyUidOrSlug, config)
|
|
40
101
|
: await fetchEntityBySlug("company", companyUidOrSlug, config);
|
|
@@ -61,9 +122,13 @@ export async function resolveEntityContext(companyUidOrSlug, config) {
|
|
|
61
122
|
},
|
|
62
123
|
expiresAt: vendResult.expiresAt,
|
|
63
124
|
};
|
|
64
|
-
// Cache by
|
|
125
|
+
// Cache by UID (globally unique) and — if the caller asked by slug —
|
|
126
|
+
// by `(callerSub, slug)` so the same slug can resolve to different
|
|
127
|
+
// entities per caller without cross-caller poisoning.
|
|
65
128
|
contextCache.set(entity.uid, ctx);
|
|
66
|
-
|
|
129
|
+
if (callerKey) {
|
|
130
|
+
contextCache.set(slugCacheKey(callerKey, entity.slug), ctx);
|
|
131
|
+
}
|
|
67
132
|
return ctx;
|
|
68
133
|
}
|
|
69
134
|
/**
|
|
@@ -79,8 +144,17 @@ export function isExpiringSoon(expiresAt) {
|
|
|
79
144
|
* an expired credentials error.
|
|
80
145
|
*/
|
|
81
146
|
export async function refreshEntityContext(companyUidOrSlug, config) {
|
|
82
|
-
// Evict
|
|
83
|
-
|
|
147
|
+
// Evict the entry under whichever key shape applies. UID inputs key
|
|
148
|
+
// the cache by the bare uid; slug inputs key by `(callerSub, slug)`.
|
|
149
|
+
const looksLikeUid = KNOWN_UID_PREFIXES.some((p) => companyUidOrSlug.startsWith(p));
|
|
150
|
+
if (looksLikeUid) {
|
|
151
|
+
contextCache.delete(companyUidOrSlug);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const token = await resolveAuthToken(config);
|
|
155
|
+
const callerKey = callerKeyFromAccessToken(token);
|
|
156
|
+
contextCache.delete(slugCacheKey(callerKey, companyUidOrSlug));
|
|
157
|
+
}
|
|
84
158
|
return resolveEntityContext(companyUidOrSlug, config);
|
|
85
159
|
}
|
|
86
160
|
/**
|
|
@@ -103,7 +177,10 @@ async function resolveAuthToken(config) {
|
|
|
103
177
|
}
|
|
104
178
|
async function fetchEntity(uid, config) {
|
|
105
179
|
const res = await fetch(`${config.apiUrl}/entity/${uid}`, {
|
|
106
|
-
headers: {
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: `Bearer ${await resolveAuthToken(config)}`,
|
|
182
|
+
...buildClientHeaders(config.clientInfo),
|
|
183
|
+
},
|
|
107
184
|
});
|
|
108
185
|
if (!res.ok) {
|
|
109
186
|
const body = await res.text();
|
|
@@ -113,15 +190,34 @@ async function fetchEntity(uid, config) {
|
|
|
113
190
|
return data.entity;
|
|
114
191
|
}
|
|
115
192
|
async function fetchEntityBySlug(type, slug, config) {
|
|
116
|
-
|
|
117
|
-
|
|
193
|
+
// Resolve the slug inside the CALLER's namespace via the new
|
|
194
|
+
// `/entity/check-slug/me` endpoint added in hq-pro PR 67 (live
|
|
195
|
+
// in prod 2026-05-15). Under the per-user-namespace model the
|
|
196
|
+
// legacy `/entity/by-slug/{type}/{slug}` either over-matches
|
|
197
|
+
// (returns another tenant's entity when more than one user holds
|
|
198
|
+
// the slug) or 409s with SlugNotUniqueError — both wrong for the
|
|
199
|
+
// sync runner's "I want MY company" intent.
|
|
200
|
+
const checkUrl = `${config.apiUrl}/entity/check-slug/me?type=${encodeURIComponent(type)}&slug=${encodeURIComponent(slug)}`;
|
|
201
|
+
const checkRes = await fetch(checkUrl, {
|
|
202
|
+
headers: {
|
|
203
|
+
Authorization: `Bearer ${await resolveAuthToken(config)}`,
|
|
204
|
+
...buildClientHeaders(config.clientInfo),
|
|
205
|
+
},
|
|
118
206
|
});
|
|
119
|
-
if (!
|
|
120
|
-
const body = await
|
|
121
|
-
throw new Error(`Failed to
|
|
207
|
+
if (!checkRes.ok) {
|
|
208
|
+
const body = await checkRes.text();
|
|
209
|
+
throw new Error(`Failed to check slug ${type}/${slug}: ${checkRes.status} ${body}`);
|
|
122
210
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
211
|
+
const check = (await checkRes.json());
|
|
212
|
+
if (check.available || !check.conflictingCompanyUid) {
|
|
213
|
+
// The slug isn't in the caller's namespace. From the sync
|
|
214
|
+
// runner's perspective this is a genuine "not found" — the
|
|
215
|
+
// caller doesn't have a `${slug}` to sync, even if some other
|
|
216
|
+
// user does.
|
|
217
|
+
throw new Error(`No entity in caller's namespace matches ${type}/${slug}. ` +
|
|
218
|
+
`If you expected to find one, verify you're signed in to the right HQ identity.`);
|
|
219
|
+
}
|
|
220
|
+
return fetchEntity(check.conflictingCompanyUid, config);
|
|
125
221
|
}
|
|
126
222
|
async function postVend(route, body, config) {
|
|
127
223
|
const res = await fetch(`${config.apiUrl}${route}`, {
|
|
@@ -129,6 +225,7 @@ async function postVend(route, body, config) {
|
|
|
129
225
|
headers: {
|
|
130
226
|
"Content-Type": "application/json",
|
|
131
227
|
Authorization: `Bearer ${await resolveAuthToken(config)}`,
|
|
228
|
+
...buildClientHeaders(config.clientInfo),
|
|
132
229
|
},
|
|
133
230
|
body: JSON.stringify({ ...body, durationSeconds: DEFAULT_SESSION_DURATION_SECONDS }),
|
|
134
231
|
});
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|