@openparachute/hub 0.7.7-rc.9 → 0.7.7
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/README.md +7 -7
- package/package.json +4 -12
- package/src/__tests__/account-api.test.ts +29 -2
- package/src/__tests__/account-session.test.ts +9 -1
- package/src/__tests__/account-token.test.ts +26 -2
- package/src/__tests__/admin-connections-credentials.test.ts +41 -0
- package/src/__tests__/admin-lock.test.ts +3 -14
- package/src/__tests__/admin-module-token.test.ts +10 -30
- package/src/__tests__/admin-surfaces.test.ts +21 -0
- package/src/__tests__/api-hub-upgrade.test.ts +11 -0
- package/src/__tests__/api-mint-token.test.ts +25 -0
- package/src/__tests__/api-modules-ops.test.ts +34 -29
- package/src/__tests__/api-modules.test.ts +50 -58
- package/src/__tests__/api-revoke-token.test.ts +23 -0
- package/src/__tests__/api-settings-hub-origin.test.ts +12 -0
- package/src/__tests__/api-settings-root-redirect.test.ts +159 -4
- package/src/__tests__/api-tokens.test.ts +44 -0
- package/src/__tests__/audience-gate.test.ts +24 -0
- package/src/__tests__/bearer-scheme-casing.test.ts +110 -0
- package/src/__tests__/chrome-strip.test.ts +18 -1
- package/src/__tests__/doctor.test.ts +10 -17
- package/src/__tests__/hub-command.test.ts +70 -2
- package/src/__tests__/hub-server.test.ts +322 -1
- package/src/__tests__/hub-settings.test.ts +110 -6
- package/src/__tests__/hub.test.ts +29 -0
- package/src/__tests__/install.test.ts +210 -38
- package/src/__tests__/migrate.test.ts +3 -1
- package/src/__tests__/oauth-handlers.test.ts +19 -8
- package/src/__tests__/operator-token.test.ts +1 -2
- package/src/__tests__/port-assign.test.ts +17 -26
- package/src/__tests__/root-serve.test.ts +139 -0
- package/src/__tests__/scope-explanations.test.ts +0 -2
- package/src/__tests__/serve-boot.test.ts +25 -36
- package/src/__tests__/service-spec-discovery.test.ts +30 -35
- package/src/__tests__/services-manifest.test.ts +22 -92
- package/src/__tests__/setup-wizard.test.ts +301 -22
- package/src/__tests__/setup.test.ts +8 -13
- package/src/__tests__/status.test.ts +0 -5
- package/src/__tests__/surface-notes-alias.test.ts +296 -0
- package/src/__tests__/wizard-transcription.test.ts +35 -0
- package/src/__tests__/wizard.test.ts +79 -0
- package/src/account-api.ts +6 -0
- package/src/admin-connections.ts +4 -2
- package/src/admin-lock.ts +1 -2
- package/src/admin-module-token.ts +13 -8
- package/src/admin-surfaces.ts +2 -1
- package/src/api-hub-upgrade.ts +2 -1
- package/src/api-mint-token.ts +2 -1
- package/src/api-modules-ops.ts +2 -1
- package/src/api-modules.ts +9 -7
- package/src/api-revoke-token.ts +2 -1
- package/src/api-settings-hub-origin.ts +2 -1
- package/src/api-settings-root-redirect.ts +97 -20
- package/src/api-tokens.ts +2 -1
- package/src/audience-gate.ts +2 -1
- package/src/chrome-strip.ts +16 -4
- package/src/commands/hub.ts +104 -0
- package/src/commands/install.ts +72 -27
- package/src/commands/migrate.ts +5 -1
- package/src/commands/setup.ts +0 -2
- package/src/commands/wizard-transcription.ts +24 -0
- package/src/commands/wizard.ts +34 -1
- package/src/help.ts +0 -1
- package/src/hub-server.ts +111 -54
- package/src/hub-settings.ts +147 -0
- package/src/hub.ts +64 -31
- package/src/module-ops-client.ts +2 -1
- package/src/oauth-handlers.ts +1 -11
- package/src/operator-token.ts +0 -1
- package/src/origin-check.ts +2 -2
- package/src/root-serve.ts +156 -0
- package/src/scope-explanations.ts +2 -5
- package/src/service-spec.ts +18 -47
- package/src/services-manifest.ts +9 -42
- package/src/setup-wizard.ts +144 -31
- package/src/surface-notes-alias.ts +126 -0
- package/src/__tests__/admin-agent-token.test.ts +0 -173
- package/src/admin-agent-token.ts +0 -147
package/src/admin-agent-token.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /admin/agent-token` — exchange a valid admin session cookie for a
|
|
3
|
-
* short-lived JWT carrying `agent:read agent:send agent:admin`.
|
|
4
|
-
*
|
|
5
|
-
* Renamed from `/admin/channel-token` 2026-06-17 (parachute-channel →
|
|
6
|
-
* parachute-agent). The old path keeps working via a 301 redirect in
|
|
7
|
-
* hub-server.ts for one release cycle.
|
|
8
|
-
*
|
|
9
|
-
* Why this exists: two agent-owned UIs, both served behind hub's proxy to a
|
|
10
|
-
* logged-in portal operator, need a Bearer to talk to agent's API the same
|
|
11
|
-
* way the vault-management and scribe-config SPAs do, without running the
|
|
12
|
-
* public `/oauth/authorize` flow:
|
|
13
|
-
* - The **chat UI** (`/agent/ui`) receives replies over SSE
|
|
14
|
-
* (`agent:read`) and posts a message (`agent:send`).
|
|
15
|
-
* - The **config/admin UI** (`/agent/admin`, the 2026-06-09 modular-UI
|
|
16
|
-
* architecture P3/P4 config surface) lists + edits configured channels via
|
|
17
|
-
* `agent:admin`-gated endpoints (`requireScope(SCOPE_ADMIN)` in agent's
|
|
18
|
-
* daemon).
|
|
19
|
-
*
|
|
20
|
-
* Both UIs fetch this single endpoint (`fetchToken()` against
|
|
21
|
-
* `/admin/agent-token`), so the minted token carries the union of the scopes
|
|
22
|
-
* either UI needs. The chat UI simply ignores the extra `agent:admin` scope;
|
|
23
|
-
* `requireScope` checks for the *presence* of a specific scope, so extra
|
|
24
|
-
* scopes never break a read/send call. This is what makes the agent config
|
|
25
|
-
* UI work without re-touching the agent repo — the hub endpoint the config
|
|
26
|
-
* UI already calls now mints the admin scope it needs (2026-06-09 modular-UI
|
|
27
|
-
* architecture, P3).
|
|
28
|
-
*
|
|
29
|
-
* Scope choice — `agent:read agent:send agent:admin`, deliberately NOT
|
|
30
|
-
* `agent:write`:
|
|
31
|
-
* - `agent:read` — receive replies over SSE.
|
|
32
|
-
* - `agent:send` — post a message into the channel.
|
|
33
|
-
* - `agent:admin` — list + edit channel config (the config UI).
|
|
34
|
-
* - `agent:write` is the *session-reply* scope (a connected Claude Code
|
|
35
|
-
* session replying on a channel). A UI token must not be able to
|
|
36
|
-
* impersonate a session, so we never mint `agent:write` here.
|
|
37
|
-
*
|
|
38
|
-
* Audience: `agent` (the bare service prefix). Agent validates the JWT's
|
|
39
|
-
* `aud` claim against the literal string `"agent"` (parachute-agent
|
|
40
|
-
* `src/hub-jwt.ts`), the same shape `inferAudience` in oauth-handlers.ts
|
|
41
|
-
* stamps for the public OAuth flow — so hub-minted and OAuth-minted agent
|
|
42
|
-
* tokens are indistinguishable to agent. Unlike the per-vault admin token
|
|
43
|
-
* (`vault.<name>`), agent has a single bare audience. (During the rename
|
|
44
|
-
* transition window the agent daemon dual-accepts both `aud: "channel"` and
|
|
45
|
-
* `aud: "agent"`, so pre-rename tokens keep validating until re-minted — see
|
|
46
|
-
* the channel→agent migration doc; that back-compat lives in the daemon, not
|
|
47
|
-
* here.)
|
|
48
|
-
*
|
|
49
|
-
* Multi-user Phase 1 gate: the session must belong to the first admin (the
|
|
50
|
-
* single hub admin under the Phase 1 model — see `users.ts:isFirstAdmin`),
|
|
51
|
-
* mirroring host-admin-token and vault-admin-token. Friends pinned to a vault
|
|
52
|
-
* use the OAuth flow for their assigned scopes; they don't get an agent
|
|
53
|
-
* Bearer via this endpoint.
|
|
54
|
-
*
|
|
55
|
-
* Tokens minted here are short-lived (10 min — matches host/vault admin
|
|
56
|
-
* tokens); the UI re-fetches on near-expiry.
|
|
57
|
-
*/
|
|
58
|
-
import type { Database } from "bun:sqlite";
|
|
59
|
-
import { lockedResponse, requireUnlocked } from "./admin-lock.ts";
|
|
60
|
-
import { signAccessToken } from "./jwt-sign.ts";
|
|
61
|
-
import { findSession, parseSessionCookie } from "./sessions.ts";
|
|
62
|
-
import { isFirstAdmin } from "./users.ts";
|
|
63
|
-
|
|
64
|
-
/** Short TTL — matches host/vault admin-token. UI re-fetches on near-expiry. */
|
|
65
|
-
export const AGENT_TOKEN_TTL_SECONDS = 10 * 60;
|
|
66
|
-
const AGENT_AUDIENCE = "agent";
|
|
67
|
-
const AGENT_CLIENT_ID = "parachute-hub-spa";
|
|
68
|
-
/**
|
|
69
|
-
* `agent:read` (SSE replies) + `agent:send` (post a message) +
|
|
70
|
-
* `agent:admin` (list + edit channel config — the config UI). Deliberately
|
|
71
|
-
* NOT `agent:write` — that's the session-reply scope, and a UI token must
|
|
72
|
-
* not be able to impersonate a connected session. The chat UI ignores the
|
|
73
|
-
* extra `agent:admin`; the config UI needs it (2026-06-09 modular-UI
|
|
74
|
-
* architecture, P3 — the hub endpoint the agent config UI already calls
|
|
75
|
-
* mints the admin scope so the agent repo doesn't have to change).
|
|
76
|
-
*/
|
|
77
|
-
export const AGENT_TOKEN_SCOPES = ["agent:read", "agent:send", "agent:admin"] as const;
|
|
78
|
-
|
|
79
|
-
export interface MintAgentTokenDeps {
|
|
80
|
-
db: Database;
|
|
81
|
-
/** Hub origin — written into JWT `iss`. */
|
|
82
|
-
issuer: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function handleAgentToken(req: Request, deps: MintAgentTokenDeps): Promise<Response> {
|
|
86
|
-
if (req.method !== "GET") {
|
|
87
|
-
return jsonError(405, "method_not_allowed", "use GET");
|
|
88
|
-
}
|
|
89
|
-
const sid = parseSessionCookie(req.headers.get("cookie"));
|
|
90
|
-
const session = sid ? findSession(deps.db, sid) : null;
|
|
91
|
-
if (!session || !sid) {
|
|
92
|
-
return jsonError(401, "unauthenticated", "no admin session — sign in at /login first");
|
|
93
|
-
}
|
|
94
|
-
// First-admin gate (mirrors host/vault-admin-token). A friend account
|
|
95
|
-
// (non-first-admin user created via `/api/users`) holds a valid session but
|
|
96
|
-
// must not mint an agent Bearer. Without this check, any signed-in friend
|
|
97
|
-
// hitting `GET /admin/agent-token` would walk away with a token carrying
|
|
98
|
-
// `agent:read agent:send`.
|
|
99
|
-
if (!isFirstAdmin(deps.db, session.userId)) {
|
|
100
|
-
return jsonError(
|
|
101
|
-
403,
|
|
102
|
-
"not_admin",
|
|
103
|
-
"agent token mint is restricted to the hub admin — your account home is at /account/",
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
// Admin screen-lock gate (see admin-host-admin-token.ts). A locked admin
|
|
107
|
-
// session can't mint an agent Bearer, so agent's config + chat UIs show
|
|
108
|
-
// the lock screen / fail closed until the operator unlocks. Off by default.
|
|
109
|
-
if (!requireUnlocked(deps.db, sid).ok) {
|
|
110
|
-
return lockedResponse();
|
|
111
|
-
}
|
|
112
|
-
const minted = await signAccessToken(deps.db, {
|
|
113
|
-
sub: session.userId,
|
|
114
|
-
scopes: [...AGENT_TOKEN_SCOPES],
|
|
115
|
-
audience: AGENT_AUDIENCE,
|
|
116
|
-
clientId: AGENT_CLIENT_ID,
|
|
117
|
-
issuer: deps.issuer,
|
|
118
|
-
ttlSeconds: AGENT_TOKEN_TTL_SECONDS,
|
|
119
|
-
// Agent tokens carry no per-user vault pin — the UI Bearer talks to an
|
|
120
|
-
// agent-scoped endpoint, not to a single vault. Empty `vault_scope` is
|
|
121
|
-
// the "no per-user restriction" sentinel matching host-admin tokens.
|
|
122
|
-
vaultScope: [],
|
|
123
|
-
});
|
|
124
|
-
return new Response(
|
|
125
|
-
JSON.stringify({
|
|
126
|
-
token: minted.token,
|
|
127
|
-
expires_at: minted.expiresAt,
|
|
128
|
-
scopes: AGENT_TOKEN_SCOPES,
|
|
129
|
-
}),
|
|
130
|
-
{
|
|
131
|
-
status: 200,
|
|
132
|
-
headers: {
|
|
133
|
-
"content-type": "application/json",
|
|
134
|
-
// No browser cache — token rotates per-fetch, and a stale 200 from a
|
|
135
|
-
// back/forward navigation could hand the UI a long-expired JWT.
|
|
136
|
-
"cache-control": "no-store",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function jsonError(status: number, error: string, description: string): Response {
|
|
143
|
-
return new Response(JSON.stringify({ error, error_description: description }), {
|
|
144
|
-
status,
|
|
145
|
-
headers: { "content-type": "application/json" },
|
|
146
|
-
});
|
|
147
|
-
}
|