@openparachute/hub 0.5.14-rc.8 → 0.6.0

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.
Files changed (87) hide show
  1. package/README.md +109 -15
  2. package/package.json +7 -3
  3. package/src/__tests__/account-home-ui.test.ts +251 -15
  4. package/src/__tests__/account-vault-token.test.ts +355 -0
  5. package/src/__tests__/admin-vaults.test.ts +70 -4
  6. package/src/__tests__/api-mint-token.test.ts +693 -5
  7. package/src/__tests__/api-modules-ops.test.ts +45 -0
  8. package/src/__tests__/api-revoke-token.test.ts +384 -0
  9. package/src/__tests__/api-users.test.ts +7 -2
  10. package/src/__tests__/auth.test.ts +157 -30
  11. package/src/__tests__/cli.test.ts +44 -5
  12. package/src/__tests__/expose-2fa-warning.test.ts +31 -17
  13. package/src/__tests__/expose-auth-preflight.test.ts +71 -72
  14. package/src/__tests__/expose-cloudflare.test.ts +482 -14
  15. package/src/__tests__/expose.test.ts +52 -2
  16. package/src/__tests__/hub-server.test.ts +97 -0
  17. package/src/__tests__/hub.test.ts +85 -6
  18. package/src/__tests__/init.test.ts +102 -1
  19. package/src/__tests__/lifecycle.test.ts +464 -2
  20. package/src/__tests__/oauth-handlers.test.ts +1252 -83
  21. package/src/__tests__/oauth-ui.test.ts +12 -1
  22. package/src/__tests__/operator-token-issuer-self-heal.test.ts +412 -0
  23. package/src/__tests__/resource-binding.test.ts +97 -0
  24. package/src/__tests__/scope-explanations.test.ts +77 -12
  25. package/src/__tests__/services-manifest.test.ts +122 -4
  26. package/src/__tests__/setup-wizard.test.ts +335 -15
  27. package/src/__tests__/status.test.ts +36 -0
  28. package/src/__tests__/two-factor-flow.test.ts +602 -0
  29. package/src/__tests__/two-factor.test.ts +183 -0
  30. package/src/__tests__/upgrade.test.ts +78 -1
  31. package/src/__tests__/users.test.ts +68 -0
  32. package/src/__tests__/vault-auth-status.test.ts +47 -6
  33. package/src/__tests__/vault-hub-origin-env.test.ts +263 -0
  34. package/src/account-home-ui.ts +488 -38
  35. package/src/account-vault-token.ts +282 -0
  36. package/src/admin-handlers.ts +159 -4
  37. package/src/admin-login-ui.ts +49 -5
  38. package/src/admin-vaults.ts +48 -15
  39. package/src/api-account.ts +14 -0
  40. package/src/api-mint-token.ts +132 -24
  41. package/src/api-modules-ops.ts +49 -11
  42. package/src/api-revoke-token.ts +107 -21
  43. package/src/api-users.ts +29 -3
  44. package/src/cli.ts +26 -21
  45. package/src/clients.ts +18 -6
  46. package/src/cloudflare/config.ts +10 -4
  47. package/src/cloudflare/detect.ts +39 -44
  48. package/src/commands/auth.ts +165 -24
  49. package/src/commands/expose-2fa-warning.ts +34 -32
  50. package/src/commands/expose-auth-preflight.ts +89 -78
  51. package/src/commands/expose-cloudflare.ts +370 -12
  52. package/src/commands/expose.ts +8 -0
  53. package/src/commands/init.ts +33 -2
  54. package/src/commands/lifecycle.ts +386 -17
  55. package/src/commands/status.ts +22 -0
  56. package/src/commands/upgrade.ts +55 -11
  57. package/src/commands/wizard.ts +8 -4
  58. package/src/env-file.ts +10 -0
  59. package/src/help.ts +3 -1
  60. package/src/hub-db.ts +39 -1
  61. package/src/hub-server.ts +52 -0
  62. package/src/hub.ts +82 -14
  63. package/src/oauth-handlers.ts +298 -21
  64. package/src/oauth-ui.ts +10 -0
  65. package/src/operator-token.ts +151 -0
  66. package/src/pending-login.ts +116 -0
  67. package/src/rate-limit.ts +51 -0
  68. package/src/resource-binding.ts +134 -0
  69. package/src/scope-attenuation.ts +85 -0
  70. package/src/scope-explanations.ts +131 -14
  71. package/src/services-manifest.ts +112 -0
  72. package/src/setup-wizard.ts +77 -7
  73. package/src/tailscale/run.ts +28 -11
  74. package/src/totp.ts +201 -0
  75. package/src/two-factor-handlers.ts +287 -0
  76. package/src/two-factor-store.ts +181 -0
  77. package/src/two-factor-ui.ts +462 -0
  78. package/src/users.ts +58 -0
  79. package/src/vault/auth-status.ts +71 -19
  80. package/src/vault-hub-origin-env.ts +163 -0
  81. package/web/ui/dist/assets/index-BiBlvEaj.css +1 -0
  82. package/web/ui/dist/assets/index-CIN3mnmf.js +61 -0
  83. package/web/ui/dist/index.html +2 -2
  84. package/src/__tests__/vault-tokens-create-interactive.test.ts +0 -183
  85. package/src/commands/vault-tokens-create-interactive.ts +0 -143
  86. package/web/ui/dist/assets/index-7DtAXz7y.css +0 -1
  87. package/web/ui/dist/assets/index-tRmPbbC7.js +0 -61
@@ -6,9 +6,11 @@ import {
6
6
  type ServiceEntry,
7
7
  ServicesManifestError,
8
8
  type UiSubUnit,
9
+ clearStartError,
9
10
  findService,
10
11
  readManifest,
11
12
  readManifestLenient,
13
+ recordStartError,
12
14
  removeService,
13
15
  upsertService,
14
16
  writeManifest,
@@ -1410,9 +1412,27 @@ describe("readManifestLenient — skips bad entries instead of throwing (hub#406
1410
1412
  path,
1411
1413
  JSON.stringify({
1412
1414
  services: [
1413
- { name: "parachute-vault", port: 1940, paths: ["/vault/default"], health: "/vault/default/health", version: "0.4.8-rc.10" },
1414
- { name: "parachute-surface", port: 1946, paths: ["/surface"], health: "/surface/healthz", version: "0.2.0-rc.13" },
1415
- { name: "widget", port: 0, paths: ["/widget"], health: "/widget/health", version: "0.0.1" },
1415
+ {
1416
+ name: "parachute-vault",
1417
+ port: 1940,
1418
+ paths: ["/vault/default"],
1419
+ health: "/vault/default/health",
1420
+ version: "0.4.8-rc.10",
1421
+ },
1422
+ {
1423
+ name: "parachute-surface",
1424
+ port: 1946,
1425
+ paths: ["/surface"],
1426
+ health: "/surface/healthz",
1427
+ version: "0.2.0-rc.13",
1428
+ },
1429
+ {
1430
+ name: "widget",
1431
+ port: 0,
1432
+ paths: ["/widget"],
1433
+ health: "/widget/health",
1434
+ version: "0.0.1",
1435
+ },
1416
1436
  ],
1417
1437
  }),
1418
1438
  );
@@ -1479,7 +1499,15 @@ describe("readManifestLenient — skips bad entries instead of throwing (hub#406
1479
1499
  writeFileSync(
1480
1500
  path,
1481
1501
  JSON.stringify({
1482
- services: [{ name: "widget", port: 0, paths: ["/widget"], health: "/widget/health", version: "0.0.1" }],
1502
+ services: [
1503
+ {
1504
+ name: "widget",
1505
+ port: 0,
1506
+ paths: ["/widget"],
1507
+ health: "/widget/health",
1508
+ version: "0.0.1",
1509
+ },
1510
+ ],
1483
1511
  }),
1484
1512
  );
1485
1513
  expect(() => readManifest(path)).toThrow(ServicesManifestError);
@@ -1487,4 +1515,94 @@ describe("readManifestLenient — skips bad entries instead of throwing (hub#406
1487
1515
  cleanup();
1488
1516
  }
1489
1517
  });
1518
+
1519
+ describe("lastStartError", () => {
1520
+ const wire = {
1521
+ error_type: "missing_dependency",
1522
+ error_description: "parachute-vault is required ...",
1523
+ binary: "parachute-vault",
1524
+ why: "run the Vault module Hub supervises",
1525
+ docs_url: "https://parachute.computer",
1526
+ install: { generic: "parachute install vault" },
1527
+ sysadmin_hint: "Or ask your system administrator to install it for you.",
1528
+ };
1529
+
1530
+ test("recordStartError persists the wire + stamps `at`", () => {
1531
+ const { path, cleanup } = makeTempPath();
1532
+ try {
1533
+ upsertService(vault, path);
1534
+ recordStartError("parachute-vault", wire, path);
1535
+ const entry = readManifest(path).services.find((s) => s.name === "parachute-vault");
1536
+ expect(entry?.lastStartError?.error_type).toBe("missing_dependency");
1537
+ expect(entry?.lastStartError?.binary).toBe("parachute-vault");
1538
+ expect(entry?.lastStartError?.install?.generic).toBe("parachute install vault");
1539
+ expect(entry?.lastStartError?.at).toBeDefined();
1540
+ } finally {
1541
+ cleanup();
1542
+ }
1543
+ });
1544
+
1545
+ test("recordStartError is a no-op when the row is absent", () => {
1546
+ const { path, cleanup } = makeTempPath();
1547
+ try {
1548
+ upsertService(vault, path);
1549
+ recordStartError("parachute-scribe", wire, path);
1550
+ const scribe = readManifest(path).services.find((s) => s.name === "parachute-scribe");
1551
+ expect(scribe).toBeUndefined();
1552
+ } finally {
1553
+ cleanup();
1554
+ }
1555
+ });
1556
+
1557
+ test("clearStartError removes a recorded error", () => {
1558
+ const { path, cleanup } = makeTempPath();
1559
+ try {
1560
+ upsertService(vault, path);
1561
+ recordStartError("parachute-vault", wire, path);
1562
+ clearStartError("parachute-vault", path);
1563
+ const entry = readManifest(path).services.find((s) => s.name === "parachute-vault");
1564
+ expect(entry?.lastStartError).toBeUndefined();
1565
+ } finally {
1566
+ cleanup();
1567
+ }
1568
+ });
1569
+
1570
+ test("lastStartError round-trips through validation", () => {
1571
+ const { path, cleanup } = makeTempPath();
1572
+ try {
1573
+ const withErr: ServiceEntry = {
1574
+ ...vault,
1575
+ lastStartError: { ...wire, at: "2026-05-29T00:00:00Z" },
1576
+ };
1577
+ upsertService(withErr, path);
1578
+ const entry = readManifest(path).services.find((s) => s.name === "parachute-vault");
1579
+ expect(entry?.lastStartError).toEqual({ ...wire, at: "2026-05-29T00:00:00Z" });
1580
+ } finally {
1581
+ cleanup();
1582
+ }
1583
+ });
1584
+
1585
+ test("a malformed lastStartError is dropped, not thrown (diagnostic field)", () => {
1586
+ const { path, cleanup } = makeTempPath();
1587
+ try {
1588
+ writeFileSync(
1589
+ path,
1590
+ JSON.stringify({
1591
+ services: [
1592
+ {
1593
+ ...vault,
1594
+ // missing error_description → invalid shape → dropped
1595
+ lastStartError: { error_type: "missing_dependency" },
1596
+ },
1597
+ ],
1598
+ }),
1599
+ );
1600
+ const entry = readManifest(path).services.find((s) => s.name === "parachute-vault");
1601
+ expect(entry).toBeDefined();
1602
+ expect(entry?.lastStartError).toBeUndefined();
1603
+ } finally {
1604
+ cleanup();
1605
+ }
1606
+ });
1607
+ });
1490
1608
  });