@openparachute/hub 0.7.4-rc.8 → 0.7.4-rc.9
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/package.json
CHANGED
|
@@ -2914,6 +2914,75 @@ describe("handleToken — full OAuth dance", () => {
|
|
|
2914
2914
|
notes: { url: `${ISSUER}/notes`, version: "0.3.0" },
|
|
2915
2915
|
});
|
|
2916
2916
|
});
|
|
2917
|
+
|
|
2918
|
+
// closes #478 — an empty-paths vault row ("installed but no servable
|
|
2919
|
+
// instance"; vault's self-register emits `paths: []` at zero vaults) must
|
|
2920
|
+
// NOT synthesize a phantom `vault` / `vault:default` entry pointing at
|
|
2921
|
+
// root in the /oauth/token services catalog. Pre-fix the `["/"]` fallback
|
|
2922
|
+
// resolved `vaultInstanceNameFor(name, "/")` → "default" and advertised
|
|
2923
|
+
// `${ISSUER}/` as the vault. Mirrors the skip in well-known.ts /
|
|
2924
|
+
// admin-vaults.ts / vault-names.ts.
|
|
2925
|
+
test("empty-paths vault row produces NO catalog entry — no phantom default (#478)", () => {
|
|
2926
|
+
const emptyPathsManifest: ServicesManifest = {
|
|
2927
|
+
services: [
|
|
2928
|
+
{
|
|
2929
|
+
name: "parachute-vault",
|
|
2930
|
+
port: 1940,
|
|
2931
|
+
paths: [],
|
|
2932
|
+
health: "/vault/default/health",
|
|
2933
|
+
version: "0.7.0",
|
|
2934
|
+
},
|
|
2935
|
+
],
|
|
2936
|
+
};
|
|
2937
|
+
// Broad scope: would have leaked `vault` + `vault:default` at `/`.
|
|
2938
|
+
expect(buildServicesCatalog(emptyPathsManifest, ISSUER, ["vault:read"])).toEqual({});
|
|
2939
|
+
// Per-vault-narrowed scope for the phantom name: also nothing.
|
|
2940
|
+
expect(buildServicesCatalog(emptyPathsManifest, ISSUER, ["vault:default:read"])).toEqual({});
|
|
2941
|
+
});
|
|
2942
|
+
|
|
2943
|
+
test("positive control: a vault row WITH a path is still cataloged (#478)", () => {
|
|
2944
|
+
const realManifest: ServicesManifest = {
|
|
2945
|
+
services: [
|
|
2946
|
+
{
|
|
2947
|
+
name: "parachute-vault",
|
|
2948
|
+
port: 1940,
|
|
2949
|
+
paths: ["/vault/default"],
|
|
2950
|
+
health: "/vault/default/health",
|
|
2951
|
+
version: "0.7.0",
|
|
2952
|
+
},
|
|
2953
|
+
],
|
|
2954
|
+
};
|
|
2955
|
+
expect(buildServicesCatalog(realManifest, ISSUER, ["vault:read"])).toEqual({
|
|
2956
|
+
vault: { url: `${ISSUER}/vault/default`, version: "0.7.0" },
|
|
2957
|
+
});
|
|
2958
|
+
});
|
|
2959
|
+
|
|
2960
|
+
test("empty-paths vault row alongside a real vault: only the real one is cataloged (#478)", () => {
|
|
2961
|
+
// A transitional manifest could carry both a path-less bare row and a
|
|
2962
|
+
// real instance row. The empty-paths row must contribute nothing; the
|
|
2963
|
+
// real vault is unaffected.
|
|
2964
|
+
const mixedManifest: ServicesManifest = {
|
|
2965
|
+
services: [
|
|
2966
|
+
{
|
|
2967
|
+
name: "parachute-vault",
|
|
2968
|
+
port: 1940,
|
|
2969
|
+
paths: [],
|
|
2970
|
+
health: "/vault/default/health",
|
|
2971
|
+
version: "0.7.0",
|
|
2972
|
+
},
|
|
2973
|
+
{
|
|
2974
|
+
name: "parachute-vault-work",
|
|
2975
|
+
port: 1941,
|
|
2976
|
+
paths: ["/vault/work"],
|
|
2977
|
+
health: "/vault/work/health",
|
|
2978
|
+
version: "0.7.0",
|
|
2979
|
+
},
|
|
2980
|
+
],
|
|
2981
|
+
};
|
|
2982
|
+
expect(buildServicesCatalog(mixedManifest, ISSUER, ["vault:read"])).toEqual({
|
|
2983
|
+
vault: { url: `${ISSUER}/vault/work`, version: "0.7.0" },
|
|
2984
|
+
});
|
|
2985
|
+
});
|
|
2917
2986
|
});
|
|
2918
2987
|
});
|
|
2919
2988
|
|
|
@@ -472,13 +472,48 @@ describe("buildWellKnown", () => {
|
|
|
472
472
|
);
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
-
test("
|
|
475
|
+
test("an empty-paths VAULT row is skipped entirely — no phantom default (#478)", () => {
|
|
476
|
+
// A vault services row with `paths: []` means "module installed but no
|
|
477
|
+
// servable vault instance" (vault's self-register emits this at zero
|
|
478
|
+
// vaults). It must NOT fabricate a vault entry at root in either the
|
|
479
|
+
// `vaults` array or the flat `services` catalog. Mirrors the empty-paths
|
|
480
|
+
// skip in admin-vaults.ts / vault-names.ts / oauth-handlers.ts.
|
|
476
481
|
const entry: ServiceEntry = { ...vault, paths: [] };
|
|
477
482
|
const doc = buildWellKnown({
|
|
478
483
|
services: [entry],
|
|
479
484
|
canonicalOrigin: "https://x.example",
|
|
480
485
|
});
|
|
481
|
-
expect(doc.vaults
|
|
486
|
+
expect(doc.vaults).toEqual([]);
|
|
487
|
+
// The row contributes nothing to the flat services list either — no
|
|
488
|
+
// phantom `/` mount advertised.
|
|
489
|
+
expect(doc.services).toEqual([]);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("positive control: a vault row WITH a path still emits its vault + services entries (#478)", () => {
|
|
493
|
+
const doc = buildWellKnown({
|
|
494
|
+
services: [{ ...vault, paths: ["/vault/default"] }],
|
|
495
|
+
canonicalOrigin: "https://x.example",
|
|
496
|
+
});
|
|
497
|
+
expect(doc.vaults).toEqual([
|
|
498
|
+
{
|
|
499
|
+
name: "default",
|
|
500
|
+
url: "https://x.example/vault/default",
|
|
501
|
+
version: "0.2.4",
|
|
502
|
+
},
|
|
503
|
+
]);
|
|
504
|
+
expect(doc.services.map((s) => s.name)).toEqual(["parachute-vault"]);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("a NON-vault row with empty paths still falls back to / (#478 scope guard)", () => {
|
|
508
|
+
// The empty-paths skip is vault-only. A non-vault service legitimately
|
|
509
|
+
// mounts at root when path-less — that behavior is unchanged.
|
|
510
|
+
const entry: ServiceEntry = { ...notes, paths: [] };
|
|
511
|
+
const doc = buildWellKnown({
|
|
512
|
+
services: [entry],
|
|
513
|
+
canonicalOrigin: "https://x.example",
|
|
514
|
+
});
|
|
515
|
+
expect(doc.services.map((s) => s.path)).toEqual(["/"]);
|
|
516
|
+
expect(doc.notes).toEqual([{ url: "https://x.example/", version: "0.0.1" }]);
|
|
482
517
|
});
|
|
483
518
|
|
|
484
519
|
// Hierarchical sub-units (hub#313 — parachute-app design doc §12). Each
|
package/src/oauth-handlers.ts
CHANGED
|
@@ -294,8 +294,11 @@ export function buildServicesCatalog(
|
|
|
294
294
|
if (audiences.has("vault")) {
|
|
295
295
|
for (const entry of manifest.services) {
|
|
296
296
|
if (!isVaultEntry(entry)) continue;
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
// #478: an empty-paths vault row is "installed but no servable instance"
|
|
298
|
+
// — skip it so it never counts toward (or, below, fabricates) a phantom
|
|
299
|
+
// vault. Mirrors the continue in well-known.ts / admin-vaults.ts.
|
|
300
|
+
if (entry.paths.length === 0) continue;
|
|
301
|
+
for (const path of entry.paths) {
|
|
299
302
|
const instance = vaultInstanceNameFor(entry.name, path);
|
|
300
303
|
if (broadVaultScope || namedVaults.has(instance)) admittedVaultPathCount++;
|
|
301
304
|
}
|
|
@@ -308,12 +311,16 @@ export function buildServicesCatalog(
|
|
|
308
311
|
for (const entry of manifest.services) {
|
|
309
312
|
if (isVaultEntry(entry)) {
|
|
310
313
|
if (!audiences.has("vault")) continue;
|
|
314
|
+
// #478: an empty-paths vault row is "installed but no servable instance"
|
|
315
|
+
// — skip it so the catalog never offers a phantom `vault` / `vault:default`
|
|
316
|
+
// entry pointing at root before any vault exists. Mirrors well-known.ts /
|
|
317
|
+
// admin-vaults.ts / vault-names.ts.
|
|
318
|
+
if (entry.paths.length === 0) continue;
|
|
311
319
|
// Walk every path the row exposes. Real multi-vault on the hub is a
|
|
312
320
|
// single `parachute-vault` row with N paths (one per vault instance);
|
|
313
321
|
// legacy per-vault rows (`parachute-vault-<name>`) are handled by the
|
|
314
322
|
// same loop because each contributes one path.
|
|
315
|
-
const
|
|
316
|
-
for (const path of paths) {
|
|
323
|
+
for (const path of entry.paths) {
|
|
317
324
|
const instance = vaultInstanceNameFor(entry.name, path);
|
|
318
325
|
const admit = broadVaultScope || namedVaults.has(instance);
|
|
319
326
|
if (!admit) continue;
|
package/src/well-known.ts
CHANGED
|
@@ -247,7 +247,16 @@ export function buildWellKnown(opts: BuildWellKnownOpts): WellKnownDocument {
|
|
|
247
247
|
// multi-path on those is treated as aliases rather than separate
|
|
248
248
|
// installs.
|
|
249
249
|
const isVault = isVaultEntry(s);
|
|
250
|
-
|
|
250
|
+
// #478: an empty-paths VAULT row means "installed but no servable vault
|
|
251
|
+
// instance" — vault's self-register emits `paths: []` at zero vaults.
|
|
252
|
+
// Skip it entirely: emitting `["/"]` here would fabricate a phantom vault
|
|
253
|
+
// entry at root in both the `services` catalog and the `vaults` array.
|
|
254
|
+
// This mirrors the empty-paths `continue` in admin-vaults.ts / vault-names.ts
|
|
255
|
+
// so every read path agrees: a vault instance exists only by a real
|
|
256
|
+
// `/vault/<name>` mount path. Non-vault services keep the `paths[0] ?? "/"`
|
|
257
|
+
// fallback (a path-less non-vault row legitimately mounts at root).
|
|
258
|
+
if (isVault && s.paths.length === 0) continue;
|
|
259
|
+
const pathsToEmit = isVault ? s.paths : [s.paths[0] ?? "/"];
|
|
251
260
|
for (const path of pathsToEmit) {
|
|
252
261
|
const url = new URL(path, `${base}/`).toString();
|
|
253
262
|
const infoUrl = new URL(joinInfoPath(path), `${base}/`).toString();
|