@openparachute/hub 0.7.4 → 0.7.5-rc.2
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 +10 -3
- package/src/__tests__/git-notify.test.ts +144 -0
- package/src/__tests__/git-transport.test.ts +492 -0
- package/src/git-notify.ts +176 -0
- package/src/git-transport.ts +515 -0
- package/src/hub-server.ts +42 -0
- package/src/oauth-handlers.ts +1 -0
- package/src/scope-explanations.ts +21 -6
package/src/hub-server.ts
CHANGED
|
@@ -239,6 +239,8 @@ import { CONFIG_DIR, SERVICES_MANIFEST_PATH } from "./config.ts";
|
|
|
239
239
|
import { applyCorsHeaders, corsPreflightResponse, isCorsAllowedRoute } from "./cors.ts";
|
|
240
240
|
import { ensureCsrfToken } from "./csrf.ts";
|
|
241
241
|
import { readExposeState } from "./expose-state.ts";
|
|
242
|
+
import { notifySurfacePushed } from "./git-notify.ts";
|
|
243
|
+
import { handleGitTransport } from "./git-transport.ts";
|
|
242
244
|
import { HUB_DEFAULT_PORT, HUB_SVC, clearHubPort, writeHubPort } from "./hub-control.ts";
|
|
243
245
|
import {
|
|
244
246
|
classifyDbError,
|
|
@@ -1212,6 +1214,12 @@ export interface HubFetchDeps {
|
|
|
1212
1214
|
* the doc reflect `parachute vault create` etc. without re-running expose.
|
|
1213
1215
|
*/
|
|
1214
1216
|
manifestPath?: string;
|
|
1217
|
+
/**
|
|
1218
|
+
* Directory holding the per-surface bare repos for the git-transport endpoint
|
|
1219
|
+
* (`/git/<name>/*` → `<gitRoot>/<name>.git`). Tests point this at a tmpdir;
|
|
1220
|
+
* production defaults to `<CONFIG_DIR>/hub/git`.
|
|
1221
|
+
*/
|
|
1222
|
+
gitRoot?: string;
|
|
1215
1223
|
/**
|
|
1216
1224
|
* Path to `connections.json` (the Connections store, P5). Tests point this
|
|
1217
1225
|
* at a tmpdir; production defaults to `<CONFIG_DIR>/connections.json`.
|
|
@@ -1983,6 +1991,7 @@ export function hubFetch(
|
|
|
1983
1991
|
const getDb = deps?.getDb;
|
|
1984
1992
|
const configuredIssuer = deps?.issuer;
|
|
1985
1993
|
const manifestPath = deps?.manifestPath ?? SERVICES_MANIFEST_PATH;
|
|
1994
|
+
const gitRoot = deps?.gitRoot ?? join(CONFIG_DIR, "hub", "git");
|
|
1986
1995
|
const spaDistDir = deps?.spaDistDir ?? defaultSpaDistDir();
|
|
1987
1996
|
const loopbackPort = deps?.loopbackPort;
|
|
1988
1997
|
const loadExposeHubOrigin =
|
|
@@ -3801,6 +3810,39 @@ export function hubFetch(
|
|
|
3801
3810
|
return serveSpa(spaDistDir, pathname, "/admin");
|
|
3802
3811
|
}
|
|
3803
3812
|
|
|
3813
|
+
// /git/<name>/* — hub-authenticated git smart-HTTP transport (Surface
|
|
3814
|
+
// Git Transport, Phase 0a). Placed BEFORE the generic services.json
|
|
3815
|
+
// proxy so a `/git/` route is never shadowed by a module mount. The
|
|
3816
|
+
// endpoint is AUTH-gated, not LAYER-gated: it's reachable from any
|
|
3817
|
+
// exposure layer because the hub JWT (validated against the multi-origin
|
|
3818
|
+
// iss-set) is the gate. It NEVER builds or executes the pushed tree — the
|
|
3819
|
+
// hub only receives + stores bytes (the RCE-bearing build is surface-host's
|
|
3820
|
+
// sandboxed job, Phase 0b). See src/git-transport.ts.
|
|
3821
|
+
if (pathname.startsWith("/git/")) {
|
|
3822
|
+
if (!getDb) return new Response("not found", { status: 404 });
|
|
3823
|
+
const db = getDb();
|
|
3824
|
+
const issuer = oauthDeps(req).issuer;
|
|
3825
|
+
return handleGitTransport(req, {
|
|
3826
|
+
db,
|
|
3827
|
+
gitRoot,
|
|
3828
|
+
knownIssuers: () => oauthDeps(req).hubBoundOrigins(),
|
|
3829
|
+
peerAddr,
|
|
3830
|
+
// Deploy hand-off (Phase 0b §5 step 5): on a successful push, notify
|
|
3831
|
+
// the surface module over HTTP + a hub JWT so it pulls + builds +
|
|
3832
|
+
// serves. NEVER a shell-out that builds the pushed tree — the hub
|
|
3833
|
+
// only sends the authenticated signal (git-notify.ts). Fire-and-
|
|
3834
|
+
// forget; a notify failure is logged, never surfaced to the pusher.
|
|
3835
|
+
onPushed: async (name) => {
|
|
3836
|
+
await notifySurfacePushed(name, {
|
|
3837
|
+
db,
|
|
3838
|
+
issuer: issuer ?? `http://127.0.0.1:${loopbackPort ?? 1939}`,
|
|
3839
|
+
resolveModuleOrigin: makeResolveModuleOrigin(manifestPath),
|
|
3840
|
+
cloneBaseOrigin: `http://127.0.0.1:${loopbackPort ?? 1939}`,
|
|
3841
|
+
});
|
|
3842
|
+
},
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3804
3846
|
// Generic services.json-driven dispatch for non-vault modules. Reaches
|
|
3805
3847
|
// here only after every hub-owned prefix above has had its turn — so
|
|
3806
3848
|
// `/`, `/admin/*`, `/oauth/*`, `/.well-known/*`, `/hub/*`, `/vault/*`,
|
package/src/oauth-handlers.ts
CHANGED
|
@@ -46,12 +46,13 @@ export const SCOPE_EXPLANATIONS: Record<string, ScopeExplanation> = {
|
|
|
46
46
|
"Read and write everything, plus admin: config & settings, triggers & automation, GitHub backup, and minting access tokens.",
|
|
47
47
|
level: "admin",
|
|
48
48
|
},
|
|
49
|
-
// Optional-module scopes (scribe / agent). These are in
|
|
50
|
-
// (= Object.keys(this map)) but the modules may not be
|
|
51
|
-
// GATED in `OPTIONAL_MODULE_SCOPES` (oauth-handlers.ts)
|
|
52
|
-
// `scopes_supported` when the service is in
|
|
53
|
-
// for another optional module here, add a
|
|
54
|
-
// vault-only hub will over-advertise them (the
|
|
49
|
+
// Optional-module scopes (scribe / agent / surface). These are in
|
|
50
|
+
// FIRST_PARTY_SCOPES (= Object.keys(this map)) but the modules may not be
|
|
51
|
+
// installed — so they're GATED in `OPTIONAL_MODULE_SCOPES` (oauth-handlers.ts)
|
|
52
|
+
// and only advertised in `scopes_supported` when the service is in
|
|
53
|
+
// services.json. If you add scopes for another optional module here, add a
|
|
54
|
+
// matching gate there too, or a vault-only hub will over-advertise them (the
|
|
55
|
+
// bug behind hub#489).
|
|
55
56
|
"scribe:transcribe": {
|
|
56
57
|
label: "Send audio to Scribe for transcription.",
|
|
57
58
|
level: "write",
|
|
@@ -64,6 +65,20 @@ export const SCOPE_EXPLANATIONS: Record<string, ScopeExplanation> = {
|
|
|
64
65
|
label: "Post messages to your Agent.",
|
|
65
66
|
level: "send",
|
|
66
67
|
},
|
|
68
|
+
// Surface Git Transport scopes (surface-host). `surface:read` = clone/fetch a
|
|
69
|
+
// surface's hub-hosted git repo; `surface:write` = push to it. Named forms
|
|
70
|
+
// (`surface:<name>:<verb>`) collapse to these via the 3→2-segment rule in
|
|
71
|
+
// `isKnownScope`. surface-host's module.json declares them too; listing them
|
|
72
|
+
// here makes them first-party (mintable + a consent label) even on a hub
|
|
73
|
+
// where surface-host isn't installed.
|
|
74
|
+
"surface:read": {
|
|
75
|
+
label: "Clone and fetch a surface's source (its hub-hosted git repo).",
|
|
76
|
+
level: "read",
|
|
77
|
+
},
|
|
78
|
+
"surface:write": {
|
|
79
|
+
label: "Push to a surface's source (its hub-hosted git repo).",
|
|
80
|
+
level: "write",
|
|
81
|
+
},
|
|
67
82
|
"hub:admin": {
|
|
68
83
|
label: "Manage hub identity (user accounts, signing keys, registered OAuth clients).",
|
|
69
84
|
level: "admin",
|