@openparachute/hub 0.7.0 → 0.7.2-rc.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.
Files changed (93) hide show
  1. package/README.md +13 -14
  2. package/package.json +1 -1
  3. package/src/__tests__/account-setup.test.ts +276 -6
  4. package/src/__tests__/admin-agent-grants.test.ts +1547 -0
  5. package/src/__tests__/{admin-channel-token.test.ts → admin-agent-token.test.ts} +32 -32
  6. package/src/__tests__/admin-connections-credentials.test.ts +1324 -0
  7. package/src/__tests__/admin-connections.test.ts +211 -57
  8. package/src/__tests__/admin-csrf-belt.test.ts +7 -7
  9. package/src/__tests__/admin-lock.test.ts +600 -0
  10. package/src/__tests__/admin-module-token.test.ts +36 -8
  11. package/src/__tests__/admin-vaults.test.ts +8 -8
  12. package/src/__tests__/api-invites.test.ts +166 -6
  13. package/src/__tests__/api-modules-ops.test.ts +17 -16
  14. package/src/__tests__/api-modules.test.ts +35 -36
  15. package/src/__tests__/api-ready.test.ts +2 -2
  16. package/src/__tests__/audience-gate.test.ts +752 -0
  17. package/src/__tests__/clients.test.ts +91 -0
  18. package/src/__tests__/grants-store.test.ts +219 -0
  19. package/src/__tests__/hub-db.test.ts +36 -0
  20. package/src/__tests__/hub-server.test.ts +275 -5
  21. package/src/__tests__/invites.test.ts +64 -1
  22. package/src/__tests__/lifecycle.test.ts +238 -3
  23. package/src/__tests__/migrate.test.ts +1 -1
  24. package/src/__tests__/module-manifest.test.ts +11 -11
  25. package/src/__tests__/oauth-client.test.ts +446 -0
  26. package/src/__tests__/oauth-flows-store.test.ts +141 -0
  27. package/src/__tests__/oauth-handlers.test.ts +124 -26
  28. package/src/__tests__/operator-token.test.ts +2 -2
  29. package/src/__tests__/scope-explanations.test.ts +3 -3
  30. package/src/__tests__/serve-boot.test.ts +14 -14
  31. package/src/__tests__/serve.test.ts +26 -0
  32. package/src/__tests__/service-spec-discovery.test.ts +26 -18
  33. package/src/__tests__/services-manifest.test.ts +60 -48
  34. package/src/__tests__/setup-gate.test.ts +52 -3
  35. package/src/__tests__/setup-wizard.test.ts +86 -280
  36. package/src/__tests__/setup.test.ts +1 -1
  37. package/src/__tests__/upgrade.test.ts +276 -0
  38. package/src/__tests__/vault-remove.test.ts +393 -0
  39. package/src/__tests__/ws-bridge.test.ts +573 -0
  40. package/src/__tests__/ws-connection-caps.test.ts +456 -0
  41. package/src/account-setup.ts +94 -23
  42. package/src/admin-agent-grants.ts +1365 -0
  43. package/src/admin-agent-token.ts +147 -0
  44. package/src/admin-connections.ts +980 -61
  45. package/src/admin-host-admin-token.ts +14 -1
  46. package/src/admin-lock.ts +281 -0
  47. package/src/admin-login-ui.ts +64 -15
  48. package/src/admin-module-token.ts +15 -7
  49. package/src/admin-vault-admin-token.ts +8 -1
  50. package/src/admin-vaults.ts +21 -12
  51. package/src/api-admin-lock.ts +335 -0
  52. package/src/api-invites.ts +92 -12
  53. package/src/api-modules-ops.ts +3 -2
  54. package/src/api-modules.ts +9 -9
  55. package/src/audience-gate.ts +268 -0
  56. package/src/chrome-strip.ts +8 -1
  57. package/src/cli.ts +13 -1
  58. package/src/clients.ts +88 -0
  59. package/src/commands/install.ts +7 -0
  60. package/src/commands/lifecycle.ts +187 -47
  61. package/src/commands/serve-boot.ts +5 -4
  62. package/src/commands/serve.ts +45 -19
  63. package/src/commands/setup.ts +4 -3
  64. package/src/commands/upgrade.ts +118 -2
  65. package/src/commands/vault-remove.ts +361 -0
  66. package/src/commands/wizard.ts +4 -4
  67. package/src/connections-store.ts +35 -5
  68. package/src/grants-store.ts +272 -0
  69. package/src/help.ts +17 -7
  70. package/src/host-admin-token-validation.ts +6 -2
  71. package/src/hub-db.ts +26 -1
  72. package/src/hub-server.ts +710 -39
  73. package/src/hub-settings.ts +23 -8
  74. package/src/invites.ts +69 -2
  75. package/src/jwt-sign.ts +11 -1
  76. package/src/module-manifest.ts +109 -2
  77. package/src/oauth-client.ts +497 -0
  78. package/src/oauth-flows-store.ts +163 -0
  79. package/src/oauth-handlers.ts +40 -13
  80. package/src/operator-token.ts +1 -1
  81. package/src/origin-check.ts +13 -5
  82. package/src/resource-binding.ts +4 -4
  83. package/src/scope-explanations.ts +3 -3
  84. package/src/service-spec.ts +56 -43
  85. package/src/services-manifest.ts +97 -0
  86. package/src/setup-wizard.ts +56 -240
  87. package/src/ws-bridge.ts +256 -0
  88. package/src/ws-connection-caps.ts +170 -0
  89. package/web/ui/dist/assets/index-B5AUE359.js +61 -0
  90. package/web/ui/dist/assets/{index-E_9wqjEm.css → index-DR6R8EFf.css} +1 -1
  91. package/web/ui/dist/index.html +2 -2
  92. package/src/admin-channel-token.ts +0 -135
  93. package/web/ui/dist/assets/index-C-XzMVqN.js +0 -61
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Tests for the channel UI session→bearer mint endpoint. Mirrors
3
- * `admin-host-admin-token.test.ts` shape (channel has a single bare audience,
2
+ * Tests for the agent UI session→bearer mint endpoint. Mirrors
3
+ * `admin-host-admin-token.test.ts` shape (agent has a single bare audience,
4
4
  * no per-vault name). Covers:
5
5
  * - 401 when no admin session cookie is present.
6
6
  * - 401 when the cookie names a deleted session.
7
7
  * - 405 on POST.
8
- * - 200 + JWT carrying `aud: "channel"` and `channel:read channel:send channel:admin`.
8
+ * - 200 + JWT carrying `aud: "agent"` and `agent:read agent:send agent:admin`.
9
9
  * - First-admin gate: 403 for a signed-in non-first-admin (friend); the
10
10
  * admin's happy path still mints when a friend exists alongside.
11
11
  */
@@ -14,7 +14,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14
14
  import { mkdtempSync, rmSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
- import { CHANNEL_TOKEN_TTL_SECONDS, handleChannelToken } from "../admin-channel-token.ts";
17
+ import { AGENT_TOKEN_TTL_SECONDS, handleAgentToken } from "../admin-agent-token.ts";
18
18
  import { hubDbPath, openHubDb } from "../hub-db.ts";
19
19
  import { validateAccessToken } from "../jwt-sign.ts";
20
20
  import { SESSION_TTL_MS, buildSessionCookie, createSession, deleteSession } from "../sessions.ts";
@@ -29,7 +29,7 @@ interface Harness {
29
29
  }
30
30
 
31
31
  function makeHarness(): Harness {
32
- const dir = mkdtempSync(join(tmpdir(), "phub-channel-token-"));
32
+ const dir = mkdtempSync(join(tmpdir(), "phub-agent-token-"));
33
33
  const db = openHubDb(hubDbPath(dir));
34
34
  return {
35
35
  db,
@@ -79,10 +79,10 @@ async function withAdminAndFriend(): Promise<{
79
79
  };
80
80
  }
81
81
 
82
- describe("handleChannelToken", () => {
82
+ describe("handleAgentToken", () => {
83
83
  test("401 when no session cookie is present", async () => {
84
- const req = new Request(`${ISSUER}/admin/channel-token`);
85
- const res = await handleChannelToken(req, { db: harness.db, issuer: ISSUER });
84
+ const req = new Request(`${ISSUER}/admin/agent-token`);
85
+ const res = await handleAgentToken(req, { db: harness.db, issuer: ISSUER });
86
86
  expect(res.status).toBe(401);
87
87
  const body = (await res.json()) as { error: string };
88
88
  expect(body.error).toBe("unauthenticated");
@@ -92,54 +92,54 @@ describe("handleChannelToken", () => {
92
92
  const { cookie } = await withSession();
93
93
  const sid = cookie.match(/parachute_hub_session=([^;]+)/)?.[1] ?? "";
94
94
  deleteSession(harness.db, sid);
95
- const req = new Request(`${ISSUER}/admin/channel-token`, { headers: { cookie } });
96
- const res = await handleChannelToken(req, { db: harness.db, issuer: ISSUER });
95
+ const req = new Request(`${ISSUER}/admin/agent-token`, { headers: { cookie } });
96
+ const res = await handleAgentToken(req, { db: harness.db, issuer: ISSUER });
97
97
  expect(res.status).toBe(401);
98
98
  });
99
99
 
100
100
  test("405 on POST", async () => {
101
101
  const { cookie } = await withSession();
102
- const req = new Request(`${ISSUER}/admin/channel-token`, {
102
+ const req = new Request(`${ISSUER}/admin/agent-token`, {
103
103
  method: "POST",
104
104
  headers: { cookie },
105
105
  });
106
- const res = await handleChannelToken(req, { db: harness.db, issuer: ISSUER });
106
+ const res = await handleAgentToken(req, { db: harness.db, issuer: ISSUER });
107
107
  expect(res.status).toBe(405);
108
108
  });
109
109
 
110
- test("200 mints a JWT carrying aud:channel + channel:read channel:send channel:admin", async () => {
110
+ test("200 mints a JWT carrying aud:agent + agent:read agent:send agent:admin", async () => {
111
111
  const { cookie, userId } = await withSession();
112
112
  rotateSigningKey(harness.db);
113
- const req = new Request(`${ISSUER}/admin/channel-token`, { headers: { cookie } });
114
- const res = await handleChannelToken(req, { db: harness.db, issuer: ISSUER });
113
+ const req = new Request(`${ISSUER}/admin/agent-token`, { headers: { cookie } });
114
+ const res = await handleAgentToken(req, { db: harness.db, issuer: ISSUER });
115
115
  expect(res.status).toBe(200);
116
116
  expect(res.headers.get("cache-control")).toBe("no-store");
117
117
 
118
118
  const body = (await res.json()) as { token: string; expires_at: string; scopes: string[] };
119
- // `channel:send` (post) + `channel:read` (SSE replies) + `channel:admin`
119
+ // `agent:send` (post) + `agent:read` (SSE replies) + `agent:admin`
120
120
  // (config UI list/edit — 2026-06-09 modular-UI architecture P3). Deliberately
121
- // NOT `channel:write` — that's the session-reply scope a UI token must not hold.
122
- expect(body.scopes).toEqual(["channel:read", "channel:send", "channel:admin"]);
123
- expect(body.scopes).not.toContain("channel:write");
121
+ // NOT `agent:write` — that's the session-reply scope a UI token must not hold.
122
+ expect(body.scopes).toEqual(["agent:read", "agent:send", "agent:admin"]);
123
+ expect(body.scopes).not.toContain("agent:write");
124
124
  expect(body.token.length).toBeGreaterThan(20);
125
125
 
126
126
  const expMs = new Date(body.expires_at).getTime();
127
127
  const skew = expMs - Date.now();
128
- expect(skew).toBeGreaterThan((CHANNEL_TOKEN_TTL_SECONDS - 30) * 1000);
129
- expect(skew).toBeLessThan((CHANNEL_TOKEN_TTL_SECONDS + 30) * 1000);
128
+ expect(skew).toBeGreaterThan((AGENT_TOKEN_TTL_SECONDS - 30) * 1000);
129
+ expect(skew).toBeLessThan((AGENT_TOKEN_TTL_SECONDS + 30) * 1000);
130
130
 
131
131
  const validated = await validateAccessToken(harness.db, body.token, ISSUER);
132
132
  expect(validated.payload.sub).toBe(userId);
133
133
  expect(validated.payload.iss).toBe(ISSUER);
134
- // Bare service audience — channel validates `aud === "channel"`
135
- // (parachute-channel src/hub-jwt.ts CHANNEL_AUDIENCE).
136
- expect(validated.payload.aud).toBe("channel");
134
+ // Bare service audience — agent validates `aud === "agent"`
135
+ // (parachute-agent src/hub-jwt.ts).
136
+ expect(validated.payload.aud).toBe("agent");
137
137
  const scopeClaim = (validated.payload as { scope?: string }).scope ?? "";
138
138
  const scopes = scopeClaim.split(/\s+/);
139
- expect(scopes).toContain("channel:read");
140
- expect(scopes).toContain("channel:send");
141
- expect(scopes).toContain("channel:admin");
142
- expect(scopes).not.toContain("channel:write");
139
+ expect(scopes).toContain("agent:read");
140
+ expect(scopes).toContain("agent:send");
141
+ expect(scopes).toContain("agent:admin");
142
+ expect(scopes).not.toContain("agent:write");
143
143
  });
144
144
 
145
145
  test("403 not_admin when a signed-in non-first-admin (friend) hits the endpoint", async () => {
@@ -148,10 +148,10 @@ describe("handleChannelToken", () => {
148
148
  // first-admin row.
149
149
  const { friendCookie } = await withAdminAndFriend();
150
150
  rotateSigningKey(harness.db);
151
- const req = new Request(`${ISSUER}/admin/channel-token`, {
151
+ const req = new Request(`${ISSUER}/admin/agent-token`, {
152
152
  headers: { cookie: friendCookie },
153
153
  });
154
- const res = await handleChannelToken(req, { db: harness.db, issuer: ISSUER });
154
+ const res = await handleAgentToken(req, { db: harness.db, issuer: ISSUER });
155
155
  expect(res.status).toBe(403);
156
156
  const body = (await res.json()) as { error: string; error_description: string };
157
157
  expect(body.error).toBe("not_admin");
@@ -161,10 +161,10 @@ describe("handleChannelToken", () => {
161
161
  test("first-admin path still succeeds when a friend exists alongside", async () => {
162
162
  const { adminCookie, adminId } = await withAdminAndFriend();
163
163
  rotateSigningKey(harness.db);
164
- const req = new Request(`${ISSUER}/admin/channel-token`, {
164
+ const req = new Request(`${ISSUER}/admin/agent-token`, {
165
165
  headers: { cookie: adminCookie },
166
166
  });
167
- const res = await handleChannelToken(req, { db: harness.db, issuer: ISSUER });
167
+ const res = await handleAgentToken(req, { db: harness.db, issuer: ISSUER });
168
168
  expect(res.status).toBe(200);
169
169
  const body = (await res.json()) as { token: string };
170
170
  const validated = await validateAccessToken(harness.db, body.token, ISSUER);